From 7b76233290bd9dead1848f28ed6d0edfcceb8e09 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Wed, 27 Dec 2006 04:35:09 +0000 Subject: [PATCH 1/1] Correcting tag name to be like previous ones --- .indent.pro | 33 + AUTHORS | 173 + Config.in | 479 + INSTALL | 125 + LICENSE | 348 + Makefile | 1289 ++ Makefile.custom | 145 + Makefile.flags | 28 + Makefile.help | 42 + README | 198 + TODO | 309 + applets/Kbuild | 21 + applets/applets.c | 483 + applets/busybox.c | 148 + applets/busybox.mkll | 24 + applets/individual.c | 27 + applets/install.sh | 94 + applets/usage.c | 17 + applets/usage_compressed | 19 + arch/i386/Makefile | 5 + archival/Config.in | 300 + archival/Kbuild | 22 + archival/ar.c | 93 + archival/bunzip2.c | 66 + archival/cpio.c | 86 + archival/dpkg.c | 1819 ++ archival/dpkg_deb.c | 96 + archival/gunzip.c | 162 + archival/gzip.c | 2473 +++ archival/libunarchive/Kbuild | 59 + archival/libunarchive/archive_xread_all_eof.c | 20 + archival/libunarchive/check_header_gzip.c | 62 + archival/libunarchive/data_align.c | 22 + archival/libunarchive/data_extract_all.c | 119 + .../libunarchive/data_extract_to_buffer.c | 18 + .../libunarchive/data_extract_to_stdout.c | 12 + archival/libunarchive/data_skip.c | 16 + archival/libunarchive/decompress_bunzip2.c | 731 + archival/libunarchive/decompress_uncompress.c | 292 + archival/libunarchive/decompress_unlzma.c | 478 + archival/libunarchive/decompress_unzip.c | 924 ++ archival/libunarchive/filter_accept_all.c | 17 + archival/libunarchive/filter_accept_list.c | 19 + .../filter_accept_list_reassign.c | 48 + .../libunarchive/filter_accept_reject_list.c | 33 + archival/libunarchive/find_list_entry.c | 54 + archival/libunarchive/get_header_ar.c | 112 + archival/libunarchive/get_header_cpio.c | 160 + archival/libunarchive/get_header_tar.c | 275 + archival/libunarchive/get_header_tar_bz2.c | 27 + archival/libunarchive/get_header_tar_gz.c | 31 + archival/libunarchive/get_header_tar_lzma.c | 22 + archival/libunarchive/header_list.c | 11 + archival/libunarchive/header_skip.c | 10 + archival/libunarchive/header_verbose_list.c | 31 + archival/libunarchive/init_handle.c | 22 + archival/libunarchive/open_transformer.c | 44 + archival/libunarchive/seek_by_jump.c | 24 + archival/libunarchive/seek_by_read.c | 19 + archival/libunarchive/unpack_ar_archive.c | 22 + archival/rpm.c | 319 + archival/rpm2cpio.c | 89 + archival/tar.c | 896 + archival/uncompress.c | 94 + archival/unlzma.c | 64 + archival/unzip.c | 386 + console-tools/Config.in | 105 + console-tools/Kbuild | 19 + console-tools/chvt.c | 33 + console-tools/clear.c | 21 + console-tools/deallocvt.c | 37 + console-tools/dumpkmap.c | 65 + console-tools/loadfont.c | 193 + console-tools/loadkmap.c | 61 + console-tools/openvt.c | 45 + console-tools/reset.c | 32 + console-tools/resize.c | 38 + console-tools/setconsole.c | 47 + console-tools/setkeycodes.c | 53 + console-tools/setlogcons.c | 31 + coreutils/Config.in | 782 + coreutils/Kbuild | 81 + coreutils/basename.c | 50 + coreutils/cal.c | 356 + coreutils/cat.c | 41 + coreutils/catv.c | 69 + coreutils/chgrp.c | 27 + coreutils/chmod.c | 157 + coreutils/chown.c | 98 + coreutils/chroot.c | 37 + coreutils/cksum.c | 53 + coreutils/cmp.c | 127 + coreutils/comm.c | 126 + coreutils/cp.c | 92 + coreutils/cut.c | 285 + coreutils/date.c | 239 + coreutils/dd.c | 248 + coreutils/df.c | 154 + coreutils/diff.c | 1243 ++ coreutils/dirname.c | 26 + coreutils/dos2unix.c | 115 + coreutils/du.c | 250 + coreutils/echo.c | 158 + coreutils/env.c | 129 + coreutils/expr.c | 505 + coreutils/false.c | 19 + coreutils/fold.c | 154 + coreutils/head.c | 139 + coreutils/hostid.c | 25 + coreutils/id.c | 111 + coreutils/install.c | 131 + coreutils/length.c | 19 + coreutils/libcoreutils/Kbuild | 12 + coreutils/libcoreutils/coreutils.h | 16 + coreutils/libcoreutils/cp_mv_stat.c | 43 + coreutils/libcoreutils/getopt_mk_fifo_nod.c | 40 + coreutils/ln.c | 104 + coreutils/logname.c | 42 + coreutils/ls.c | 940 ++ coreutils/md5_sha1_sum.c | 186 + coreutils/mkdir.c | 66 + coreutils/mkfifo.c | 38 + coreutils/mknod.c | 50 + coreutils/mv.c | 131 + coreutils/nice.c | 54 + coreutils/nohup.c | 53 + coreutils/od.c | 224 + coreutils/od_bloaty.c | 1466 ++ coreutils/printenv.c | 41 + coreutils/printf.c | 321 + coreutils/pwd.c | 24 + coreutils/realpath.c | 48 + coreutils/rm.c | 52 + coreutils/rmdir.c | 60 + coreutils/seq.c | 40 + coreutils/sleep.c | 67 + coreutils/sort.c | 357 + coreutils/stat.c | 538 + coreutils/stty.c | 1302 ++ coreutils/sum.c | 156 + coreutils/sync.c | 23 + coreutils/tail.c | 326 + coreutils/tee.c | 100 + coreutils/test.c | 583 + coreutils/touch.c | 63 + coreutils/tr.c | 253 + coreutils/true.c | 19 + coreutils/tty.c | 45 + coreutils/uname.c | 107 + coreutils/uniq.c | 103 + coreutils/usleep.c | 28 + coreutils/uudecode.c | 181 + coreutils/uuencode.c | 75 + coreutils/watch.c | 79 + coreutils/wc.c | 204 + coreutils/who.c | 67 + coreutils/whoami.c | 25 + coreutils/yes.c | 41 + debianutils/Config.in | 88 + debianutils/Kbuild | 13 + debianutils/mktemp.c | 48 + debianutils/pipe_progress.c | 42 + debianutils/readlink.c | 51 + debianutils/run_parts.c | 191 + debianutils/start_stop_daemon.c | 319 + debianutils/which.c | 43 + docs/autodocifier.pl | 304 + docs/busybox.net/FAQ.html | 1108 ++ docs/busybox.net/about.html | 23 + docs/busybox.net/busybox-growth.ps | 404 + docs/busybox.net/copyright.txt | 30 + docs/busybox.net/developer.html | 69 + docs/busybox.net/download.html | 29 + docs/busybox.net/footer.html | 47 + docs/busybox.net/header.html | 96 + docs/busybox.net/images/back.png | Bin 0 -> 322 bytes docs/busybox.net/images/busybox.jpeg | Bin 0 -> 9023 bytes docs/busybox.net/images/busybox.png | Bin 0 -> 34014 bytes docs/busybox.net/images/busybox1.png | Bin 0 -> 10913 bytes docs/busybox.net/images/busybox2.jpg | Bin 0 -> 8204 bytes docs/busybox.net/images/busybox3.jpg | Bin 0 -> 3292 bytes docs/busybox.net/images/dir.png | Bin 0 -> 309 bytes docs/busybox.net/images/donate.png | Bin 0 -> 807 bytes docs/busybox.net/images/fm.mini.png | Bin 0 -> 7708 bytes docs/busybox.net/images/gfx_by_gimp.png | Bin 0 -> 3955 bytes docs/busybox.net/images/ltbutton2.png | Bin 0 -> 6798 bytes docs/busybox.net/images/osuosl.png | Bin 0 -> 8683 bytes docs/busybox.net/images/sdsmall.png | Bin 0 -> 1593 bytes docs/busybox.net/images/text.png | Bin 0 -> 307 bytes docs/busybox.net/images/valid-html401.png | Bin 0 -> 1950 bytes docs/busybox.net/images/vh40.gif | Bin 0 -> 906 bytes docs/busybox.net/images/written.in.vi.png | Bin 0 -> 4394 bytes docs/busybox.net/index.html | 1 + docs/busybox.net/license.html | 97 + docs/busybox.net/links.html | 19 + docs/busybox.net/lists.html | 46 + docs/busybox.net/news.html | 185 + docs/busybox.net/oldnews.html | 1140 ++ docs/busybox.net/products.html | 170 + docs/busybox.net/screenshot.html | 62 + docs/busybox.net/shame.html | 82 + docs/busybox.net/sponsors.html | 35 + docs/busybox.net/subversion.html | 52 + docs/busybox.net/tinyutils.html | 86 + docs/busybox_footer.pod | 258 + docs/busybox_header.pod | 82 + docs/contributing.txt | 449 + docs/ipv4_ipv6.txt | 223 + docs/new-applet-HOWTO.txt | 150 + docs/sigint.htm | 627 + docs/style-guide.txt | 689 + docs/tar_pax.txt | 239 + e2fsprogs/Config.in | 67 + e2fsprogs/Kbuild | 16 + e2fsprogs/README | 3 + e2fsprogs/blkid/Kbuild | 23 + e2fsprogs/blkid/blkid.h | 105 + e2fsprogs/blkid/blkidP.h | 187 + e2fsprogs/blkid/blkid_getsize.c | 179 + e2fsprogs/blkid/cache.c | 126 + e2fsprogs/blkid/dev.c | 214 + e2fsprogs/blkid/devname.c | 368 + e2fsprogs/blkid/devno.c | 223 + e2fsprogs/blkid/list.c | 110 + e2fsprogs/blkid/list.h | 73 + e2fsprogs/blkid/probe.c | 721 + e2fsprogs/blkid/probe.h | 375 + e2fsprogs/blkid/read.c | 462 + e2fsprogs/blkid/resolve.c | 139 + e2fsprogs/blkid/save.c | 189 + e2fsprogs/blkid/tag.c | 432 + e2fsprogs/chattr.c | 218 + e2fsprogs/e2fsbb.h | 43 + e2fsprogs/e2fsck.c | 13552 +++++++++++++++ e2fsprogs/e2fsck.h | 640 + e2fsprogs/e2p/Kbuild | 15 + e2fsprogs/e2p/e2p.h | 64 + e2fsprogs/e2p/feature.c | 187 + e2fsprogs/e2p/fgetsetflags.c | 70 + e2fsprogs/e2p/fgetsetversion.c | 70 + e2fsprogs/e2p/hashstr.c | 70 + e2fsprogs/e2p/iod.c | 52 + e2fsprogs/e2p/ls.c | 273 + e2fsprogs/e2p/mntopts.c | 134 + e2fsprogs/e2p/ostype.c | 74 + e2fsprogs/e2p/parse_num.c | 65 + e2fsprogs/e2p/pe.c | 32 + e2fsprogs/e2p/pf.c | 74 + e2fsprogs/e2p/ps.c | 27 + e2fsprogs/e2p/uuid.c | 78 + e2fsprogs/ext2fs/Kbuild | 23 + e2fsprogs/ext2fs/alloc.c | 174 + e2fsprogs/ext2fs/alloc_sb.c | 58 + e2fsprogs/ext2fs/alloc_stats.c | 53 + e2fsprogs/ext2fs/alloc_tables.c | 118 + e2fsprogs/ext2fs/badblocks.c | 328 + e2fsprogs/ext2fs/bb_compat.c | 64 + e2fsprogs/ext2fs/bb_inode.c | 268 + e2fsprogs/ext2fs/bitmaps.c | 213 + e2fsprogs/ext2fs/bitops.c | 91 + e2fsprogs/ext2fs/bitops.h | 107 + e2fsprogs/ext2fs/block.c | 438 + e2fsprogs/ext2fs/bmap.c | 264 + e2fsprogs/ext2fs/bmove.c | 156 + e2fsprogs/ext2fs/brel.h | 87 + e2fsprogs/ext2fs/brel_ma.c | 196 + e2fsprogs/ext2fs/check_desc.c | 69 + e2fsprogs/ext2fs/closefs.c | 381 + e2fsprogs/ext2fs/cmp_bitmaps.c | 73 + e2fsprogs/ext2fs/dblist.c | 260 + e2fsprogs/ext2fs/dblist_dir.c | 76 + e2fsprogs/ext2fs/dir_iterate.c | 220 + e2fsprogs/ext2fs/dirblock.c | 133 + e2fsprogs/ext2fs/dirhash.c | 234 + e2fsprogs/ext2fs/dupfs.c | 97 + e2fsprogs/ext2fs/e2image.h | 52 + e2fsprogs/ext2fs/expanddir.c | 127 + e2fsprogs/ext2fs/ext2_err.h | 116 + e2fsprogs/ext2fs/ext2_ext_attr.h | 53 + e2fsprogs/ext2fs/ext2_fs.h | 570 + e2fsprogs/ext2fs/ext2_io.h | 114 + e2fsprogs/ext2fs/ext2_types.h | 2 + e2fsprogs/ext2fs/ext2fs.h | 923 ++ e2fsprogs/ext2fs/ext2fsP.h | 89 + e2fsprogs/ext2fs/ext2fs_inline.c | 367 + e2fsprogs/ext2fs/ext_attr.c | 101 + e2fsprogs/ext2fs/fileio.c | 377 + e2fsprogs/ext2fs/finddev.c | 199 + e2fsprogs/ext2fs/flushb.c | 83 + e2fsprogs/ext2fs/freefs.c | 128 + e2fsprogs/ext2fs/gen_bitmap.c | 49 + e2fsprogs/ext2fs/get_pathname.c | 157 + e2fsprogs/ext2fs/getsectsize.c | 58 + e2fsprogs/ext2fs/getsize.c | 291 + e2fsprogs/ext2fs/icount.c | 467 + e2fsprogs/ext2fs/imager.c | 377 + e2fsprogs/ext2fs/ind_block.c | 71 + e2fsprogs/ext2fs/initialize.c | 388 + e2fsprogs/ext2fs/inline.c | 33 + e2fsprogs/ext2fs/inode.c | 768 + e2fsprogs/ext2fs/inode_io.c | 271 + e2fsprogs/ext2fs/io_manager.c | 70 + e2fsprogs/ext2fs/irel.h | 115 + e2fsprogs/ext2fs/irel_ma.c | 367 + e2fsprogs/ext2fs/ismounted.c | 357 + e2fsprogs/ext2fs/jfs_dat.h | 65 + e2fsprogs/ext2fs/kernel-jbd.h | 236 + e2fsprogs/ext2fs/kernel-list.h | 113 + e2fsprogs/ext2fs/link.c | 135 + e2fsprogs/ext2fs/lookup.c | 70 + e2fsprogs/ext2fs/mkdir.c | 142 + e2fsprogs/ext2fs/mkjournal.c | 428 + e2fsprogs/ext2fs/namei.c | 205 + e2fsprogs/ext2fs/newdir.c | 73 + e2fsprogs/ext2fs/openfs.c | 330 + e2fsprogs/ext2fs/read_bb.c | 98 + e2fsprogs/ext2fs/read_bb_file.c | 98 + e2fsprogs/ext2fs/res_gdt.c | 221 + e2fsprogs/ext2fs/rs_bitmap.c | 107 + e2fsprogs/ext2fs/rw_bitmaps.c | 296 + e2fsprogs/ext2fs/sparse.c | 79 + e2fsprogs/ext2fs/swapfs.c | 236 + e2fsprogs/ext2fs/test_io.c | 380 + e2fsprogs/ext2fs/unix_io.c | 703 + e2fsprogs/ext2fs/unlink.c | 100 + e2fsprogs/ext2fs/valid_blk.c | 57 + e2fsprogs/ext2fs/version.c | 51 + e2fsprogs/ext2fs/write_bb_file.c | 35 + e2fsprogs/fsck.c | 1392 ++ e2fsprogs/fsck.h | 16 + e2fsprogs/lsattr.c | 128 + e2fsprogs/mke2fs.c | 1336 ++ e2fsprogs/tune2fs.c | 729 + e2fsprogs/util.c | 267 + e2fsprogs/util.h | 22 + e2fsprogs/uuid/Kbuild | 14 + e2fsprogs/uuid/compare.c | 55 + e2fsprogs/uuid/gen_uuid.c | 305 + e2fsprogs/uuid/pack.c | 69 + e2fsprogs/uuid/parse.c | 80 + e2fsprogs/uuid/unpack.c | 63 + e2fsprogs/uuid/unparse.c | 77 + e2fsprogs/uuid/uuid.h | 104 + e2fsprogs/uuid/uuidP.h | 60 + e2fsprogs/uuid/uuid_time.c | 161 + editors/Config.in | 131 + editors/Kbuild | 12 + editors/awk.c | 2769 ++++ editors/ed.c | 1231 ++ editors/patch.c | 275 + editors/sed.c | 1281 ++ editors/vi.c | 3926 +++++ examples/bootfloppy/bootfloppy.txt | 180 + examples/bootfloppy/display.txt | 4 + examples/bootfloppy/etc/fstab | 2 + examples/bootfloppy/etc/init.d/rcS | 3 + examples/bootfloppy/etc/inittab | 5 + examples/bootfloppy/etc/profile | 8 + examples/bootfloppy/mkdevs.sh | 62 + examples/bootfloppy/mkrootfs.sh | 105 + examples/bootfloppy/mksyslinux.sh | 48 + examples/bootfloppy/quickstart.txt | 15 + examples/bootfloppy/syslinux.cfg | 7 + examples/busybox.spec | 44 + examples/depmod.pl | 289 + examples/devfsd.conf | 133 + examples/dnsd.conf | 1 + examples/inetd.conf | 73 + examples/inittab | 90 + examples/mk2knr.pl | 84 + examples/udhcp/sample.bound | 31 + examples/udhcp/sample.deconfig | 4 + examples/udhcp/sample.nak | 4 + examples/udhcp/sample.renew | 31 + examples/udhcp/sample.script | 7 + examples/udhcp/simple.script | 40 + examples/udhcp/udhcpd.conf | 123 + examples/undeb | 53 + examples/unrpm | 48 + examples/zcip.script | 38 + findutils/Config.in | 159 + findutils/Kbuild | 10 + findutils/find.c | 582 + findutils/grep.c | 483 + findutils/xargs.c | 539 + include/applets.h | 333 + include/busybox.h | 47 + include/dump.h | 50 + include/grp_.h | 115 + include/inet_common.h | 30 + include/libbb.h | 689 + include/platform.h | 276 + include/pwd_.h | 107 + include/shadow_.h | 99 + include/unarchive.h | 114 + include/usage.h | 3495 ++++ include/xatonum.h | 157 + include/xregex.h | 17 + init/Config.in | 84 + init/Kbuild | 12 + init/halt.c | 58 + init/init.c | 1131 ++ init/init_shared.c | 63 + init/init_shared.h | 10 + init/mesg.c | 47 + libbb/Config.in | 29 + libbb/Kbuild | 117 + libbb/README | 11 + libbb/ask_confirmation.c | 36 + libbb/bb_askpass.c | 75 + libbb/bb_do_delay.c | 24 + libbb/bb_pwd.c | 130 + libbb/bb_strtonum.c | 155 + libbb/change_identity.c | 51 + libbb/chomp.c | 19 + libbb/compare_string_array.c | 37 + libbb/concat_path_file.c | 27 + libbb/concat_subpath_file.c | 23 + libbb/copy_file.c | 282 + libbb/copyfd.c | 86 + libbb/correct_password.c | 65 + libbb/crc32.c | 39 + libbb/create_icmp6_socket.c | 39 + libbb/create_icmp_socket.c | 37 + libbb/default_error_retval.c | 19 + libbb/device_open.c | 33 + libbb/dump.c | 803 + libbb/error_msg.c | 23 + libbb/error_msg_and_die.c | 28 + libbb/execable.c | 61 + libbb/fclose_nonstdin.c | 23 + libbb/fflush_stdout_and_exit.c | 24 + libbb/fgets_str.c | 53 + libbb/find_mount_point.c | 53 + libbb/find_pid_by_name.c | 55 + libbb/find_root_device.c | 33 + libbb/full_write.c | 38 + libbb/get_console.c | 77 + libbb/get_last_path_component.c | 32 + libbb/get_line_from_file.c | 68 + libbb/getopt32.c | 529 + libbb/herror_msg.c | 22 + libbb/herror_msg_and_die.c | 25 + libbb/human_readable.c | 89 + libbb/inet_common.c | 235 + libbb/info_msg.c | 23 + libbb/inode_hash.c | 88 + libbb/isdirectory.c | 39 + libbb/kernel_version.c | 37 + libbb/last_char_is.c | 24 + libbb/llist.c | 78 + libbb/login.c | 105 + libbb/loop.c | 149 + libbb/make_directory.c | 104 + libbb/makedev.c | 20 + libbb/md5.c | 450 + libbb/messages.c | 53 + libbb/mode_string.c | 128 + libbb/mtab.c | 52 + libbb/mtab_file.c | 17 + libbb/obscure.c | 170 + libbb/parse_mode.c | 164 + libbb/perror_msg.c | 23 + libbb/perror_msg_and_die.c | 26 + libbb/perror_nomsg.c | 16 + libbb/perror_nomsg_and_die.c | 17 + libbb/process_escape_sequence.c | 89 + libbb/procps.c | 255 + libbb/pw_encrypt.c | 29 + libbb/read.c | 134 + libbb/recursive_action.c | 126 + libbb/remove_file.c | 111 + libbb/restricted_shell.c | 57 + libbb/run_shell.c | 101 + libbb/safe_strncpy.c | 21 + libbb/safe_write.c | 26 + libbb/setup_environment.c | 83 + libbb/sha1.c | 178 + libbb/simplify_path.c | 50 + libbb/skip_whitespace.c | 18 + libbb/speed_table.c | 117 + libbb/trim.c | 31 + libbb/u_signal_names.c | 58 + libbb/uuencode.c | 63 + libbb/vdprintf.c | 25 + libbb/verror_msg.c | 46 + libbb/vfork_daemon_rexec.c | 67 + libbb/vherror_msg.c | 15 + libbb/vinfo_msg.c | 26 + libbb/vperror_msg.c | 15 + libbb/warn_ignoring_args.c | 17 + libbb/wfopen.c | 20 + libbb/wfopen_input.c | 31 + libbb/xatonum.c | 69 + libbb/xatonum_template.c | 185 + libbb/xconnect.c | 153 + libbb/xfuncs.c | 521 + libbb/xgetcwd.c | 44 + libbb/xgethostbyname.c | 19 + libbb/xgethostbyname2.c | 22 + libbb/xreadlink.c | 38 + libbb/xregcomp.c | 26 + libpwdgrp/Kbuild | 7 + libpwdgrp/pwd_grp.c | 969 ++ libpwdgrp/pwd_grp_internal.c | 62 + libpwdgrp/uidgid_get.c | 49 + loginutils/Config.in | 183 + loginutils/Kbuild | 17 + loginutils/addgroup.c | 107 + loginutils/adduser.c | 194 + loginutils/deluser.c | 83 + loginutils/getty.c | 862 + loginutils/login.c | 402 + loginutils/passwd.c | 339 + loginutils/su.c | 90 + loginutils/sulogin.c | 121 + loginutils/vlock.c | 118 + miscutils/Config.in | 374 + miscutils/Kbuild | 30 + miscutils/adjtimex.c | 117 + miscutils/bbconfig.c | 11 + miscutils/crond.c | 1016 ++ miscutils/crontab.c | 344 + miscutils/dc.c | 231 + miscutils/devfsd.c | 2032 +++ miscutils/eject.c | 60 + miscutils/hdparm.c | 2184 +++ miscutils/last.c | 91 + miscutils/less.c | 1144 ++ miscutils/makedevs.c | 230 + miscutils/mountpoint.c | 65 + miscutils/mt.c | 120 + miscutils/nmeter.c | 848 + miscutils/raidautorun.c | 26 + miscutils/readahead.c | 34 + miscutils/runlevel.c | 42 + miscutils/rx.c | 290 + miscutils/setsid.c | 44 + miscutils/strings.c | 88 + miscutils/taskset.c | 96 + miscutils/time.c | 467 + miscutils/watchdog.c | 65 + modutils/Config.in | 158 + modutils/Kbuild | 11 + modutils/insmod.c | 4316 +++++ modutils/lsmod.c | 188 + modutils/modprobe.c | 905 + modutils/rmmod.c | 96 + networking/Config.in | 720 + networking/Kbuild | 39 + networking/arping.c | 446 + networking/dnsd.c | 456 + networking/ether-wake.c | 284 + networking/fakeidentd.c | 387 + networking/ftpgetput.c | 340 + networking/hostname.c | 102 + networking/httpd.c | 1970 +++ networking/httpd_index_cgi_example | 55 + networking/ifconfig.c | 548 + networking/ifupdown.c | 1270 ++ networking/inetd.c | 1767 ++ networking/interface.c | 1152 ++ networking/ip.c | 48 + networking/ipaddr.c | 25 + networking/ipcalc.c | 194 + networking/iplink.c | 25 + networking/iproute.c | 25 + networking/iprule.c | 25 + networking/iptunnel.c | 25 + networking/libiproute/Kbuild | 66 + networking/libiproute/ip_common.h | 34 + networking/libiproute/ip_parse_common_args.c | 77 + networking/libiproute/ipaddress.c | 824 + networking/libiproute/iplink.c | 354 + networking/libiproute/iproute.c | 858 + networking/libiproute/iprule.c | 331 + networking/libiproute/iptunnel.c | 546 + networking/libiproute/libnetlink.c | 396 + networking/libiproute/libnetlink.h | 43 + networking/libiproute/linux/pkt_sched.h | 414 + networking/libiproute/ll_addr.c | 85 + networking/libiproute/ll_map.c | 186 + networking/libiproute/ll_map.h | 13 + networking/libiproute/ll_proto.c | 122 + networking/libiproute/ll_types.c | 118 + networking/libiproute/rt_names.c | 384 + networking/libiproute/rt_names.h | 29 + networking/libiproute/rtm_map.c | 111 + networking/libiproute/rtm_map.h | 11 + networking/libiproute/utils.c | 354 + networking/libiproute/utils.h | 103 + networking/nameif.c | 170 + networking/nc.c | 201 + networking/netstat.c | 613 + networking/nslookup.c | 149 + networking/ping.c | 440 + networking/ping6.c | 480 + networking/route.c | 703 + networking/telnet.c | 714 + networking/telnetd.c | 578 + networking/tftp.c | 561 + networking/traceroute.c | 1350 ++ networking/udhcp/Config.in | 67 + networking/udhcp/Kbuild | 18 + networking/udhcp/arpping.c | 114 + networking/udhcp/clientpacket.c | 224 + networking/udhcp/clientsocket.c | 59 + networking/udhcp/common.c | 75 + networking/udhcp/common.h | 108 + networking/udhcp/dhcpc.c | 509 + networking/udhcp/dhcpc.h | 50 + networking/udhcp/dhcpd.c | 226 + networking/udhcp/dhcpd.h | 190 + networking/udhcp/dhcprelay.c | 340 + networking/udhcp/dumpleases.c | 74 + networking/udhcp/files.c | 395 + networking/udhcp/leases.c | 145 + networking/udhcp/options.c | 174 + networking/udhcp/options.h | 37 + networking/udhcp/packet.c | 211 + networking/udhcp/pidfile.c | 66 + networking/udhcp/script.c | 213 + networking/udhcp/serverpacket.c | 261 + networking/udhcp/signalpipe.c | 77 + networking/udhcp/socket.c | 130 + networking/udhcp/static_leases.c | 99 + networking/vconfig.c | 164 + networking/wget.c | 832 + networking/zcip.c | 546 + procps/Config.in | 121 + procps/Kbuild | 16 + procps/free.c | 68 + procps/fuser.c | 366 + procps/kill.c | 155 + procps/pidof.c | 90 + procps/ps.c | 388 + procps/ps.posix | 175 + procps/renice.c | 126 + procps/sysctl.c | 326 + procps/top.c | 566 + procps/uptime.c | 59 + runit/Config.in | 66 + runit/Kbuild | 12 + runit/chpst.c | 373 + runit/runit_lib.c | 982 ++ runit/runit_lib.h | 403 + runit/runsv.c | 613 + runit/runsvdir.c | 306 + runit/sv.c | 360 + runit/svlogd.c | 878 + scripts/Kbuild | 7 + scripts/Kbuild.include | 148 + scripts/Makefile.build | 338 + scripts/Makefile.clean | 102 + scripts/Makefile.host | 156 + scripts/Makefile.lib | 165 + scripts/basic/Makefile | 18 + scripts/basic/docproc.c | 398 + scripts/basic/fixdep.c | 399 + scripts/basic/split-include.c | 226 + scripts/bloat-o-meter | 65 + scripts/checkhelp.awk | 40 + scripts/defconfig | 665 + scripts/individual | 129 + scripts/kconfig/Makefile | 254 + scripts/kconfig/POTFILES.in | 5 + scripts/kconfig/conf.c | 612 + scripts/kconfig/confdata.c | 562 + scripts/kconfig/expr.c | 1099 ++ scripts/kconfig/expr.h | 194 + scripts/kconfig/gconf.c | 1645 ++ scripts/kconfig/gconf.glade | 648 + scripts/kconfig/images.c | 326 + scripts/kconfig/kconfig_load.c | 35 + scripts/kconfig/kxgettext.c | 227 + scripts/kconfig/lex.zconf.c_shipped | 2317 +++ scripts/kconfig/lkc.h | 147 + scripts/kconfig/lkc_proto.h | 41 + scripts/kconfig/lxdialog/BIG.FAT.WARNING | 4 + scripts/kconfig/lxdialog/Makefile | 21 + scripts/kconfig/lxdialog/check-lxdialog.sh | 84 + scripts/kconfig/lxdialog/checklist.c | 333 + scripts/kconfig/lxdialog/colors.h | 154 + scripts/kconfig/lxdialog/dialog.h | 177 + scripts/kconfig/lxdialog/inputbox.c | 224 + scripts/kconfig/lxdialog/lxdialog.c | 204 + scripts/kconfig/lxdialog/menubox.c | 426 + scripts/kconfig/lxdialog/msgbox.c | 71 + scripts/kconfig/lxdialog/textbox.c | 533 + scripts/kconfig/lxdialog/util.c | 362 + scripts/kconfig/lxdialog/yesno.c | 102 + scripts/kconfig/mconf.c | 1098 ++ scripts/kconfig/menu.c | 397 + scripts/kconfig/qconf.cc | 1426 ++ scripts/kconfig/qconf.h | 263 + scripts/kconfig/symbol.c | 882 + scripts/kconfig/util.c | 109 + scripts/kconfig/zconf.gperf | 43 + scripts/kconfig/zconf.hash.c_shipped | 231 + scripts/kconfig/zconf.l | 350 + scripts/kconfig/zconf.tab.c_shipped | 2173 +++ scripts/kconfig/zconf.y | 681 + scripts/mkconfigs | 51 + scripts/mkmakefile | 36 + scripts/objsizes | 7 + scripts/showasm | 21 + scripts/trylink | 18 + shell/Config.in | 295 + shell/Kbuild | 12 + shell/ash.c | 13701 ++++++++++++++++ shell/bbsh.c | 222 + shell/cmdedit.c | 1924 +++ shell/cmdedit.h | 20 + shell/hush.c | 2875 ++++ shell/lash.c | 1573 ++ shell/msh.c | 5250 ++++++ sysklogd/Config.in | 111 + sysklogd/Kbuild | 11 + sysklogd/klogd.c | 123 + sysklogd/logger.c | 179 + sysklogd/logread.c | 171 + sysklogd/syslogd.c | 643 + testsuite/README | 34 + testsuite/TODO | 26 + testsuite/all_sourcecode.tests | 78 + ...sename-does-not-remove-identical-extension | 1 + testsuite/basename/basename-works | 2 + .../bunzip2/bunzip2-reads-from-standard-input | 2 + .../bunzip2/bunzip2-removes-compressed-file | 3 + .../bzcat-does-not-remove-compressed-file | 3 + testsuite/busybox.tests | 46 + testsuite/cat/cat-prints-a-file | 3 + .../cat/cat-prints-a-file-and-standard-input | 7 + testsuite/cmp/cmp-detects-difference | 9 + testsuite/cp/cp-a-files-to-dir | 14 + testsuite/cp/cp-a-preserves-links | 5 + testsuite/cp/cp-copies-empty-file | 3 + testsuite/cp/cp-copies-large-file | 3 + testsuite/cp/cp-copies-small-file | 3 + testsuite/cp/cp-d-files-to-dir | 11 + testsuite/cp/cp-dir-create-dir | 4 + testsuite/cp/cp-dir-existing-dir | 5 + testsuite/cp/cp-does-not-copy-unreadable-file | 6 + testsuite/cp/cp-files-to-dir | 11 + testsuite/cp/cp-follows-links | 4 + testsuite/cp/cp-preserves-hard-links | 6 + testsuite/cp/cp-preserves-links | 5 + testsuite/cp/cp-preserves-source-file | 3 + testsuite/cut/cut-cuts-a-character | 1 + testsuite/cut/cut-cuts-a-closed-range | 1 + testsuite/cut/cut-cuts-a-field | 1 + testsuite/cut/cut-cuts-an-open-range | 1 + testsuite/cut/cut-cuts-an-unclosed-range | 1 + testsuite/date/date-R-works | 2 + testsuite/date/date-format-works | 1 + testsuite/date/date-u-works | 2 + testsuite/date/date-works | 2 + testsuite/dd/dd-accepts-if | 2 + testsuite/dd/dd-accepts-of | 2 + ...ies-from-standard-input-to-standard-output | 1 + .../dd/dd-prints-count-to-standard-error | 2 + .../dirname/dirname-handles-absolute-path | 1 + testsuite/dirname/dirname-handles-empty-path | 1 + .../dirname/dirname-handles-multiple-slashes | 1 + .../dirname/dirname-handles-relative-path | 1 + testsuite/dirname/dirname-handles-root | 1 + .../dirname/dirname-handles-single-component | 1 + testsuite/dirname/dirname-works | 2 + testsuite/du/du-h-works | 4 + testsuite/du/du-k-works | 4 + testsuite/du/du-l-works | 4 + testsuite/du/du-m-works | 4 + testsuite/du/du-s-works | 4 + testsuite/du/du-works | 4 + testsuite/echo/echo-does-not-print-newline | 1 + testsuite/echo/echo-prints-argument | 1 + testsuite/echo/echo-prints-arguments | 1 + testsuite/echo/echo-prints-newline | 1 + testsuite/expr/expr-works | 59 + testsuite/false/false-is-silent | 1 + testsuite/false/false-returns-failure | 1 + testsuite/find/find-supports-minus-xdev | 1 + testsuite/grep.tests | 84 + .../gunzip/gunzip-reads-from-standard-input | 2 + testsuite/gzip/gzip-accepts-multiple-files | 3 + testsuite/gzip/gzip-accepts-single-minus | 1 + testsuite/gzip/gzip-removes-original-file | 3 + testsuite/head/head-n-works | 4 + testsuite/head/head-works | 4 + testsuite/hostid/hostid-works | 2 + testsuite/hostname/hostname-d-works | 2 + testsuite/hostname/hostname-i-works | 2 + testsuite/hostname/hostname-s-works | 1 + testsuite/hostname/hostname-works | 1 + testsuite/id/id-g-works | 1 + testsuite/id/id-u-works | 1 + testsuite/id/id-un-works | 1 + testsuite/id/id-ur-works | 1 + testsuite/ln/ln-creates-hard-links | 4 + testsuite/ln/ln-creates-soft-links | 4 + testsuite/ln/ln-force-creates-hard-links | 5 + testsuite/ln/ln-force-creates-soft-links | 5 + testsuite/ln/ln-preserves-hard-links | 8 + testsuite/ln/ln-preserves-soft-links | 9 + testsuite/ls/ls-1-works | 4 + testsuite/ls/ls-h-works | 4 + testsuite/ls/ls-l-works | 4 + testsuite/ls/ls-s-works | 4 + .../md5sum/md5sum-verifies-non-binary-file | 3 + testsuite/mkdir/mkdir-makes-a-directory | 2 + .../mkdir/mkdir-makes-parent-directories | 2 + testsuite/mount.testroot | 183 + ...msh-supports-underscores-in-variable-names | 1 + testsuite/mv/mv-files-to-dir | 16 + testsuite/mv/mv-follows-links | 4 + testsuite/mv/mv-moves-empty-file | 4 + testsuite/mv/mv-moves-file | 3 + testsuite/mv/mv-moves-hardlinks | 4 + testsuite/mv/mv-moves-large-file | 4 + testsuite/mv/mv-moves-small-file | 4 + testsuite/mv/mv-moves-symlinks | 6 + testsuite/mv/mv-moves-unreadable-files | 5 + testsuite/mv/mv-preserves-hard-links | 6 + testsuite/mv/mv-preserves-links | 5 + testsuite/mv/mv-refuses-mv-dir-to-subdir | 23 + testsuite/mv/mv-removes-source-file | 4 + testsuite/pidof.tests | 29 + testsuite/pwd/pwd-prints-working-directory | 1 + testsuite/readlink.tests | 32 + testsuite/rm/rm-removes-file | 3 + .../rmdir/rmdir-removes-parent-directories | 3 + testsuite/runtest | 140 + testsuite/sed.tests | 187 + testsuite/seq.tests | 36 + testsuite/sort.tests | 83 + testsuite/strings/strings-works-like-GNU | 9 + testsuite/tail/tail-n-works | 4 + testsuite/tail/tail-works | 4 + testsuite/tar/tar-archives-multiple-files | 6 + .../tar/tar-complains-about-missing-file | 3 + testsuite/tar/tar-demands-at-least-one-ctx | 1 + testsuite/tar/tar-demands-at-most-one-ctx | 1 + testsuite/tar/tar-extracts-all-subdirs | 12 + testsuite/tar/tar-extracts-file | 5 + .../tar/tar-extracts-from-standard-input | 5 + testsuite/tar/tar-extracts-multiple-files | 6 + testsuite/tar/tar-extracts-to-standard-output | 3 + testsuite/tar/tar-handles-cz-options | 5 + ...s-empty-include-and-non-empty-exclude-list | 6 + .../tar/tar-handles-exclude-and-extract-lists | 8 + testsuite/tar/tar-handles-multiple-X-options | 10 + testsuite/tar/tar-handles-nested-exclude | 9 + testsuite/tee/tee-appends-input | 5 + testsuite/tee/tee-tees-input | 3 + testsuite/testing.sh | 154 + testsuite/touch/touch-creates-file | 2 + testsuite/touch/touch-does-not-create-file | 2 + ...ouch-touches-files-after-non-existent-file | 3 + testsuite/tr/tr-d-works | 4 + testsuite/tr/tr-non-gnu | 1 + testsuite/tr/tr-works | 24 + testsuite/true/true-is-silent | 1 + testsuite/true/true-returns-success | 1 + testsuite/umlwrapper.sh | 20 + testsuite/uniq.tests | 70 + testsuite/unzip.tests | 38 + testsuite/uptime/uptime-works | 2 + testsuite/uuencode.tests | 28 + testsuite/wc/wc-counts-all | 1 + testsuite/wc/wc-counts-characters | 1 + testsuite/wc/wc-counts-lines | 1 + testsuite/wc/wc-counts-words | 1 + testsuite/wc/wc-prints-longest-line-length | 1 + testsuite/wget/wget--O-overrides--P | 3 + testsuite/wget/wget-handles-empty-path | 1 + testsuite/wget/wget-retrieves-google-index | 2 + testsuite/wget/wget-supports--P | 3 + testsuite/which/which-uses-default-path | 4 + testsuite/xargs/xargs-works | 4 + util-linux/Config.in | 526 + util-linux/Kbuild | 32 + util-linux/dmesg.c | 53 + util-linux/fbset.c | 407 + util-linux/fdformat.c | 143 + util-linux/fdisk.c | 3043 ++++ util-linux/fdisk_aix.c | 76 + util-linux/fdisk_osf.c | 1046 ++ util-linux/fdisk_sgi.c | 885 + util-linux/fdisk_sun.c | 729 + util-linux/freeramdisk.c | 34 + util-linux/fsck_minix.c | 1417 ++ util-linux/getopt.c | 365 + util-linux/hexdump.c | 101 + util-linux/hwclock.c | 213 + util-linux/ipcrm.c | 217 + util-linux/ipcs.c | 630 + util-linux/losetup.c | 64 + util-linux/mdev.c | 268 + util-linux/mkfs_minix.c | 762 + util-linux/mkswap.c | 47 + util-linux/more.c | 177 + util-linux/mount.c | 1712 ++ util-linux/pivot_root.c | 24 + util-linux/rdate.c | 86 + util-linux/readprofile.c | 236 + util-linux/setarch.c | 52 + util-linux/swaponoff.c | 77 + util-linux/switch_root.c | 122 + util-linux/umount.c | 151 + 909 files changed, 222869 insertions(+) create mode 100644 .indent.pro create mode 100644 AUTHORS create mode 100644 Config.in create mode 100644 INSTALL create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 Makefile.custom create mode 100644 Makefile.flags create mode 100644 Makefile.help create mode 100644 README create mode 100644 TODO create mode 100644 applets/Kbuild create mode 100644 applets/applets.c create mode 100644 applets/busybox.c create mode 100755 applets/busybox.mkll create mode 100644 applets/individual.c create mode 100755 applets/install.sh create mode 100644 applets/usage.c create mode 100755 applets/usage_compressed create mode 100644 arch/i386/Makefile create mode 100644 archival/Config.in create mode 100644 archival/Kbuild create mode 100644 archival/ar.c create mode 100644 archival/bunzip2.c create mode 100644 archival/cpio.c create mode 100644 archival/dpkg.c create mode 100644 archival/dpkg_deb.c create mode 100644 archival/gunzip.c create mode 100644 archival/gzip.c create mode 100644 archival/libunarchive/Kbuild create mode 100644 archival/libunarchive/archive_xread_all_eof.c create mode 100644 archival/libunarchive/check_header_gzip.c create mode 100644 archival/libunarchive/data_align.c create mode 100644 archival/libunarchive/data_extract_all.c create mode 100644 archival/libunarchive/data_extract_to_buffer.c create mode 100644 archival/libunarchive/data_extract_to_stdout.c create mode 100644 archival/libunarchive/data_skip.c create mode 100644 archival/libunarchive/decompress_bunzip2.c create mode 100644 archival/libunarchive/decompress_uncompress.c create mode 100644 archival/libunarchive/decompress_unlzma.c create mode 100644 archival/libunarchive/decompress_unzip.c create mode 100644 archival/libunarchive/filter_accept_all.c create mode 100644 archival/libunarchive/filter_accept_list.c create mode 100644 archival/libunarchive/filter_accept_list_reassign.c create mode 100644 archival/libunarchive/filter_accept_reject_list.c create mode 100644 archival/libunarchive/find_list_entry.c create mode 100644 archival/libunarchive/get_header_ar.c create mode 100644 archival/libunarchive/get_header_cpio.c create mode 100644 archival/libunarchive/get_header_tar.c create mode 100644 archival/libunarchive/get_header_tar_bz2.c create mode 100644 archival/libunarchive/get_header_tar_gz.c create mode 100644 archival/libunarchive/get_header_tar_lzma.c create mode 100644 archival/libunarchive/header_list.c create mode 100644 archival/libunarchive/header_skip.c create mode 100644 archival/libunarchive/header_verbose_list.c create mode 100644 archival/libunarchive/init_handle.c create mode 100644 archival/libunarchive/open_transformer.c create mode 100644 archival/libunarchive/seek_by_jump.c create mode 100644 archival/libunarchive/seek_by_read.c create mode 100644 archival/libunarchive/unpack_ar_archive.c create mode 100644 archival/rpm.c create mode 100644 archival/rpm2cpio.c create mode 100644 archival/tar.c create mode 100644 archival/uncompress.c create mode 100644 archival/unlzma.c create mode 100644 archival/unzip.c create mode 100644 console-tools/Config.in create mode 100644 console-tools/Kbuild create mode 100644 console-tools/chvt.c create mode 100644 console-tools/clear.c create mode 100644 console-tools/deallocvt.c create mode 100644 console-tools/dumpkmap.c create mode 100644 console-tools/loadfont.c create mode 100644 console-tools/loadkmap.c create mode 100644 console-tools/openvt.c create mode 100644 console-tools/reset.c create mode 100644 console-tools/resize.c create mode 100644 console-tools/setconsole.c create mode 100644 console-tools/setkeycodes.c create mode 100644 console-tools/setlogcons.c create mode 100644 coreutils/Config.in create mode 100644 coreutils/Kbuild create mode 100644 coreutils/basename.c create mode 100644 coreutils/cal.c create mode 100644 coreutils/cat.c create mode 100644 coreutils/catv.c create mode 100644 coreutils/chgrp.c create mode 100644 coreutils/chmod.c create mode 100644 coreutils/chown.c create mode 100644 coreutils/chroot.c create mode 100644 coreutils/cksum.c create mode 100644 coreutils/cmp.c create mode 100644 coreutils/comm.c create mode 100644 coreutils/cp.c create mode 100644 coreutils/cut.c create mode 100644 coreutils/date.c create mode 100644 coreutils/dd.c create mode 100644 coreutils/df.c create mode 100644 coreutils/diff.c create mode 100644 coreutils/dirname.c create mode 100644 coreutils/dos2unix.c create mode 100644 coreutils/du.c create mode 100644 coreutils/echo.c create mode 100644 coreutils/env.c create mode 100644 coreutils/expr.c create mode 100644 coreutils/false.c create mode 100644 coreutils/fold.c create mode 100644 coreutils/head.c create mode 100644 coreutils/hostid.c create mode 100644 coreutils/id.c create mode 100644 coreutils/install.c create mode 100644 coreutils/length.c create mode 100644 coreutils/libcoreutils/Kbuild create mode 100644 coreutils/libcoreutils/coreutils.h create mode 100644 coreutils/libcoreutils/cp_mv_stat.c create mode 100644 coreutils/libcoreutils/getopt_mk_fifo_nod.c create mode 100644 coreutils/ln.c create mode 100644 coreutils/logname.c create mode 100644 coreutils/ls.c create mode 100644 coreutils/md5_sha1_sum.c create mode 100644 coreutils/mkdir.c create mode 100644 coreutils/mkfifo.c create mode 100644 coreutils/mknod.c create mode 100644 coreutils/mv.c create mode 100644 coreutils/nice.c create mode 100644 coreutils/nohup.c create mode 100644 coreutils/od.c create mode 100644 coreutils/od_bloaty.c create mode 100644 coreutils/printenv.c create mode 100644 coreutils/printf.c create mode 100644 coreutils/pwd.c create mode 100644 coreutils/realpath.c create mode 100644 coreutils/rm.c create mode 100644 coreutils/rmdir.c create mode 100644 coreutils/seq.c create mode 100644 coreutils/sleep.c create mode 100644 coreutils/sort.c create mode 100644 coreutils/stat.c create mode 100644 coreutils/stty.c create mode 100644 coreutils/sum.c create mode 100644 coreutils/sync.c create mode 100644 coreutils/tail.c create mode 100644 coreutils/tee.c create mode 100644 coreutils/test.c create mode 100644 coreutils/touch.c create mode 100644 coreutils/tr.c create mode 100644 coreutils/true.c create mode 100644 coreutils/tty.c create mode 100644 coreutils/uname.c create mode 100644 coreutils/uniq.c create mode 100644 coreutils/usleep.c create mode 100644 coreutils/uudecode.c create mode 100644 coreutils/uuencode.c create mode 100644 coreutils/watch.c create mode 100644 coreutils/wc.c create mode 100644 coreutils/who.c create mode 100644 coreutils/whoami.c create mode 100644 coreutils/yes.c create mode 100644 debianutils/Config.in create mode 100644 debianutils/Kbuild create mode 100644 debianutils/mktemp.c create mode 100644 debianutils/pipe_progress.c create mode 100644 debianutils/readlink.c create mode 100644 debianutils/run_parts.c create mode 100644 debianutils/start_stop_daemon.c create mode 100644 debianutils/which.c create mode 100755 docs/autodocifier.pl create mode 100644 docs/busybox.net/FAQ.html create mode 100644 docs/busybox.net/about.html create mode 100644 docs/busybox.net/busybox-growth.ps create mode 100644 docs/busybox.net/copyright.txt create mode 100644 docs/busybox.net/developer.html create mode 100644 docs/busybox.net/download.html create mode 100644 docs/busybox.net/footer.html create mode 100644 docs/busybox.net/header.html create mode 100644 docs/busybox.net/images/back.png create mode 100644 docs/busybox.net/images/busybox.jpeg create mode 100644 docs/busybox.net/images/busybox.png create mode 100644 docs/busybox.net/images/busybox1.png create mode 100644 docs/busybox.net/images/busybox2.jpg create mode 100644 docs/busybox.net/images/busybox3.jpg create mode 100644 docs/busybox.net/images/dir.png create mode 100644 docs/busybox.net/images/donate.png create mode 100644 docs/busybox.net/images/fm.mini.png create mode 100644 docs/busybox.net/images/gfx_by_gimp.png create mode 100644 docs/busybox.net/images/ltbutton2.png create mode 100644 docs/busybox.net/images/osuosl.png create mode 100644 docs/busybox.net/images/sdsmall.png create mode 100644 docs/busybox.net/images/text.png create mode 100644 docs/busybox.net/images/valid-html401.png create mode 100644 docs/busybox.net/images/vh40.gif create mode 100644 docs/busybox.net/images/written.in.vi.png create mode 100644 docs/busybox.net/index.html create mode 100644 docs/busybox.net/license.html create mode 100644 docs/busybox.net/links.html create mode 100644 docs/busybox.net/lists.html create mode 100644 docs/busybox.net/news.html create mode 100644 docs/busybox.net/oldnews.html create mode 100644 docs/busybox.net/products.html create mode 100644 docs/busybox.net/screenshot.html create mode 100644 docs/busybox.net/shame.html create mode 100644 docs/busybox.net/sponsors.html create mode 100644 docs/busybox.net/subversion.html create mode 100644 docs/busybox.net/tinyutils.html create mode 100644 docs/busybox_footer.pod create mode 100644 docs/busybox_header.pod create mode 100644 docs/contributing.txt create mode 100644 docs/ipv4_ipv6.txt create mode 100644 docs/new-applet-HOWTO.txt create mode 100644 docs/sigint.htm create mode 100644 docs/style-guide.txt create mode 100644 docs/tar_pax.txt create mode 100644 e2fsprogs/Config.in create mode 100644 e2fsprogs/Kbuild create mode 100644 e2fsprogs/README create mode 100644 e2fsprogs/blkid/Kbuild create mode 100644 e2fsprogs/blkid/blkid.h create mode 100644 e2fsprogs/blkid/blkidP.h create mode 100644 e2fsprogs/blkid/blkid_getsize.c create mode 100644 e2fsprogs/blkid/cache.c create mode 100644 e2fsprogs/blkid/dev.c create mode 100644 e2fsprogs/blkid/devname.c create mode 100644 e2fsprogs/blkid/devno.c create mode 100644 e2fsprogs/blkid/list.c create mode 100644 e2fsprogs/blkid/list.h create mode 100644 e2fsprogs/blkid/probe.c create mode 100644 e2fsprogs/blkid/probe.h create mode 100644 e2fsprogs/blkid/read.c create mode 100644 e2fsprogs/blkid/resolve.c create mode 100644 e2fsprogs/blkid/save.c create mode 100644 e2fsprogs/blkid/tag.c create mode 100644 e2fsprogs/chattr.c create mode 100644 e2fsprogs/e2fsbb.h create mode 100644 e2fsprogs/e2fsck.c create mode 100644 e2fsprogs/e2fsck.h create mode 100644 e2fsprogs/e2p/Kbuild create mode 100644 e2fsprogs/e2p/e2p.h create mode 100644 e2fsprogs/e2p/feature.c create mode 100644 e2fsprogs/e2p/fgetsetflags.c create mode 100644 e2fsprogs/e2p/fgetsetversion.c create mode 100644 e2fsprogs/e2p/hashstr.c create mode 100644 e2fsprogs/e2p/iod.c create mode 100644 e2fsprogs/e2p/ls.c create mode 100644 e2fsprogs/e2p/mntopts.c create mode 100644 e2fsprogs/e2p/ostype.c create mode 100644 e2fsprogs/e2p/parse_num.c create mode 100644 e2fsprogs/e2p/pe.c create mode 100644 e2fsprogs/e2p/pf.c create mode 100644 e2fsprogs/e2p/ps.c create mode 100644 e2fsprogs/e2p/uuid.c create mode 100644 e2fsprogs/ext2fs/Kbuild create mode 100644 e2fsprogs/ext2fs/alloc.c create mode 100644 e2fsprogs/ext2fs/alloc_sb.c create mode 100644 e2fsprogs/ext2fs/alloc_stats.c create mode 100644 e2fsprogs/ext2fs/alloc_tables.c create mode 100644 e2fsprogs/ext2fs/badblocks.c create mode 100644 e2fsprogs/ext2fs/bb_compat.c create mode 100644 e2fsprogs/ext2fs/bb_inode.c create mode 100644 e2fsprogs/ext2fs/bitmaps.c create mode 100644 e2fsprogs/ext2fs/bitops.c create mode 100644 e2fsprogs/ext2fs/bitops.h create mode 100644 e2fsprogs/ext2fs/block.c create mode 100644 e2fsprogs/ext2fs/bmap.c create mode 100644 e2fsprogs/ext2fs/bmove.c create mode 100644 e2fsprogs/ext2fs/brel.h create mode 100644 e2fsprogs/ext2fs/brel_ma.c create mode 100644 e2fsprogs/ext2fs/check_desc.c create mode 100644 e2fsprogs/ext2fs/closefs.c create mode 100644 e2fsprogs/ext2fs/cmp_bitmaps.c create mode 100644 e2fsprogs/ext2fs/dblist.c create mode 100644 e2fsprogs/ext2fs/dblist_dir.c create mode 100644 e2fsprogs/ext2fs/dir_iterate.c create mode 100644 e2fsprogs/ext2fs/dirblock.c create mode 100644 e2fsprogs/ext2fs/dirhash.c create mode 100644 e2fsprogs/ext2fs/dupfs.c create mode 100644 e2fsprogs/ext2fs/e2image.h create mode 100644 e2fsprogs/ext2fs/expanddir.c create mode 100644 e2fsprogs/ext2fs/ext2_err.h create mode 100644 e2fsprogs/ext2fs/ext2_ext_attr.h create mode 100644 e2fsprogs/ext2fs/ext2_fs.h create mode 100644 e2fsprogs/ext2fs/ext2_io.h create mode 100644 e2fsprogs/ext2fs/ext2_types.h create mode 100644 e2fsprogs/ext2fs/ext2fs.h create mode 100644 e2fsprogs/ext2fs/ext2fsP.h create mode 100644 e2fsprogs/ext2fs/ext2fs_inline.c create mode 100644 e2fsprogs/ext2fs/ext_attr.c create mode 100644 e2fsprogs/ext2fs/fileio.c create mode 100644 e2fsprogs/ext2fs/finddev.c create mode 100644 e2fsprogs/ext2fs/flushb.c create mode 100644 e2fsprogs/ext2fs/freefs.c create mode 100644 e2fsprogs/ext2fs/gen_bitmap.c create mode 100644 e2fsprogs/ext2fs/get_pathname.c create mode 100644 e2fsprogs/ext2fs/getsectsize.c create mode 100644 e2fsprogs/ext2fs/getsize.c create mode 100644 e2fsprogs/ext2fs/icount.c create mode 100644 e2fsprogs/ext2fs/imager.c create mode 100644 e2fsprogs/ext2fs/ind_block.c create mode 100644 e2fsprogs/ext2fs/initialize.c create mode 100644 e2fsprogs/ext2fs/inline.c create mode 100644 e2fsprogs/ext2fs/inode.c create mode 100644 e2fsprogs/ext2fs/inode_io.c create mode 100644 e2fsprogs/ext2fs/io_manager.c create mode 100644 e2fsprogs/ext2fs/irel.h create mode 100644 e2fsprogs/ext2fs/irel_ma.c create mode 100644 e2fsprogs/ext2fs/ismounted.c create mode 100644 e2fsprogs/ext2fs/jfs_dat.h create mode 100644 e2fsprogs/ext2fs/kernel-jbd.h create mode 100644 e2fsprogs/ext2fs/kernel-list.h create mode 100644 e2fsprogs/ext2fs/link.c create mode 100644 e2fsprogs/ext2fs/lookup.c create mode 100644 e2fsprogs/ext2fs/mkdir.c create mode 100644 e2fsprogs/ext2fs/mkjournal.c create mode 100644 e2fsprogs/ext2fs/namei.c create mode 100644 e2fsprogs/ext2fs/newdir.c create mode 100644 e2fsprogs/ext2fs/openfs.c create mode 100644 e2fsprogs/ext2fs/read_bb.c create mode 100644 e2fsprogs/ext2fs/read_bb_file.c create mode 100644 e2fsprogs/ext2fs/res_gdt.c create mode 100644 e2fsprogs/ext2fs/rs_bitmap.c create mode 100644 e2fsprogs/ext2fs/rw_bitmaps.c create mode 100644 e2fsprogs/ext2fs/sparse.c create mode 100644 e2fsprogs/ext2fs/swapfs.c create mode 100644 e2fsprogs/ext2fs/test_io.c create mode 100644 e2fsprogs/ext2fs/unix_io.c create mode 100644 e2fsprogs/ext2fs/unlink.c create mode 100644 e2fsprogs/ext2fs/valid_blk.c create mode 100644 e2fsprogs/ext2fs/version.c create mode 100644 e2fsprogs/ext2fs/write_bb_file.c create mode 100644 e2fsprogs/fsck.c create mode 100644 e2fsprogs/fsck.h create mode 100644 e2fsprogs/lsattr.c create mode 100644 e2fsprogs/mke2fs.c create mode 100644 e2fsprogs/tune2fs.c create mode 100644 e2fsprogs/util.c create mode 100644 e2fsprogs/util.h create mode 100644 e2fsprogs/uuid/Kbuild create mode 100644 e2fsprogs/uuid/compare.c create mode 100644 e2fsprogs/uuid/gen_uuid.c create mode 100644 e2fsprogs/uuid/pack.c create mode 100644 e2fsprogs/uuid/parse.c create mode 100644 e2fsprogs/uuid/unpack.c create mode 100644 e2fsprogs/uuid/unparse.c create mode 100644 e2fsprogs/uuid/uuid.h create mode 100644 e2fsprogs/uuid/uuidP.h create mode 100644 e2fsprogs/uuid/uuid_time.c create mode 100644 editors/Config.in create mode 100644 editors/Kbuild create mode 100644 editors/awk.c create mode 100644 editors/ed.c create mode 100644 editors/patch.c create mode 100644 editors/sed.c create mode 100644 editors/vi.c create mode 100644 examples/bootfloppy/bootfloppy.txt create mode 100644 examples/bootfloppy/display.txt create mode 100644 examples/bootfloppy/etc/fstab create mode 100755 examples/bootfloppy/etc/init.d/rcS create mode 100644 examples/bootfloppy/etc/inittab create mode 100644 examples/bootfloppy/etc/profile create mode 100755 examples/bootfloppy/mkdevs.sh create mode 100755 examples/bootfloppy/mkrootfs.sh create mode 100755 examples/bootfloppy/mksyslinux.sh create mode 100644 examples/bootfloppy/quickstart.txt create mode 100644 examples/bootfloppy/syslinux.cfg create mode 100644 examples/busybox.spec create mode 100755 examples/depmod.pl create mode 100644 examples/devfsd.conf create mode 100644 examples/dnsd.conf create mode 100644 examples/inetd.conf create mode 100644 examples/inittab create mode 100755 examples/mk2knr.pl create mode 100755 examples/udhcp/sample.bound create mode 100755 examples/udhcp/sample.deconfig create mode 100755 examples/udhcp/sample.nak create mode 100755 examples/udhcp/sample.renew create mode 100644 examples/udhcp/sample.script create mode 100644 examples/udhcp/simple.script create mode 100644 examples/udhcp/udhcpd.conf create mode 100644 examples/undeb create mode 100644 examples/unrpm create mode 100644 examples/zcip.script create mode 100644 findutils/Config.in create mode 100644 findutils/Kbuild create mode 100644 findutils/find.c create mode 100644 findutils/grep.c create mode 100644 findutils/xargs.c create mode 100644 include/applets.h create mode 100644 include/busybox.h create mode 100644 include/dump.h create mode 100644 include/grp_.h create mode 100644 include/inet_common.h create mode 100644 include/libbb.h create mode 100644 include/platform.h create mode 100644 include/pwd_.h create mode 100644 include/shadow_.h create mode 100644 include/unarchive.h create mode 100644 include/usage.h create mode 100644 include/xatonum.h create mode 100644 include/xregex.h create mode 100644 init/Config.in create mode 100644 init/Kbuild create mode 100644 init/halt.c create mode 100644 init/init.c create mode 100644 init/init_shared.c create mode 100644 init/init_shared.h create mode 100644 init/mesg.c create mode 100644 libbb/Config.in create mode 100644 libbb/Kbuild create mode 100644 libbb/README create mode 100644 libbb/ask_confirmation.c create mode 100644 libbb/bb_askpass.c create mode 100644 libbb/bb_do_delay.c create mode 100644 libbb/bb_pwd.c create mode 100644 libbb/bb_strtonum.c create mode 100644 libbb/change_identity.c create mode 100644 libbb/chomp.c create mode 100644 libbb/compare_string_array.c create mode 100644 libbb/concat_path_file.c create mode 100644 libbb/concat_subpath_file.c create mode 100644 libbb/copy_file.c create mode 100644 libbb/copyfd.c create mode 100644 libbb/correct_password.c create mode 100644 libbb/crc32.c create mode 100644 libbb/create_icmp6_socket.c create mode 100644 libbb/create_icmp_socket.c create mode 100644 libbb/default_error_retval.c create mode 100644 libbb/device_open.c create mode 100644 libbb/dump.c create mode 100644 libbb/error_msg.c create mode 100644 libbb/error_msg_and_die.c create mode 100644 libbb/execable.c create mode 100644 libbb/fclose_nonstdin.c create mode 100644 libbb/fflush_stdout_and_exit.c create mode 100644 libbb/fgets_str.c create mode 100644 libbb/find_mount_point.c create mode 100644 libbb/find_pid_by_name.c create mode 100644 libbb/find_root_device.c create mode 100644 libbb/full_write.c create mode 100644 libbb/get_console.c create mode 100644 libbb/get_last_path_component.c create mode 100644 libbb/get_line_from_file.c create mode 100644 libbb/getopt32.c create mode 100644 libbb/herror_msg.c create mode 100644 libbb/herror_msg_and_die.c create mode 100644 libbb/human_readable.c create mode 100644 libbb/inet_common.c create mode 100644 libbb/info_msg.c create mode 100644 libbb/inode_hash.c create mode 100644 libbb/isdirectory.c create mode 100644 libbb/kernel_version.c create mode 100644 libbb/last_char_is.c create mode 100644 libbb/llist.c create mode 100644 libbb/login.c create mode 100644 libbb/loop.c create mode 100644 libbb/make_directory.c create mode 100644 libbb/makedev.c create mode 100644 libbb/md5.c create mode 100644 libbb/messages.c create mode 100644 libbb/mode_string.c create mode 100644 libbb/mtab.c create mode 100644 libbb/mtab_file.c create mode 100644 libbb/obscure.c create mode 100644 libbb/parse_mode.c create mode 100644 libbb/perror_msg.c create mode 100644 libbb/perror_msg_and_die.c create mode 100644 libbb/perror_nomsg.c create mode 100644 libbb/perror_nomsg_and_die.c create mode 100644 libbb/process_escape_sequence.c create mode 100644 libbb/procps.c create mode 100644 libbb/pw_encrypt.c create mode 100644 libbb/read.c create mode 100644 libbb/recursive_action.c create mode 100644 libbb/remove_file.c create mode 100644 libbb/restricted_shell.c create mode 100644 libbb/run_shell.c create mode 100644 libbb/safe_strncpy.c create mode 100644 libbb/safe_write.c create mode 100644 libbb/setup_environment.c create mode 100644 libbb/sha1.c create mode 100644 libbb/simplify_path.c create mode 100644 libbb/skip_whitespace.c create mode 100644 libbb/speed_table.c create mode 100644 libbb/trim.c create mode 100644 libbb/u_signal_names.c create mode 100644 libbb/uuencode.c create mode 100644 libbb/vdprintf.c create mode 100644 libbb/verror_msg.c create mode 100644 libbb/vfork_daemon_rexec.c create mode 100644 libbb/vherror_msg.c create mode 100644 libbb/vinfo_msg.c create mode 100644 libbb/vperror_msg.c create mode 100644 libbb/warn_ignoring_args.c create mode 100644 libbb/wfopen.c create mode 100644 libbb/wfopen_input.c create mode 100644 libbb/xatonum.c create mode 100644 libbb/xatonum_template.c create mode 100644 libbb/xconnect.c create mode 100644 libbb/xfuncs.c create mode 100644 libbb/xgetcwd.c create mode 100644 libbb/xgethostbyname.c create mode 100644 libbb/xgethostbyname2.c create mode 100644 libbb/xreadlink.c create mode 100644 libbb/xregcomp.c create mode 100644 libpwdgrp/Kbuild create mode 100644 libpwdgrp/pwd_grp.c create mode 100644 libpwdgrp/pwd_grp_internal.c create mode 100644 libpwdgrp/uidgid_get.c create mode 100644 loginutils/Config.in create mode 100644 loginutils/Kbuild create mode 100644 loginutils/addgroup.c create mode 100644 loginutils/adduser.c create mode 100644 loginutils/deluser.c create mode 100644 loginutils/getty.c create mode 100644 loginutils/login.c create mode 100644 loginutils/passwd.c create mode 100644 loginutils/su.c create mode 100644 loginutils/sulogin.c create mode 100644 loginutils/vlock.c create mode 100644 miscutils/Config.in create mode 100644 miscutils/Kbuild create mode 100644 miscutils/adjtimex.c create mode 100644 miscutils/bbconfig.c create mode 100644 miscutils/crond.c create mode 100644 miscutils/crontab.c create mode 100644 miscutils/dc.c create mode 100644 miscutils/devfsd.c create mode 100644 miscutils/eject.c create mode 100644 miscutils/hdparm.c create mode 100644 miscutils/last.c create mode 100644 miscutils/less.c create mode 100644 miscutils/makedevs.c create mode 100644 miscutils/mountpoint.c create mode 100644 miscutils/mt.c create mode 100644 miscutils/nmeter.c create mode 100644 miscutils/raidautorun.c create mode 100644 miscutils/readahead.c create mode 100644 miscutils/runlevel.c create mode 100644 miscutils/rx.c create mode 100644 miscutils/setsid.c create mode 100644 miscutils/strings.c create mode 100644 miscutils/taskset.c create mode 100644 miscutils/time.c create mode 100644 miscutils/watchdog.c create mode 100644 modutils/Config.in create mode 100644 modutils/Kbuild create mode 100644 modutils/insmod.c create mode 100644 modutils/lsmod.c create mode 100644 modutils/modprobe.c create mode 100644 modutils/rmmod.c create mode 100644 networking/Config.in create mode 100644 networking/Kbuild create mode 100644 networking/arping.c create mode 100644 networking/dnsd.c create mode 100644 networking/ether-wake.c create mode 100644 networking/fakeidentd.c create mode 100644 networking/ftpgetput.c create mode 100644 networking/hostname.c create mode 100644 networking/httpd.c create mode 100644 networking/httpd_index_cgi_example create mode 100644 networking/ifconfig.c create mode 100644 networking/ifupdown.c create mode 100644 networking/inetd.c create mode 100644 networking/interface.c create mode 100644 networking/ip.c create mode 100644 networking/ipaddr.c create mode 100644 networking/ipcalc.c create mode 100644 networking/iplink.c create mode 100644 networking/iproute.c create mode 100644 networking/iprule.c create mode 100644 networking/iptunnel.c create mode 100644 networking/libiproute/Kbuild create mode 100644 networking/libiproute/ip_common.h create mode 100644 networking/libiproute/ip_parse_common_args.c create mode 100644 networking/libiproute/ipaddress.c create mode 100644 networking/libiproute/iplink.c create mode 100644 networking/libiproute/iproute.c create mode 100644 networking/libiproute/iprule.c create mode 100644 networking/libiproute/iptunnel.c create mode 100644 networking/libiproute/libnetlink.c create mode 100644 networking/libiproute/libnetlink.h create mode 100644 networking/libiproute/linux/pkt_sched.h create mode 100644 networking/libiproute/ll_addr.c create mode 100644 networking/libiproute/ll_map.c create mode 100644 networking/libiproute/ll_map.h create mode 100644 networking/libiproute/ll_proto.c create mode 100644 networking/libiproute/ll_types.c create mode 100644 networking/libiproute/rt_names.c create mode 100644 networking/libiproute/rt_names.h create mode 100644 networking/libiproute/rtm_map.c create mode 100644 networking/libiproute/rtm_map.h create mode 100644 networking/libiproute/utils.c create mode 100644 networking/libiproute/utils.h create mode 100644 networking/nameif.c create mode 100644 networking/nc.c create mode 100644 networking/netstat.c create mode 100644 networking/nslookup.c create mode 100644 networking/ping.c create mode 100644 networking/ping6.c create mode 100644 networking/route.c create mode 100644 networking/telnet.c create mode 100644 networking/telnetd.c create mode 100644 networking/tftp.c create mode 100644 networking/traceroute.c create mode 100644 networking/udhcp/Config.in create mode 100644 networking/udhcp/Kbuild create mode 100644 networking/udhcp/arpping.c create mode 100644 networking/udhcp/clientpacket.c create mode 100644 networking/udhcp/clientsocket.c create mode 100644 networking/udhcp/common.c create mode 100644 networking/udhcp/common.h create mode 100644 networking/udhcp/dhcpc.c create mode 100644 networking/udhcp/dhcpc.h create mode 100644 networking/udhcp/dhcpd.c create mode 100644 networking/udhcp/dhcpd.h create mode 100644 networking/udhcp/dhcprelay.c create mode 100644 networking/udhcp/dumpleases.c create mode 100644 networking/udhcp/files.c create mode 100644 networking/udhcp/leases.c create mode 100644 networking/udhcp/options.c create mode 100644 networking/udhcp/options.h create mode 100644 networking/udhcp/packet.c create mode 100644 networking/udhcp/pidfile.c create mode 100644 networking/udhcp/script.c create mode 100644 networking/udhcp/serverpacket.c create mode 100644 networking/udhcp/signalpipe.c create mode 100644 networking/udhcp/socket.c create mode 100644 networking/udhcp/static_leases.c create mode 100644 networking/vconfig.c create mode 100644 networking/wget.c create mode 100644 networking/zcip.c create mode 100644 procps/Config.in create mode 100644 procps/Kbuild create mode 100644 procps/free.c create mode 100644 procps/fuser.c create mode 100644 procps/kill.c create mode 100644 procps/pidof.c create mode 100644 procps/ps.c create mode 100644 procps/ps.posix create mode 100644 procps/renice.c create mode 100644 procps/sysctl.c create mode 100644 procps/top.c create mode 100644 procps/uptime.c create mode 100644 runit/Config.in create mode 100644 runit/Kbuild create mode 100644 runit/chpst.c create mode 100644 runit/runit_lib.c create mode 100644 runit/runit_lib.h create mode 100644 runit/runsv.c create mode 100644 runit/runsvdir.c create mode 100644 runit/sv.c create mode 100644 runit/svlogd.c create mode 100644 scripts/Kbuild create mode 100644 scripts/Kbuild.include create mode 100644 scripts/Makefile.build create mode 100644 scripts/Makefile.clean create mode 100644 scripts/Makefile.host create mode 100644 scripts/Makefile.lib create mode 100644 scripts/basic/Makefile create mode 100644 scripts/basic/docproc.c create mode 100644 scripts/basic/fixdep.c create mode 100644 scripts/basic/split-include.c create mode 100755 scripts/bloat-o-meter create mode 100755 scripts/checkhelp.awk create mode 100644 scripts/defconfig create mode 100755 scripts/individual create mode 100644 scripts/kconfig/Makefile create mode 100644 scripts/kconfig/POTFILES.in create mode 100644 scripts/kconfig/conf.c create mode 100644 scripts/kconfig/confdata.c create mode 100644 scripts/kconfig/expr.c create mode 100644 scripts/kconfig/expr.h create mode 100644 scripts/kconfig/gconf.c create mode 100644 scripts/kconfig/gconf.glade create mode 100644 scripts/kconfig/images.c create mode 100644 scripts/kconfig/kconfig_load.c create mode 100644 scripts/kconfig/kxgettext.c create mode 100644 scripts/kconfig/lex.zconf.c_shipped create mode 100644 scripts/kconfig/lkc.h create mode 100644 scripts/kconfig/lkc_proto.h create mode 100644 scripts/kconfig/lxdialog/BIG.FAT.WARNING create mode 100644 scripts/kconfig/lxdialog/Makefile create mode 100644 scripts/kconfig/lxdialog/check-lxdialog.sh create mode 100644 scripts/kconfig/lxdialog/checklist.c create mode 100644 scripts/kconfig/lxdialog/colors.h create mode 100644 scripts/kconfig/lxdialog/dialog.h create mode 100644 scripts/kconfig/lxdialog/inputbox.c create mode 100644 scripts/kconfig/lxdialog/lxdialog.c create mode 100644 scripts/kconfig/lxdialog/menubox.c create mode 100644 scripts/kconfig/lxdialog/msgbox.c create mode 100644 scripts/kconfig/lxdialog/textbox.c create mode 100644 scripts/kconfig/lxdialog/util.c create mode 100644 scripts/kconfig/lxdialog/yesno.c create mode 100644 scripts/kconfig/mconf.c create mode 100644 scripts/kconfig/menu.c create mode 100644 scripts/kconfig/qconf.cc create mode 100644 scripts/kconfig/qconf.h create mode 100644 scripts/kconfig/symbol.c create mode 100644 scripts/kconfig/util.c create mode 100644 scripts/kconfig/zconf.gperf create mode 100644 scripts/kconfig/zconf.hash.c_shipped create mode 100644 scripts/kconfig/zconf.l create mode 100644 scripts/kconfig/zconf.tab.c_shipped create mode 100644 scripts/kconfig/zconf.y create mode 100755 scripts/mkconfigs create mode 100755 scripts/mkmakefile create mode 100755 scripts/objsizes create mode 100755 scripts/showasm create mode 100755 scripts/trylink create mode 100644 shell/Config.in create mode 100644 shell/Kbuild create mode 100644 shell/ash.c create mode 100644 shell/bbsh.c create mode 100644 shell/cmdedit.c create mode 100644 shell/cmdedit.h create mode 100644 shell/hush.c create mode 100644 shell/lash.c create mode 100644 shell/msh.c create mode 100644 sysklogd/Config.in create mode 100644 sysklogd/Kbuild create mode 100644 sysklogd/klogd.c create mode 100644 sysklogd/logger.c create mode 100644 sysklogd/logread.c create mode 100644 sysklogd/syslogd.c create mode 100644 testsuite/README create mode 100644 testsuite/TODO create mode 100755 testsuite/all_sourcecode.tests create mode 100644 testsuite/basename/basename-does-not-remove-identical-extension create mode 100644 testsuite/basename/basename-works create mode 100644 testsuite/bunzip2/bunzip2-reads-from-standard-input create mode 100644 testsuite/bunzip2/bunzip2-removes-compressed-file create mode 100644 testsuite/bunzip2/bzcat-does-not-remove-compressed-file create mode 100755 testsuite/busybox.tests create mode 100644 testsuite/cat/cat-prints-a-file create mode 100644 testsuite/cat/cat-prints-a-file-and-standard-input create mode 100644 testsuite/cmp/cmp-detects-difference create mode 100644 testsuite/cp/cp-a-files-to-dir create mode 100644 testsuite/cp/cp-a-preserves-links create mode 100644 testsuite/cp/cp-copies-empty-file create mode 100644 testsuite/cp/cp-copies-large-file create mode 100644 testsuite/cp/cp-copies-small-file create mode 100644 testsuite/cp/cp-d-files-to-dir create mode 100644 testsuite/cp/cp-dir-create-dir create mode 100644 testsuite/cp/cp-dir-existing-dir create mode 100644 testsuite/cp/cp-does-not-copy-unreadable-file create mode 100644 testsuite/cp/cp-files-to-dir create mode 100644 testsuite/cp/cp-follows-links create mode 100644 testsuite/cp/cp-preserves-hard-links create mode 100644 testsuite/cp/cp-preserves-links create mode 100644 testsuite/cp/cp-preserves-source-file create mode 100644 testsuite/cut/cut-cuts-a-character create mode 100644 testsuite/cut/cut-cuts-a-closed-range create mode 100644 testsuite/cut/cut-cuts-a-field create mode 100644 testsuite/cut/cut-cuts-an-open-range create mode 100644 testsuite/cut/cut-cuts-an-unclosed-range create mode 100644 testsuite/date/date-R-works create mode 100644 testsuite/date/date-format-works create mode 100644 testsuite/date/date-u-works create mode 100644 testsuite/date/date-works create mode 100644 testsuite/dd/dd-accepts-if create mode 100644 testsuite/dd/dd-accepts-of create mode 100644 testsuite/dd/dd-copies-from-standard-input-to-standard-output create mode 100644 testsuite/dd/dd-prints-count-to-standard-error create mode 100644 testsuite/dirname/dirname-handles-absolute-path create mode 100644 testsuite/dirname/dirname-handles-empty-path create mode 100644 testsuite/dirname/dirname-handles-multiple-slashes create mode 100644 testsuite/dirname/dirname-handles-relative-path create mode 100644 testsuite/dirname/dirname-handles-root create mode 100644 testsuite/dirname/dirname-handles-single-component create mode 100644 testsuite/dirname/dirname-works create mode 100644 testsuite/du/du-h-works create mode 100644 testsuite/du/du-k-works create mode 100644 testsuite/du/du-l-works create mode 100644 testsuite/du/du-m-works create mode 100644 testsuite/du/du-s-works create mode 100644 testsuite/du/du-works create mode 100644 testsuite/echo/echo-does-not-print-newline create mode 100644 testsuite/echo/echo-prints-argument create mode 100644 testsuite/echo/echo-prints-arguments create mode 100644 testsuite/echo/echo-prints-newline create mode 100644 testsuite/expr/expr-works create mode 100644 testsuite/false/false-is-silent create mode 100644 testsuite/false/false-returns-failure create mode 100644 testsuite/find/find-supports-minus-xdev create mode 100755 testsuite/grep.tests create mode 100644 testsuite/gunzip/gunzip-reads-from-standard-input create mode 100644 testsuite/gzip/gzip-accepts-multiple-files create mode 100644 testsuite/gzip/gzip-accepts-single-minus create mode 100644 testsuite/gzip/gzip-removes-original-file create mode 100644 testsuite/head/head-n-works create mode 100644 testsuite/head/head-works create mode 100644 testsuite/hostid/hostid-works create mode 100644 testsuite/hostname/hostname-d-works create mode 100644 testsuite/hostname/hostname-i-works create mode 100644 testsuite/hostname/hostname-s-works create mode 100644 testsuite/hostname/hostname-works create mode 100644 testsuite/id/id-g-works create mode 100644 testsuite/id/id-u-works create mode 100644 testsuite/id/id-un-works create mode 100644 testsuite/id/id-ur-works create mode 100644 testsuite/ln/ln-creates-hard-links create mode 100644 testsuite/ln/ln-creates-soft-links create mode 100644 testsuite/ln/ln-force-creates-hard-links create mode 100644 testsuite/ln/ln-force-creates-soft-links create mode 100644 testsuite/ln/ln-preserves-hard-links create mode 100644 testsuite/ln/ln-preserves-soft-links create mode 100644 testsuite/ls/ls-1-works create mode 100644 testsuite/ls/ls-h-works create mode 100644 testsuite/ls/ls-l-works create mode 100644 testsuite/ls/ls-s-works create mode 100644 testsuite/md5sum/md5sum-verifies-non-binary-file create mode 100644 testsuite/mkdir/mkdir-makes-a-directory create mode 100644 testsuite/mkdir/mkdir-makes-parent-directories create mode 100755 testsuite/mount.testroot create mode 100644 testsuite/msh/msh-supports-underscores-in-variable-names create mode 100644 testsuite/mv/mv-files-to-dir create mode 100644 testsuite/mv/mv-follows-links create mode 100644 testsuite/mv/mv-moves-empty-file create mode 100644 testsuite/mv/mv-moves-file create mode 100644 testsuite/mv/mv-moves-hardlinks create mode 100644 testsuite/mv/mv-moves-large-file create mode 100644 testsuite/mv/mv-moves-small-file create mode 100644 testsuite/mv/mv-moves-symlinks create mode 100644 testsuite/mv/mv-moves-unreadable-files create mode 100644 testsuite/mv/mv-preserves-hard-links create mode 100644 testsuite/mv/mv-preserves-links create mode 100644 testsuite/mv/mv-refuses-mv-dir-to-subdir create mode 100644 testsuite/mv/mv-removes-source-file create mode 100755 testsuite/pidof.tests create mode 100644 testsuite/pwd/pwd-prints-working-directory create mode 100755 testsuite/readlink.tests create mode 100644 testsuite/rm/rm-removes-file create mode 100644 testsuite/rmdir/rmdir-removes-parent-directories create mode 100755 testsuite/runtest create mode 100755 testsuite/sed.tests create mode 100755 testsuite/seq.tests create mode 100755 testsuite/sort.tests create mode 100644 testsuite/strings/strings-works-like-GNU create mode 100644 testsuite/tail/tail-n-works create mode 100644 testsuite/tail/tail-works create mode 100644 testsuite/tar/tar-archives-multiple-files create mode 100644 testsuite/tar/tar-complains-about-missing-file create mode 100644 testsuite/tar/tar-demands-at-least-one-ctx create mode 100644 testsuite/tar/tar-demands-at-most-one-ctx create mode 100644 testsuite/tar/tar-extracts-all-subdirs create mode 100644 testsuite/tar/tar-extracts-file create mode 100644 testsuite/tar/tar-extracts-from-standard-input create mode 100644 testsuite/tar/tar-extracts-multiple-files create mode 100644 testsuite/tar/tar-extracts-to-standard-output create mode 100644 testsuite/tar/tar-handles-cz-options create mode 100644 testsuite/tar/tar-handles-empty-include-and-non-empty-exclude-list create mode 100644 testsuite/tar/tar-handles-exclude-and-extract-lists create mode 100644 testsuite/tar/tar-handles-multiple-X-options create mode 100644 testsuite/tar/tar-handles-nested-exclude create mode 100644 testsuite/tee/tee-appends-input create mode 100644 testsuite/tee/tee-tees-input create mode 100755 testsuite/testing.sh create mode 100644 testsuite/touch/touch-creates-file create mode 100644 testsuite/touch/touch-does-not-create-file create mode 100644 testsuite/touch/touch-touches-files-after-non-existent-file create mode 100644 testsuite/tr/tr-d-works create mode 100644 testsuite/tr/tr-non-gnu create mode 100644 testsuite/tr/tr-works create mode 100644 testsuite/true/true-is-silent create mode 100644 testsuite/true/true-returns-success create mode 100755 testsuite/umlwrapper.sh create mode 100755 testsuite/uniq.tests create mode 100755 testsuite/unzip.tests create mode 100644 testsuite/uptime/uptime-works create mode 100755 testsuite/uuencode.tests create mode 100644 testsuite/wc/wc-counts-all create mode 100644 testsuite/wc/wc-counts-characters create mode 100644 testsuite/wc/wc-counts-lines create mode 100644 testsuite/wc/wc-counts-words create mode 100644 testsuite/wc/wc-prints-longest-line-length create mode 100644 testsuite/wget/wget--O-overrides--P create mode 100644 testsuite/wget/wget-handles-empty-path create mode 100644 testsuite/wget/wget-retrieves-google-index create mode 100644 testsuite/wget/wget-supports--P create mode 100644 testsuite/which/which-uses-default-path create mode 100644 testsuite/xargs/xargs-works create mode 100644 util-linux/Config.in create mode 100644 util-linux/Kbuild create mode 100644 util-linux/dmesg.c create mode 100644 util-linux/fbset.c create mode 100644 util-linux/fdformat.c create mode 100644 util-linux/fdisk.c create mode 100644 util-linux/fdisk_aix.c create mode 100644 util-linux/fdisk_osf.c create mode 100644 util-linux/fdisk_sgi.c create mode 100644 util-linux/fdisk_sun.c create mode 100644 util-linux/freeramdisk.c create mode 100644 util-linux/fsck_minix.c create mode 100644 util-linux/getopt.c create mode 100644 util-linux/hexdump.c create mode 100644 util-linux/hwclock.c create mode 100644 util-linux/ipcrm.c create mode 100644 util-linux/ipcs.c create mode 100644 util-linux/losetup.c create mode 100644 util-linux/mdev.c create mode 100644 util-linux/mkfs_minix.c create mode 100644 util-linux/mkswap.c create mode 100644 util-linux/more.c create mode 100644 util-linux/mount.c create mode 100644 util-linux/pivot_root.c create mode 100644 util-linux/rdate.c create mode 100644 util-linux/readprofile.c create mode 100644 util-linux/setarch.c create mode 100644 util-linux/swaponoff.c create mode 100644 util-linux/switch_root.c create mode 100644 util-linux/umount.c diff --git a/.indent.pro b/.indent.pro new file mode 100644 index 000000000..492ecf1c7 --- /dev/null +++ b/.indent.pro @@ -0,0 +1,33 @@ +--blank-lines-after-declarations +--blank-lines-after-procedures +--break-before-boolean-operator +--no-blank-lines-after-commas +--braces-on-if-line +--braces-on-struct-decl-line +--comment-indentation25 +--declaration-comment-column25 +--no-comment-delimiters-on-blank-lines +--cuddle-else +--continuation-indentation4 +--case-indentation0 +--else-endif-column33 +--space-after-cast +--line-comments-indentation0 +--declaration-indentation1 +--dont-format-first-column-comments +--dont-format-comments +--honour-newlines +--indent-level4 +/* changed from 0 to 4 */ +--parameter-indentation4 +--line-length78 /* changed from 75 */ +--continue-at-parentheses +--no-space-after-function-call-names +--dont-break-procedure-type +--dont-star-comments +--leave-optional-blank-lines +--dont-space-special-semicolon +--tab-size4 +/* additions by Mark */ +--case-brace-indentation0 +--leave-preprocessor-space diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..8f6498b98 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,173 @@ +List of the authors of code contained in BusyBox. + +If you have code in BusyBox, you should be listed here. If you should be +listed, or the description of what you have done needs more detail, or is +incorrect, _please_ let me know. + + -Erik + +----------- + +Peter Willis + eject + +Emanuele Aina + run-parts + +Erik Andersen + Tons of new stuff, major rewrite of most of the + core apps, tons of new apps as noted in header files. + Lots of tedious effort writing these boring docs that + nobody is going to actually read. + +Laurence Anderson + rpm2cpio, unzip, get_header_cpio, read_gz interface, rpm + +Jeff Angielski + ftpput, ftpget + +Enrik Berkhan + setconsole + +Jim Bauer + modprobe shell dependency + +Edward Betts + expr, hostid, logname, whoami + +John Beppu + du, nslookup, sort + +David Brownell + zcip + +Brian Candler + tiny-ls(ls) + +Randolph Chung + fbset, ping, hostname + +Dave Cinege + more(v2), makedevs, dutmp, modularization, auto links file, + various fixes, Linux Router Project maintenance + +Jordan Crouse + ipcalc + +Magnus Damm + tftp client + insmod powerpc support + +Larry Doolittle + pristine source directory compilation, lots of patches and fixes. + +Glenn Engel + httpd + +Gennady Feldman + Sysklogd (single threaded syslogd, IPC Circular buffer support, + logread), various fixes. + +Robert Griebl + modprobe, hwclock, suid/sgid handling, tinylogin integration + many bugfixes and enhancements + +Karl M. Hegbloom + cp_mv.c, the test suite, various fixes to utility.c, &c. + +Daniel Jacobowitz + mktemp.c + +Matt Kraai + documentation, bugfixes, test suite + +Rob Landley + Became busybox maintainer in 2006. + + sed (major rewrite in 2003, and I now maintain the thing) + bunzip2 (complete from-scratch rewrite, then mjn3 optimized the result) + sort (more or less from scratch rewrite in 2004, I now maintain it) + mount (rewrite in 2005, I maintain the new one) + +Stephan Linz + ipcalc, Red Hat equivalence + +John Lombardo + tr + +Glenn McGrath + Common unarchiving code and unarchiving applets, ifupdown, ftpgetput, + nameif, sed, patch, fold, install, uudecode. + Various bugfixes, review and apply numerous patches. + +Manuel Novoa III + cat, head, mkfifo, mknod, rmdir, sleep, tee, tty, uniq, usleep, wc, yes, + mesg, vconfig, nice, renice, + make_directory, parse_mode, dirname, mode_string, + get_last_path_component, simplify_path, and a number trivial libbb routines + + also bug fixes, partial rewrites, and size optimizations in + ash, basename, cal, cmp, cp, df, du, echo, env, ln, logname, md5sum, mkdir, + mv, realpath, rm, sort, tail, touch, uname, watch, arith, human_readable, + interface, dutmp, ifconfig, route + +Vladimir Oleynik + cmdedit; bb_mkdep, xargs(current), httpd(current); + ports: ash, crond, fdisk (initial, unmaintained now), inetd, stty, traceroute, + top; + locale, various fixes + and irreconcilable critic of everything not perfect. + +Bruce Perens + Original author of BusyBox in 1995, 1996. Some of his code can + still be found hiding here and there... + +Rodney Radford + ipcs, ipcrm + +Tim Riker + bug fixes, member of fan club + +Kent Robotti + reset, tons and tons of bug reports and patches. + +Chip Rosenthal , + wget - Contributed by permission of Covad Communications + +Pavel Roskin + Lots of bugs fixes and patches. + +Gyepi Sam + Remote logging feature for syslogd + +Rob Sullivan + comm + +Linus Torvalds + mkswap, fsck.minix, mkfs.minix + +Mark Whitley + grep, sed, cut, xargs(previous), + style-guide, new-applet-HOWTO, bug fixes, etc. + +Charles P. Wright + gzip, mini-netcat(nc) + +Enrique Zanardi + tarcat (since removed), loadkmap, various fixes, Debian maintenance + +Tito Ragusa + devfsd and size optimizations in strings, openvt, chvt, deallocvt, hdparm, + fdformat, lsattr, chattr, id and eject. + +Paul Fox + vi editing mode for ash, various other patches/fixes + +Roberto A. Foglietta + port: dnsd + +Bernhard Fischer + misc + +Mike Frysinger + initial e2fsprogs, printenv, setarch, sum, misc diff --git a/Config.in b/Config.in new file mode 100644 index 000000000..fb07c681b --- /dev/null +++ b/Config.in @@ -0,0 +1,479 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +mainmenu "BusyBox Configuration" + +config HAVE_DOT_CONFIG + bool + default y + +menu "Busybox Settings" + +menu "General Configuration" + +config NITPICK + bool "See lots more (probably unnecessary) configuration options." + default n + help + Some BusyBox applets have more configuration options than anyone + will ever care about. To avoid drowining people in complexity, most + of the applet features that can be set to a sane default value are + hidden, unless you hit the above switch. + + This is better than to telling people to edit the busybox source + code, but not by much. + + See http://en.wikipedia.org/wiki/Fibber_McGee_and_Molly#The_Closet + + You have been warned. + +config DESKTOP + bool "Enable options for full-blown desktop systems" + default n + help + Enable options and features which are not essential. + Select this only if you plan to use busybox on full-blown + desktop machine with common Linux distro, not on an embedded box. + +choice + prompt "Buffer allocation policy" + default FEATURE_BUFFERS_USE_MALLOC + depends on NITPICK + help + There are 3 ways BusyBox can handle buffer allocations: + - Use malloc. This costs code size for the call to xmalloc. + - Put them on stack. For some very small machines with limited stack + space, this can be deadly. For most folks, this works just fine. + - Put them in BSS. This works beautifully for computers with a real + MMU (and OS support), but wastes runtime RAM for uCLinux. This + behavior was the only one available for BusyBox versions 0.48 and + earlier. + +config FEATURE_BUFFERS_USE_MALLOC + bool "Allocate with Malloc" + +config FEATURE_BUFFERS_GO_ON_STACK + bool "Allocate on the Stack" + +config FEATURE_BUFFERS_GO_IN_BSS + bool "Allocate in the .bss section" + +endchoice + +config SHOW_USAGE + bool "Show terse applet usage messages" + default y + help + All BusyBox applets will show help messages when invoked with + wrong arguments. You can turn off printing these terse usage + messages if you say no here. + This will save you up to 7k. + +config FEATURE_VERBOSE_USAGE + bool "Show verbose applet usage messages" + default n + select SHOW_USAGE + help + All BusyBox applets will show more verbose help messages when + busybox is invoked with --help. This will add a lot of text to the + busybox binary. In the default configuration, this will add about + 13k, but it can add much more depending on your configuration. + +config FEATURE_COMPRESS_USAGE + bool "Store applet usage messages in compressed form" + default y + depends on SHOW_USAGE + help + Store usage messages in compressed form, uncompress them on-the-fly + when --help is called. + + If you have a really tiny busybox with few applets enabled (and + bunzip2 isn't one of them), the overhead of the decompressor might + be noticeable. Also, if you run executables directly from ROM + and have very little memory, this might not be a win. Otherwise, + you probably want this. + +config FEATURE_INSTALLER + bool "Support --install [-s] to install applet links at runtime" + default n + help + Enable 'busybox --install [-s]' support. This will allow you to use + busybox at runtime to create hard links or symlinks for all the + applets that are compiled into busybox. This feature requires the + /proc filesystem. + +config LOCALE_SUPPORT + bool "Enable locale support (system needs locale for this to work)" + default n + help + Enable this if your system has locale support and you would like + busybox to support locale settings. + +config GETOPT_LONG + bool "Enable support for --long-options" + default y + help + Enable this if you want busybox applets to use the gnu --long-option + style, in addition to single character -a -b -c style options. + +config FEATURE_DEVPTS + bool "Use the devpts filesystem for Unix98 PTYs" + default y + help + Enable if you want BusyBox to use Unix98 PTY support. If enabled, + busybox will use /dev/ptmx for the master side of the pseudoterminal + and /dev/pts/ for the slave side. Otherwise, BSD style + /dev/ttyp will be used. To use this option, you should have + devpts mounted. + +config FEATURE_CLEAN_UP + bool "Clean up all memory before exiting (usually not needed)" + default n + depends on NITPICK + help + As a size optimization, busybox normally exits without explicitly + freeing dynamically allocated memory or closing files. This saves + space since the OS will clean up for us, but it can confuse debuggers + like valgrind, which report tons of memory and resource leaks. + + Don't enable this unless you have a really good reason to clean + things up manually. + +config FEATURE_SUID + bool "Support for SUID/SGID handling" + default n + help + With this option you can install the busybox binary belonging + to root with the suid bit set, and it'll and it'll automatically drop + priviledges for applets that don't need root access. + + If you're really paranoid and don't want to do this, build two + busybox binaries with different applets in them (and the appropriate + symlinks pointing to each binary), and only set the suid bit on the + one that needs it. The applets currently marked to need the suid bit + are login, passwd, su, ping, traceroute, crontab, dnsd, ipcrm, ipcs, + and vlock. + +config FEATURE_SYSLOG + bool "Support for syslog" + default n + help + This option is auto-selected when you select any applet which may + send its output to syslog. You do not need to select it manually. + +config FEATURE_SUID_CONFIG + bool "Runtime SUID/SGID configuration via /etc/busybox.conf" + default n if FEATURE_SUID + depends on FEATURE_SUID + help + Allow the SUID / SGID state of an applet to be determined at runtime + by checking /etc/busybox.conf. (This is sort of a poor man's sudo.) + The format of this file is as follows: + + = [Ssx-][Ssx-][x-] (|).(|) + + An example might help: + + [SUID] + su = ssx root.0 # applet su can be run by anyone and runs with euid=0/egid=0 + su = ssx # exactly the same + + mount = sx- root.disk # applet mount can be run by root and members of group disk + # and runs with euid=0 + + cp = --- # disable applet cp for everyone + + The file has to be owned by user root, group root and has to be + writeable only by root: + (chown 0.0 /etc/busybox.conf; chmod 600 /etc/busybox.conf) + The busybox executable has to be owned by user root, group + root and has to be setuid root for this to work: + (chown 0.0 /bin/busybox; chmod 4755 /bin/busybox) + + Robert 'sandman' Griebl has more information here: + . + +config FEATURE_SUID_CONFIG_QUIET + bool "Suppress warning message if /etc/busybox.conf is not readable" + default y + depends on FEATURE_SUID_CONFIG + help + /etc/busybox.conf should be readable by the user needing the SUID, check + this option to avoid users to be notified about missing permissions. + +config FEATURE_HAVE_RPC + bool "RPC support" + default y + help + Select this if you have rpc support. + This automatically turns off all configuration options that rely + on RPC. + +config SELINUX + bool "Support NSA Security Enhanced Linux" + default n + help + Enable support for SELinux in applets ls, ps, and id. Also provide + the option of compiling in SELinux applets. + + If you do not have a complete SELinux userland installed, this stuff + will not compile. Go visit + http://www.nsa.gov/selinux/index.html + to download the necessary stuff to allow busybox to compile with + this option enabled. Specifially, libselinux 1.28 or better is + directly required by busybox. If the installation is located in a + non-standard directory, provide it by invoking make as follows: + CFLAGS=-I \ + LDFLAGS=-L \ + make + + Most people will leave this set to 'N'. + +config BUSYBOX_EXEC_PATH + string "Path to BusyBox executable" + default "/proc/self/exe" + help + When Busybox applets need to run other busybox applets, BusyBox + sometimes needs to exec() itself. When the /proc filesystem is + mounted, /proc/self/exe always points to the currently running + executable. If you haven't got /proc, set this to wherever you + want to run BusyBox from. + +endmenu + +menu 'Build Options' + +config STATIC + bool "Build BusyBox as a static binary (no shared libs)" + default n + help + If you want to build a static BusyBox binary, which does not + use or require any shared libraries, then enable this option. + This can cause BusyBox to be considerably larger, so you should + leave this option false unless you have a good reason (i.e. + your target platform does not support shared libraries, or + you are building an initrd which doesn't need anything but + BusyBox, etc). + + Most people will leave this set to 'N'. + +config BUILD_LIBBUSYBOX + bool "Build shared libbusybox" + default n + help + Build a shared library libbusybox.so which contains all + libraries used inside busybox. + + This is an experimental feature intended to support the upcoming + "make standalone" mode. Enabling it against the one big busybox + binary serves no purpose (and increases the size). You should + almost certainly say "no" to this right now. + +config FEATURE_FULL_LIBBUSYBOX + bool "Feature-complete libbusybox" + default n if !FEATURE_SHARED_BUSYBOX + depends on BUILD_LIBBUSYBOX + help + Build a libbusybox with the complete feature-set, disregarding + the actually selected config. + + Normally, libbusybox will only contain the features which are + used by busybox itself. If you plan to write a separate + standalone application which uses libbusybox say 'Y'. + + Note: libbusybox is GPL, not LGPL, and exports no stable API that + might act as a copyright barrier. We can and will modify the + exported function set between releases (even minor version number + changes), and happily break out-of-tree features. + + Say 'N' if in doubt. + +config FEATURE_SHARED_BUSYBOX + bool "Use shared libbusybox for busybox" + default y if BUILD_LIBBUSYBOX + depends on !STATIC && BUILD_LIBBUSYBOX + help + Use libbusybox.so also for busybox itself. + You need to have a working dynamic linker to use this variant. + +config LFS + bool "Build with Large File Support (for accessing files > 2 GB)" + default n + select FDISK_SUPPORT_LARGE_DISKS + help + If you want to build BusyBox with large file support, then enable + this option. This will have no effect if your kernel or your C + library lacks large file support for large files. Some of the + programs that can benefit from large file support include dd, gzip, + cp, mount, tar, and many others. If you want to access files larger + than 2 Gigabytes, enable this option. Otherwise, leave it set to 'N'. + +config BUILD_AT_ONCE + bool "Compile all sources at once" + default n + help + Normally each source-file is compiled with one invocation of + the compiler. + If you set this option, all sources are compiled at once. + This gives the compiler more opportunities to optimize which can + result in smaller and/or faster binaries. + + Setting this option will consume alot of memory, e.g. if you + enable all applets with all features, gcc uses more than 300MB + RAM during compilation of busybox. + + This option is most likely only beneficial for newer compilers + such as gcc-4.1 and above. + + Say 'N' unless you know what you are doing. + +endmenu + +menu 'Debugging Options' + +config DEBUG + bool "Build BusyBox with extra Debugging symbols" + default n + help + Say Y here if you wish to examine BusyBox internals while applets are + running. This increases the size of the binary considerably, and + should only be used when doing development. If you are doing + development and want to debug BusyBox, answer Y. + + Most people should answer N. + +config DEBUG_PESSIMIZE + bool "Disable compiler optimizations." + default n + depends on DEBUG + help + The compiler's optimization of source code can eliminate and reorder + code, resulting in an executable that's hard to understand when + stepping through it with a debugger. This switches it off, resulting + in a much bigger executable that more closely matches the source + code. + +choice + prompt "Additional debugging library" + default NO_DEBUG_LIB + depends on DEBUG + help + Using an additional debugging library will make BusyBox become + considerable larger and will cause it to run more slowly. You + should always leave this option disabled for production use. + + dmalloc support: + ---------------- + This enables compiling with dmalloc ( http://dmalloc.com/ ) + which is an excellent public domain mem leak and malloc problem + detector. To enable dmalloc, before running busybox you will + want to properly set your environment, for example: + export DMALLOC_OPTIONS=debug=0x34f47d83,inter=100,log=logfile + The 'debug=' value is generated using the following command + dmalloc -p log-stats -p log-non-free -p log-bad-space -p log-elapsed-time \ + -p check-fence -p check-heap -p check-lists -p check-blank \ + -p check-funcs -p realloc-copy -p allow-free-null + + Electric-fence support: + ----------------------- + This enables compiling with Electric-fence support. Electric + fence is another very useful malloc debugging library which uses + your computer's virtual memory hardware to detect illegal memory + accesses. This support will make BusyBox be considerable larger + and run slower, so you should leave this option disabled unless + you are hunting a hard to find memory problem. + + +config NO_DEBUG_LIB + bool "None" + +config DMALLOC + bool "Dmalloc" + +config EFENCE + bool "Electric-fence" + +endchoice + +config DEBUG_YANK_SUSv2 + bool "Disable obsolete features removed before SUSv3?" + default y + help + This option will disable backwards compatibility with SuSv2, + specifically, old-style numeric options ('command -1 ') + will not be supported in head, tail, and fold. (Note: should + yank from renice too.) + +endmenu + +menu 'Installation Options' + +config INSTALL_NO_USR + bool "Don't use /usr" + default n + help + Disable use of /usr. Don't activate this option if you don't know + that you really want this behaviour. + +choice + prompt "Applets links" + default INSTALL_APPLET_SYMLINKS + help + Choose how you install applets links. + +config INSTALL_APPLET_SYMLINKS + bool "as soft-links" + help + Install applets as soft-links to the busybox binary. This needs some + free inodes on the filesystem, but might help with filesystem + generators that can't cope with hard-links. + +config INSTALL_APPLET_HARDLINKS + bool "as hard-links" + help + Install applets as hard-links to the busybox binary. This might count + on a filesystem with few inodes. + +config INSTALL_APPLET_DONT + bool + prompt "not installed" + depends on FEATURE_INSTALLER || FEATURE_SH_STANDALONE_SHELL + help + Do not install applets links. Usefull when using the -install feature + or a standalone shell for rescue pruposes. + +endchoice + +config PREFIX + string "BusyBox installation prefix" + default "./_install" + help + Define your directory to install BusyBox files/subdirs in. + +endmenu + +source libbb/Config.in + +endmenu + +comment "Applets" + +source archival/Config.in +source coreutils/Config.in +source console-tools/Config.in +source debianutils/Config.in +source editors/Config.in +source findutils/Config.in +source init/Config.in +source loginutils/Config.in +source e2fsprogs/Config.in +source modutils/Config.in +source util-linux/Config.in +source miscutils/Config.in +source networking/Config.in +source procps/Config.in +source shell/Config.in +source sysklogd/Config.in +source runit/Config.in diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000..6644481a3 --- /dev/null +++ b/INSTALL @@ -0,0 +1,125 @@ +Building: +========= + +The BusyBox build process is similar to the Linux kernel build: + + make menuconfig # This creates a file called ".config" + make # This creates the "busybox" executable + make install # or make PREFIX=/path/from/root install + +The full list of configuration and install options is available by typing: + + make help + +Quick Start: +============ + +The easy way to try out BusyBox for the first time, without having to install +it, is to enable all features and then use "standalone shell" mode with a +blank command $PATH. + +To enable all features, use "make defconfig", which produces the largest +general-purpose configuration. (It's allyesconfig minus debugging options, +optional packaging choices, and a few special-purpose features requiring +extra configuration to use.) + + make defconfig + make + PATH= ./busybox ash + +Standalone shell mode causes busybox's built-in command shell to run +any built-in busybox applets directly, without looking for external +programs by that name. Supplying an empty command path (as above) means +the only commands busybox can find are the built-in ones. + +Note that the standalone shell requires CONFIG_BUSYBOX_EXEC_PATH +to be set appropriately, depending on whether or not /proc/self/exe is +available or not. If you do not have /proc, then point that config option +to the location of your busybox binary, usually /bin/busybox. + +Configuring Busybox: +==================== + +Busybox is optimized for size, but enabling the full set of functionality +still results in a fairly large executable -- more than 1 megabyte when +statically linked. To save space, busybox can be configured with only the +set of applets needed for each environment. The minimal configuration, with +all applets disabled, produces a 4k executable. (It's useless, but very small.) + +The manual configurator "make menuconfig" modifies the existing configuration. +(For systems without ncurses, try "make config" instead.) The two most +interesting starting configurations are "make allnoconfig" (to start with +everything disabled and add just what you need), and "make defconfig" (to +start with everything enabled and remove what you don't need). If menuconfig +is run without an existing configuration, make defconfig will run first to +create a known starting point. + +Other starting configurations (mostly used for testing purposes) include +"make allbareconfig" (enables all applets but disables all optional features), +"make allyesconfig" (enables absolutely everything including debug features), +and "make randconfig" (produce a random configuration). + +Configuring BusyBox produces a file ".config", which can be saved for future +use. Run "make oldconfig" to bring a .config file from an older version of +busybox up to date. + +Installing Busybox: +=================== + +Busybox is a single executable that can behave like many different commands, +and BusyBox uses the name it was invoked under to determine the desired +behavior. (Try "mv busybox ls" and then "./ls -l".) + +Installing busybox consists of creating symlinks (or hardlinks) to the busybox +binary for each applet enabled in busybox, and making sure these symlinks are +in the shell's command $PATH. Running "make install" creates these symlinks, +or "make install-hardlinks" creates hardlinks instead (useful on systems with +a limited number of inodes). This install process uses the file +"busybox.links" (created by make), which contains the list of enabled applets +and the path at which to install them. + +Installing links to busybox is not always necessary. The special applet name +"busybox" (or with any optional suffix, such as "busybox-static") uses the +first argument to determine which applet to behave as, for example +"./busybox cat LICENSE". (Running the busybox applet with no arguments gives +a list of all enabled applets.) The standalone shell can also call busybox +applets without links to busybox under other names in the filesystem. You can +also configure a standaone install capability into the busybox base applet, +and then install such links at runtime with one of "busybox --install" (for +hardlinks) or "busybox --install -s" (for symlinks). + +If you enabled the busybox shared library feature (libbusybox.so) and want +to run tests without installing, set your LD_LIBRARY_PATH accordingly when +running the executable: + + LD_LIBRARY_PATH=`pwd` ./busybox + +Building out-of-tree: +===================== + +By default, the BusyBox build puts its temporary files in the source tree. +Building from a read-only source tree, or building multiple configurations from +the same source directory, requires the ability to put the temporary files +somewhere else. + +To build out of tree, cd to an empty directory and configure busybox from there: + + make -f /path/to/source/Makefile defconfig + make + make install + +Alternately, use the O=$BUILDPATH option (with an absolute path) during the +configuration step, as in: + + make O=/some/empty/directory allyesconfig + cd /some/empty/directory + make + make PREFIX=. install + +More Information: +================= + +Se also the busybox FAQ, under the questions "How can I get started using +BusyBox" and "How do I build a BusyBox-based system?" The BusyBox FAQ is +available from http://www.busybox.net/FAQ.html or as the file +docs/busybox.net/FAQ.html in this tarball. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..9d9bdc7e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,348 @@ +--- A note on GPL versions + +BusyBox is distributed under version 2 of the General Public License (included +in its entirety, below). Version 2 is the only version of this license which +this version of BusyBox (or modified versions derived from this one) may be +distributed under. + +------------------------------------------------------------------------ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..e05fad166 --- /dev/null +++ b/Makefile @@ -0,0 +1,1289 @@ +VERSION = 1 +PATCHLEVEL = 3 +SUBLEVEL = 0 +EXTRAVERSION = +NAME = Unnamed + +# *DOCUMENTATION* +# To see a list of typical targets execute "make help" +# More info can be located in ./README +# Comments in this file are targeted only to the developer, do not +# expect to learn how to build the kernel reading this file. + +# Do not print "Entering directory ..." +MAKEFLAGS += --no-print-directory + +# We are using a recursive build, so we need to do a little thinking +# to get the ordering right. +# +# Most importantly: sub-Makefiles should only ever modify files in +# their own directory. If in some directory we have a dependency on +# a file in another dir (which doesn't happen often, but it's often +# unavoidable when linking the built-in.o targets which finally +# turn into busybox), we will call a sub make in that other dir, and +# after that we are sure that everything which is in that other dir +# is now up to date. +# +# The only cases where we need to modify files which have global +# effects are thus separated out and done before the recursive +# descending is started. They are now explicitly listed as the +# prepare rule. + +# To put more focus on warnings, be less verbose as default +# Use 'make V=1' to see the full commands + +ifdef V + ifeq ("$(origin V)", "command line") + KBUILD_VERBOSE = $(V) + endif +endif +ifndef KBUILD_VERBOSE + KBUILD_VERBOSE = 0 +endif + +# Call sparse as part of compilation of C files +# Use 'make C=1' to enable sparse checking + +ifdef C + ifeq ("$(origin C)", "command line") + KBUILD_CHECKSRC = $(C) + endif +endif +ifndef KBUILD_CHECKSRC + KBUILD_CHECKSRC = 0 +endif + +# Use make M=dir to specify directory of external module to build +# Old syntax make ... SUBDIRS=$PWD is still supported +# Setting the environment variable KBUILD_EXTMOD take precedence +ifdef SUBDIRS + KBUILD_EXTMOD ?= $(SUBDIRS) +endif +ifdef M + ifeq ("$(origin M)", "command line") + KBUILD_EXTMOD := $(M) + endif +endif + + +# kbuild supports saving output files in a separate directory. +# To locate output files in a separate directory two syntaxes are supported. +# In both cases the working directory must be the root of the kernel src. +# 1) O= +# Use "make O=dir/to/store/output/files/" +# +# 2) Set KBUILD_OUTPUT +# Set the environment variable KBUILD_OUTPUT to point to the directory +# where the output files shall be placed. +# export KBUILD_OUTPUT=dir/to/store/output/files/ +# make +# +# The O= assignment takes precedence over the KBUILD_OUTPUT environment +# variable. + + +# KBUILD_SRC is set on invocation of make in OBJ directory +# KBUILD_SRC is not intended to be used by the regular user (for now) +ifeq ($(KBUILD_SRC),) + +# OK, Make called in directory where kernel src resides +# Do we want to locate output files in a separate directory? +ifdef O + ifeq ("$(origin O)", "command line") + KBUILD_OUTPUT := $(O) + endif +endif + +# That's our default target when none is given on the command line +PHONY := _all +_all: + +ifneq ($(KBUILD_OUTPUT),) +# Invoke a second make in the output directory, passing relevant variables +# check that the output directory actually exists +saved-output := $(KBUILD_OUTPUT) +KBUILD_OUTPUT := $(shell cd $(KBUILD_OUTPUT) && /bin/pwd) +$(if $(KBUILD_OUTPUT),, \ + $(error output directory "$(saved-output)" does not exist)) + +PHONY += $(MAKECMDGOALS) + +$(filter-out _all,$(MAKECMDGOALS)) _all: + $(if $(KBUILD_VERBOSE:1=),@)$(MAKE) -C $(KBUILD_OUTPUT) \ + KBUILD_SRC=$(CURDIR) \ + KBUILD_EXTMOD="$(KBUILD_EXTMOD)" -f $(CURDIR)/Makefile $@ + +# Leave processing to above invocation of make +skip-makefile := 1 +endif # ifneq ($(KBUILD_OUTPUT),) +endif # ifeq ($(KBUILD_SRC),) + +# We process the rest of the Makefile if this is the final invocation of make +ifeq ($(skip-makefile),) + +# If building an external module we do not care about the all: rule +# but instead _all depend on modules +PHONY += all +ifeq ($(KBUILD_EXTMOD),) +_all: all +else +_all: modules +endif + +srctree := $(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR)) +TOPDIR := $(srctree) +# FIXME - TOPDIR is obsolete, use srctree/objtree +objtree := $(CURDIR) +src := $(srctree) +obj := $(objtree) + +VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD)) + +export srctree objtree VPATH TOPDIR + + +# SUBARCH tells the usermode build what the underlying arch is. That is set +# first, and if a usermode build is happening, the "ARCH=um" on the command +# line overrides the setting of ARCH below. If a native build is happening, +# then ARCH is assigned, getting whatever value it gets normally, and +# SUBARCH is subsequently ignored. + +SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \ + -e s/arm.*/arm/ -e s/sa110/arm/ \ + -e s/s390x/s390/ -e s/parisc64/parisc/ \ + -e s/ppc.*/powerpc/ -e s/mips.*/mips/ ) + +# Cross compiling and selecting different set of gcc/bin-utils +# --------------------------------------------------------------------------- +# +# When performing cross compilation for other architectures ARCH shall be set +# to the target architecture. (See arch/* for the possibilities). +# ARCH can be set during invocation of make: +# make ARCH=ia64 +# Another way is to have ARCH set in the environment. +# The default ARCH is the host where make is executed. + +# CROSS_COMPILE specify the prefix used for all executables used +# during compilation. Only gcc and related bin-utils executables +# are prefixed with $(CROSS_COMPILE). +# CROSS_COMPILE can be set on the command line +# make CROSS_COMPILE=ia64-linux- +# Alternatively CROSS_COMPILE can be set in the environment. +# Default value for CROSS_COMPILE is not to prefix executables +# Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile + +ARCH ?= $(SUBARCH) +CROSS_COMPILE ?= + +# Architecture as present in compile.h +UTS_MACHINE := $(ARCH) + +# SHELL used by kbuild +CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ + else if [ -x /bin/bash ]; then echo /bin/bash; \ + else echo sh; fi ; fi) + +HOSTCC = gcc +HOSTCXX = g++ +HOSTCFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer +HOSTCXXFLAGS = -O2 + +# Decide whether to build built-in, modular, or both. +# Normally, just do built-in. + +KBUILD_MODULES := +KBUILD_BUILTIN := 1 + +# If we have only "make modules", don't compile built-in objects. +# When we're building modules with modversions, we need to consider +# the built-in objects during the descend as well, in order to +# make sure the checksums are uptodate before we record them. + +ifeq ($(MAKECMDGOALS),modules) + KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1) +endif + +# If we have "make modules", compile modules +# in addition to whatever we do anyway. +# Just "make" or "make all" shall build modules as well + +ifneq ($(filter all _all modules,$(MAKECMDGOALS)),) + KBUILD_MODULES := 1 +endif + +ifeq ($(MAKECMDGOALS),) + KBUILD_MODULES := 1 +endif + +export KBUILD_MODULES KBUILD_BUILTIN +export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD + +# Beautify output +# --------------------------------------------------------------------------- +# +# Normally, we echo the whole command before executing it. By making +# that echo $($(quiet)$(cmd)), we now have the possibility to set +# $(quiet) to choose other forms of output instead, e.g. +# +# quiet_cmd_cc_o_c = Compiling $(RELDIR)/$@ +# cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< +# +# If $(quiet) is empty, the whole command will be printed. +# If it is set to "quiet_", only the short version will be printed. +# If it is set to "silent_", nothing wil be printed at all, since +# the variable $(silent_cmd_cc_o_c) doesn't exist. +# +# A simple variant is to prefix commands with $(Q) - that's useful +# for commands that shall be hidden in non-verbose mode. +# +# $(Q)ln $@ :< +# +# If KBUILD_VERBOSE equals 0 then the above command will be hidden. +# If KBUILD_VERBOSE equals 1 then the above command is displayed. + +ifeq ($(KBUILD_VERBOSE),1) + quiet = + Q = +else + quiet=quiet_ + Q = @ +endif + +# If the user is running make -s (silent mode), suppress echoing of +# commands + +ifneq ($(findstring s,$(MAKEFLAGS)),) + quiet=silent_ +endif + +export quiet Q KBUILD_VERBOSE + + +# Look for make include files relative to root of kernel src +MAKEFLAGS += --include-dir=$(srctree) + +# We need some generic definitions +include $(srctree)/scripts/Kbuild.include + +# For maximum performance (+ possibly random breakage, uncomment +# the following) + +MAKEFLAGS += -rR + +# Make variables (CC, etc...) + +AS = $(CROSS_COMPILE)as +LD = $(CROSS_COMPILE)ld +CC = $(CROSS_COMPILE)gcc +CPP = $(CC) -E +AR = $(CROSS_COMPILE)ar +NM = $(CROSS_COMPILE)nm +STRIP = $(CROSS_COMPILE)strip +OBJCOPY = $(CROSS_COMPILE)objcopy +OBJDUMP = $(CROSS_COMPILE)objdump +AWK = awk +GENKSYMS = scripts/genksyms/genksyms +DEPMOD = /sbin/depmod +KALLSYMS = scripts/kallsyms +PERL = perl +CHECK = sparse + +CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ -Wbitwise $(CF) +MODFLAGS = -DMODULE +CFLAGS_MODULE = $(MODFLAGS) +AFLAGS_MODULE = $(MODFLAGS) +LDFLAGS_MODULE = -r +CFLAGS_KERNEL = +AFLAGS_KERNEL = + + +# Use LINUXINCLUDE when you must reference the include/ directory. +# Needed to be compatible with the O= option +CFLAGS := +CPPFLAGS := +AFLAGS := + +# Read KERNELRELEASE from .kernelrelease (if it exists) +KERNELRELEASE = $(shell cat .kernelrelease 2> /dev/null) +KERNELVERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) + +export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION \ + ARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC \ + CPP AR NM STRIP OBJCOPY OBJDUMP MAKE AWK GENKSYMS PERL UTS_MACHINE \ + HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS + +export CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS +export CFLAGS CFLAGS_KERNEL CFLAGS_MODULE +export AFLAGS AFLAGS_KERNEL AFLAGS_MODULE + +# When compiling out-of-tree modules, put MODVERDIR in the module +# tree rather than in the kernel tree. The kernel tree might +# even be read-only. +export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions + +# Files to ignore in find ... statements + +RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o -name CVS -o -name .pc -o -name .hg -o -name .git \) -prune -o +export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn --exclude CVS --exclude .pc --exclude .hg --exclude .git + +# =========================================================================== +# Rules shared between *config targets and build targets + +# Basic helpers built in scripts/ +PHONY += scripts_basic +scripts_basic: + $(Q)$(MAKE) $(build)=scripts/basic + +# To avoid any implicit rule to kick in, define an empty command. +scripts/basic/%: scripts_basic ; + +PHONY += outputmakefile +# outputmakefile generates a Makefile in the output directory, if using a +# separate output directory. This allows convenient use of make in the +# output directory. +outputmakefile: +ifneq ($(KBUILD_SRC),) + $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \ + $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL) +endif + +# To make sure we do not include .config for any of the *config targets +# catch them early, and hand them over to scripts/kconfig/Makefile +# It is allowed to specify more targets when calling make, including +# mixing *config targets and build targets. +# For example 'make oldconfig all'. +# Detect when mixed targets is specified, and make a second invocation +# of make so .config is not included in this case either (for *config). + +no-dot-config-targets := clean mrproper distclean \ + cscope TAGS tags help %docs check% + +config-targets := 0 +mixed-targets := 0 +dot-config := 1 + +ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),) + ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),) + dot-config := 0 + endif +endif + +ifeq ($(KBUILD_EXTMOD),) + ifneq ($(filter config %config,$(MAKECMDGOALS)),) + config-targets := 1 + ifneq ($(filter-out config %config,$(MAKECMDGOALS)),) + mixed-targets := 1 + endif + endif +endif + +ifeq ($(mixed-targets),1) +# =========================================================================== +# We're called with mixed targets (*config and build targets). +# Handle them one by one. + +%:: FORCE + $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= $@ + +else +ifeq ($(config-targets),1) +# =========================================================================== +# *config targets only - make sure prerequisites are updated, and descend +# in scripts/kconfig to make the *config target + +# Read arch specific Makefile to set KBUILD_DEFCONFIG as needed. +# KBUILD_DEFCONFIG may point out an alternative default configuration +# used for 'make defconfig' +-include $(srctree)/arch/$(ARCH)/Makefile +export KBUILD_DEFCONFIG + +config %config: scripts_basic outputmakefile FORCE + $(Q)mkdir -p include + $(Q)$(MAKE) $(build)=scripts/kconfig $@ + $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= .kernelrelease + +else +# =========================================================================== +# Build targets only - this includes busybox, arch specific targets, clean +# targets and others. In general all targets except *config targets. + +ifeq ($(KBUILD_EXTMOD),) +# Additional helpers built in scripts/ +# Carefully list dependencies so we do not try to build scripts twice +# in parrallel +PHONY += scripts +scripts: scripts_basic include/config/MARKER + $(Q)$(MAKE) $(build)=$(@) + +scripts_basic: include/autoconf.h + +# Objects we will link into busybox / subdirs we need to visit +core-y := \ + applets/ \ + +libs-y := \ + archival/ \ + archival/libunarchive/ \ + console-tools/ \ + coreutils/ \ + coreutils/libcoreutils/ \ + debianutils/ \ + e2fsprogs/ \ + e2fsprogs/blkid/ \ + e2fsprogs/e2p/ \ + e2fsprogs/ext2fs/ \ + e2fsprogs/uuid/ \ + editors/ \ + findutils/ \ + init/ \ + libbb/ \ + libpwdgrp/ \ + loginutils/ \ + miscutils/ \ + modutils/ \ + networking/ \ + networking/libiproute/ \ + networking/udhcp/ \ + procps/ \ + runit/ \ + shell/ \ + sysklogd/ \ + util-linux/ \ + +endif # KBUILD_EXTMOD + +ifeq ($(dot-config),1) +# In this section, we need .config + +# Read in dependencies to all Kconfig* files, make sure to run +# oldconfig if changes are detected. +-include .kconfig.d + +-include .config + +# If .config needs to be updated, it will be done via the dependency +# that autoconf has on .config. +# To avoid any implicit rule to kick in, define an empty command +.config .kconfig.d: ; + +# Now we can define CFLAGS etc according to .config +include $(srctree)/Makefile.flags + +# If .config is newer than include/autoconf.h, someone tinkered +# with it and forgot to run make oldconfig. +# If kconfig.d is missing then we are probarly in a cleaned tree so +# we execute the config step to be sure to catch updated Kconfig files +include/autoconf.h: .kconfig.d .config + $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig + +else +# Dummy target needed, because used as prerequisite +include/autoconf.h: ; +endif + +# The all: target is the default when no target is given on the +# command line. +# This allow a user to issue only 'make' to build a kernel including modules +# Defaults busybox but it is usually overriden in the arch makefile +all: busybox + +-include $(srctree)/arch/$(ARCH)/Makefile + +# arch Makefile may override CC so keep this after arch Makefile is included +#bbox# NOSTDINC_FLAGS += -nostdinc -isystem $(shell $(CC) -print-file-name=include) +CHECKFLAGS += $(NOSTDINC_FLAGS) + +# warn about C99 declaration after statement +CFLAGS += $(call cc-option,-Wdeclaration-after-statement,) + +# disable pointer signedness warnings in gcc 4.0 +CFLAGS += $(call cc-option,-Wno-pointer-sign,) + +# Default kernel image to build when no specific target is given. +# KBUILD_IMAGE may be overruled on the commandline or +# set in the environment +# Also any assignments in arch/$(ARCH)/Makefile take precedence over +# this default value +export KBUILD_IMAGE ?= busybox + +# +# INSTALL_PATH specifies where to place the updated kernel and system map +# images. Default is /boot, but you can set it to other values +export INSTALL_PATH ?= /boot + +# +# INSTALL_MOD_PATH specifies a prefix to MODLIB for module directory +# relocations required by build roots. This is not defined in the +# makefile but the arguement can be passed to make if needed. +# + +MODLIB = $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE) +export MODLIB + + +ifeq ($(KBUILD_EXTMOD),) +busybox-dirs := $(patsubst %/,%,$(filter %/, $(core-y) $(core-m) $(libs-y) $(libs-m))) + +busybox-alldirs := $(sort $(busybox-dirs) $(patsubst %/,%,$(filter %/, \ + $(core-n) $(core-) $(libs-n) $(libs-) \ + ))) + +core-y := $(patsubst %/, %/built-in.o, $(core-y)) +libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) +libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y)) +libs-y := $(libs-y1) $(libs-y2) + + +# Build busybox +# --------------------------------------------------------------------------- +# busybox is build from the objects selected by $(busybox-init) and +# $(busybox-main). Most are built-in.o files from top-level directories +# in the kernel tree, others are specified in arch/$(ARCH)Makefile. +# Ordering when linking is important, and $(busybox-init) must be first. +# +# busybox +# ^ +# | +# +-< $(busybox-init) +# | +--< init/version.o + more +# | +# +--< $(busybox-main) +# | +--< driver/built-in.o mm/built-in.o + more +# | +# +-< kallsyms.o (see description in CONFIG_KALLSYMS section) +# +# busybox version (uname -v) cannot be updated during normal +# descending-into-subdirs phase since we do not yet know if we need to +# update busybox. +# Therefore this step is delayed until just before final link of busybox - +# except in the kallsyms case where it is done just before adding the +# symbols to the kernel. +# +# System.map is generated to document addresses of all kernel symbols + +busybox-all := $(core-y) $(libs-y) + +# Rule to link busybox - also used during CONFIG_KALLSYMS +# May be overridden by arch/$(ARCH)/Makefile +quiet_cmd_busybox__ ?= LINK $@ +ifdef CONFIG_STATIC + cmd_busybox__ ?= $(srctree)/scripts/trylink $(CC) $(LDFLAGS) \ + -static \ + -o $@ \ + -Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections \ + -Wl,--start-group $(busybox-all) -Wl,--end-group +else + cmd_busybox__ ?= $(srctree)/scripts/trylink $(CC) -o $@ $(LDFLAGS) \ + -Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections \ + -Wl,--start-group $(busybox-all) -Wl,--end-group +endif + +# Generate System.map +quiet_cmd_sysmap = SYSMAP + cmd_sysmap = $(CONFIG_SHELL) $(srctree)/scripts/mksysmap + +# Link of busybox +# If CONFIG_KALLSYMS is set .version is already updated +# Generate System.map and verify that the content is consistent +# Use + in front of the busybox_version rule to silent warning with make -j2 +# First command is ':' to allow us to use + in front of the rule +define rule_busybox__ + : + $(call cmd,busybox__) + $(Q)echo 'cmd_$@ := $(cmd_busybox__)' > $(@D)/.$(@F).cmd +endef + + +ifdef CONFIG_KALLSYMS +# Generate section listing all symbols and add it into busybox $(kallsyms.o) +# It's a three stage process: +# o .tmp_busybox1 has all symbols and sections, but __kallsyms is +# empty +# Running kallsyms on that gives us .tmp_kallsyms1.o with +# the right size - busybox version (uname -v) is updated during this step +# o .tmp_busybox2 now has a __kallsyms section of the right size, +# but due to the added section, some addresses have shifted. +# From here, we generate a correct .tmp_kallsyms2.o +# o The correct .tmp_kallsyms2.o is linked into the final busybox. +# o Verify that the System.map from busybox matches the map from +# .tmp_busybox2, just in case we did not generate kallsyms correctly. +# o If CONFIG_KALLSYMS_EXTRA_PASS is set, do an extra pass using +# .tmp_busybox3 and .tmp_kallsyms3.o. This is only meant as a +# temporary bypass to allow the kernel to be built while the +# maintainers work out what went wrong with kallsyms. + +ifdef CONFIG_KALLSYMS_EXTRA_PASS +last_kallsyms := 3 +else +last_kallsyms := 2 +endif + +kallsyms.o := .tmp_kallsyms$(last_kallsyms).o + +define verify_kallsyms + $(Q)$(if $($(quiet)cmd_sysmap), \ + echo ' $($(quiet)cmd_sysmap) .tmp_System.map' &&) \ + $(cmd_sysmap) .tmp_busybox$(last_kallsyms) .tmp_System.map + $(Q)cmp -s System.map .tmp_System.map || \ + (echo Inconsistent kallsyms data; \ + echo Try setting CONFIG_KALLSYMS_EXTRA_PASS; \ + rm .tmp_kallsyms* ; /bin/false ) +endef + +# Update busybox version before link +# Use + in front of this rule to silent warning about make -j1 +# First command is ':' to allow us to use + in front of this rule +cmd_ksym_ld = $(cmd_busybox__) +define rule_ksym_ld + : + +$(call cmd,busybox_version) + $(call cmd,busybox__) + $(Q)echo 'cmd_$@ := $(cmd_busybox__)' > $(@D)/.$(@F).cmd +endef + +# Generate .S file with all kernel symbols +quiet_cmd_kallsyms = KSYM $@ + cmd_kallsyms = $(NM) -n $< | $(KALLSYMS) \ + $(if $(CONFIG_KALLSYMS_ALL),--all-symbols) > $@ + +.tmp_kallsyms1.o .tmp_kallsyms2.o .tmp_kallsyms3.o: %.o: %.S scripts FORCE + $(call if_changed_dep,as_o_S) + +.tmp_kallsyms%.S: .tmp_busybox% $(KALLSYMS) + $(call cmd,kallsyms) + +# .tmp_busybox1 must be complete except kallsyms, so update busybox version +.tmp_busybox1: $(busybox-lds) $(busybox-all) FORCE + $(call if_changed_rule,ksym_ld) + +.tmp_busybox2: $(busybox-lds) $(busybox-all) .tmp_kallsyms1.o FORCE + $(call if_changed,busybox__) + +.tmp_busybox3: $(busybox-lds) $(busybox-all) .tmp_kallsyms2.o FORCE + $(call if_changed,busybox__) + +# Needs to visit scripts/ before $(KALLSYMS) can be used. +$(KALLSYMS): scripts ; + +# Generate some data for debugging strange kallsyms problems +debug_kallsyms: .tmp_map$(last_kallsyms) + +.tmp_map%: .tmp_busybox% FORCE + ($(OBJDUMP) -h $< | $(AWK) '/^ +[0-9]/{print $$4 " 0 " $$2}'; $(NM) $<) | sort > $@ + +.tmp_map3: .tmp_map2 + +.tmp_map2: .tmp_map1 + +endif # ifdef CONFIG_KALLSYMS + +# busybox image - including updated kernel symbols +busybox_unstripped: $(busybox-all) FORCE + $(call if_changed_rule,busybox__) + $(Q)rm -f .old_version + +busybox: busybox_unstripped + $(Q)$(STRIP) -s --remove-section=.note --remove-section=.comment \ + busybox_unstripped -o $@ + +# The actual objects are generated when descending, +# make sure no implicit rule kicks in +$(sort $(busybox-all)): $(busybox-dirs) ; + +# Handle descending into subdirectories listed in $(busybox-dirs) +# Preset locale variables to speed up the build process. Limit locale +# tweaks to this spot to avoid wrong language settings when running +# make menuconfig etc. +# Error messages still appears in the original language + +PHONY += $(busybox-dirs) +$(busybox-dirs): prepare scripts + $(Q)$(MAKE) $(build)=$@ + +# Build the kernel release string +# The KERNELRELEASE is stored in a file named .kernelrelease +# to be used when executing for example make install or make modules_install +# +# Take the contents of any files called localversion* and the config +# variable CONFIG_LOCALVERSION and append them to KERNELRELEASE. +# LOCALVERSION from the command line override all of this + +nullstring := +space := $(nullstring) # end of line + +___localver = $(objtree)/localversion* $(srctree)/localversion* +__localver = $(sort $(wildcard $(___localver))) +# skip backup files (containing '~') +_localver = $(foreach f, $(__localver), $(if $(findstring ~, $(f)),,$(f))) + +localver = $(subst $(space),, \ + $(shell cat /dev/null $(_localver)) \ + $(patsubst "%",%,$(CONFIG_LOCALVERSION))) + +# If CONFIG_LOCALVERSION_AUTO is set scripts/setlocalversion is called +# and if the SCM is know a tag from the SCM is appended. +# The appended tag is determinded by the SCM used. +# +# Currently, only git is supported. +# Other SCMs can edit scripts/setlocalversion and add the appropriate +# checks as needed. +ifdef CONFIG_LOCALVERSION_AUTO + _localver-auto = $(shell $(CONFIG_SHELL) \ + $(srctree)/scripts/setlocalversion $(srctree)) + localver-auto = $(LOCALVERSION)$(_localver-auto) +endif + +localver-full = $(localver)$(localver-auto) + +# Store (new) KERNELRELASE string in .kernelrelease +kernelrelease = $(KERNELVERSION)$(localver-full) +.kernelrelease: FORCE + $(Q)rm -f $@ + $(Q)echo $(kernelrelease) > $@ + + +# Things we need to do before we recursively start building the kernel +# or the modules are listed in "prepare". +# A multi level approach is used. prepareN is processed before prepareN-1. +# archprepare is used in arch Makefiles and when processed asm symlink, +# version.h and scripts_basic is processed / created. + +# Listed in dependency order +PHONY += prepare archprepare prepare0 prepare1 prepare2 prepare3 + +# prepare-all is deprecated, use prepare as valid replacement +PHONY += prepare-all + +# prepare3 is used to check if we are building in a separate output directory, +# and if so do: +# 1) Check that make has not been executed in the kernel src $(srctree) +# 2) Create the include2 directory, used for the second asm symlink +prepare3: .kernelrelease +ifneq ($(KBUILD_SRC),) + @echo ' Using $(srctree) as source for kernel' + $(Q)if [ -f $(srctree)/.config ]; then \ + echo " $(srctree) is not clean, please run 'make mrproper'";\ + echo " in the '$(srctree)' directory.";\ + /bin/false; \ + fi; + $(Q)if [ ! -d include2 ]; then mkdir -p include2; fi; + $(Q)ln -fsn $(srctree)/include/asm-$(ARCH) include2/asm +endif + +# prepare2 creates a makefile if using a separate output directory +prepare2: prepare3 outputmakefile + +prepare1: prepare2 include/config/MARKER +ifneq ($(KBUILD_MODULES),) + $(Q)mkdir -p $(MODVERDIR) + $(Q)rm -f $(MODVERDIR)/* +endif + +archprepare: prepare1 scripts_basic + +prepare0: archprepare FORCE + $(Q)$(MAKE) $(build)=. + +# All the preparing.. +prepare prepare-all: prepare0 + +# Leave this as default for preprocessing busybox.lds.S, which is now +# done in arch/$(ARCH)/kernel/Makefile + +export CPPFLAGS_busybox.lds += -P -C -U$(ARCH) + +# FIXME: The asm symlink changes when $(ARCH) changes. That's +# hard to detect, but I suppose "make mrproper" is a good idea +# before switching between archs anyway. + +#bbox# include/asm: +#bbox# @echo ' SYMLINK $@ -> include/asm-$(ARCH)' +#bbox# $(Q)if [ ! -d include ]; then mkdir -p include; fi; +#bbox# @ln -fsn asm-$(ARCH) $@ + +# Split autoconf.h into include/linux/config/* +#bbox# piggybacked generation of few .h files +include/config/MARKER: scripts/basic/split-include include/autoconf.h + @echo ' SPLIT include/autoconf.h -> include/config/*' + @scripts/basic/split-include include/autoconf.h include/config + @echo ' GEN include/bbconfigopts.h' + @$(srctree)/scripts/mkconfigs >include/bbconfigopts.h + @touch $@ + +# Generate some files +# --------------------------------------------------------------------------- + +# KERNELRELEASE can change from a few different places, meaning version.h +# needs to be updated, so this check is forced on all builds + +uts_len := 64 + +define filechk_version.h + if [ `echo -n "$(KERNELRELEASE)" | wc -c ` -gt $(uts_len) ]; then \ + echo '"$(KERNELRELEASE)" exceeds $(uts_len) characters' >&2; \ + exit 1; \ + fi; \ + (echo \#define UTS_RELEASE \"$(KERNELRELEASE)\"; \ + echo \#define LINUX_VERSION_CODE `expr $(VERSION) \\* 65536 + $(PATCHLEVEL) \\* 256 + $(SUBLEVEL)`; \ + echo '#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))'; \ + ) +endef + +# --------------------------------------------------------------------------- + +PHONY += depend dep +depend dep: + @echo '*** Warning: make $@ is unnecessary now.' + +# --------------------------------------------------------------------------- +# Modules + +ifdef CONFIG_MODULES + +# By default, build modules as well + +all: modules + +# Build modules + +PHONY += modules +modules: $(busybox-dirs) $(if $(KBUILD_BUILTIN),busybox) + @echo ' Building modules, stage 2.'; + $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modpost + + +# Target to prepare building external modules +PHONY += modules_prepare +modules_prepare: prepare scripts + +# Target to install modules +PHONY += modules_install +modules_install: _modinst_ _modinst_post + +PHONY += _modinst_ +_modinst_: + @if [ -z "`$(DEPMOD) -V 2>/dev/null | grep module-init-tools`" ]; then \ + echo "Warning: you may need to install module-init-tools"; \ + echo "See http://www.codemonkey.org.uk/docs/post-halloween-2.6.txt";\ + sleep 1; \ + fi + @rm -rf $(MODLIB)/kernel + @rm -f $(MODLIB)/source + @mkdir -p $(MODLIB)/kernel + @ln -s $(srctree) $(MODLIB)/source + @if [ ! $(objtree) -ef $(MODLIB)/build ]; then \ + rm -f $(MODLIB)/build ; \ + ln -s $(objtree) $(MODLIB)/build ; \ + fi + $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modinst + +# If System.map exists, run depmod. This deliberately does not have a +# dependency on System.map since that would run the dependency tree on +# busybox. This depmod is only for convenience to give the initial +# boot a modules.dep even before / is mounted read-write. However the +# boot script depmod is the master version. +ifeq "$(strip $(INSTALL_MOD_PATH))" "" +depmod_opts := +else +depmod_opts := -b $(INSTALL_MOD_PATH) -r +endif +PHONY += _modinst_post +_modinst_post: _modinst_ + if [ -r System.map -a -x $(DEPMOD) ]; then $(DEPMOD) -ae -F System.map $(depmod_opts) $(KERNELRELEASE); fi + +else # CONFIG_MODULES + +# Modules not configured +# --------------------------------------------------------------------------- + +modules modules_install: FORCE + @echo + @echo "The present busybox configuration has modules disabled." + @echo "Type 'make config' and enable loadable module support." + @echo "Then build a kernel with module support enabled." + @echo + @exit 1 + +endif # CONFIG_MODULES + +### +# Cleaning is done on three levels. +# make clean Delete most generated files +# Leave enough to build external modules +# make mrproper Delete the current configuration, and all generated files +# make distclean Remove editor backup files, patch leftover files and the like + +# Directories & files removed with 'make clean' +CLEAN_DIRS += $(MODVERDIR) +CLEAN_FILES += busybox* System.map \ + .tmp_kallsyms* .tmp_version .tmp_busybox* .tmp_System.map + +# Directories & files removed with 'make mrproper' +MRPROPER_DIRS += include/config include2 +MRPROPER_FILES += .config .config.old include/asm .version .old_version \ + include/autoconf.h \ + include/bbconfigopts.h \ + include/usage_compressed.h \ + .kernelrelease Module.symvers tags TAGS cscope* + +# clean - Delete most, but leave enough to build external modules +# +clean: rm-dirs := $(CLEAN_DIRS) +clean: rm-files := $(CLEAN_FILES) +clean-dirs := $(addprefix _clean_,$(srctree) $(busybox-alldirs)) + +PHONY += $(clean-dirs) clean archclean +$(clean-dirs): + $(Q)$(MAKE) $(clean)=$(patsubst _clean_%,%,$@) + +clean: archclean $(clean-dirs) + $(call cmd,rmdirs) + $(call cmd,rmfiles) + @find . $(RCS_FIND_IGNORE) \ + \( -name '*.[oas]' -o -name '*.ko' -o -name '.*.cmd' \ + -o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \) \ + -type f -print | xargs rm -f + +# mrproper - Delete all generated files, including .config +# +mrproper: rm-dirs := $(wildcard $(MRPROPER_DIRS)) +mrproper: rm-files := $(wildcard $(MRPROPER_FILES)) +mrproper-dirs := $(addprefix _mrproper_,scripts) + +PHONY += $(mrproper-dirs) mrproper archmrproper +$(mrproper-dirs): + $(Q)$(MAKE) $(clean)=$(patsubst _mrproper_%,%,$@) + +mrproper: clean archmrproper $(mrproper-dirs) + $(call cmd,rmdirs) + $(call cmd,rmfiles) + +# distclean +# +PHONY += distclean + +distclean: mrproper + @find $(srctree) $(RCS_FIND_IGNORE) \ + \( -name '*.orig' -o -name '*.rej' -o -name '*~' \ + -o -name '*.bak' -o -name '#*#' -o -name '.*.orig' \ + -o -name '.*.rej' -o -size 0 \ + -o -name '*%' -o -name '.*.cmd' -o -name 'core' \) \ + -type f -print | xargs rm -f + + +# Packaging of the kernel to various formats +# --------------------------------------------------------------------------- +# rpm target kept for backward compatibility +package-dir := $(srctree)/scripts/package + +%pkg: FORCE + $(Q)$(MAKE) $(build)=$(package-dir) $@ +rpm: FORCE + $(Q)$(MAKE) $(build)=$(package-dir) $@ + + +# Brief documentation of the typical targets used +# --------------------------------------------------------------------------- + +boards := $(wildcard $(srctree)/arch/$(ARCH)/configs/*_defconfig) +boards := $(notdir $(boards)) + +-include $(srctree)/Makefile.help + +# Documentation targets +# --------------------------------------------------------------------------- +%docs: scripts_basic FORCE + $(Q)$(MAKE) $(build)=Documentation/DocBook $@ + +else # KBUILD_EXTMOD + +### +# External module support. +# When building external modules the kernel used as basis is considered +# read-only, and no consistency checks are made and the make +# system is not used on the basis kernel. If updates are required +# in the basis kernel ordinary make commands (without M=...) must +# be used. +# +# The following are the only valid targets when building external +# modules. +# make M=dir clean Delete all automatically generated files +# make M=dir modules Make all modules in specified dir +# make M=dir Same as 'make M=dir modules' +# make M=dir modules_install +# Install the modules build in the module directory +# Assumes install directory is already created + +# We are always building modules +KBUILD_MODULES := 1 +PHONY += crmodverdir +crmodverdir: + $(Q)mkdir -p $(MODVERDIR) + $(Q)rm -f $(MODVERDIR)/* + +PHONY += $(objtree)/Module.symvers +$(objtree)/Module.symvers: + @test -e $(objtree)/Module.symvers || ( \ + echo; \ + echo " WARNING: Symbol version dump $(objtree)/Module.symvers"; \ + echo " is missing; modules will have no dependencies and modversions."; \ + echo ) + +module-dirs := $(addprefix _module_,$(KBUILD_EXTMOD)) +PHONY += $(module-dirs) modules +$(module-dirs): crmodverdir $(objtree)/Module.symvers + $(Q)$(MAKE) $(build)=$(patsubst _module_%,%,$@) + +modules: $(module-dirs) + @echo ' Building modules, stage 2.'; + $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modpost + +PHONY += modules_install +modules_install: _emodinst_ _emodinst_post + +install-dir := $(if $(INSTALL_MOD_DIR),$(INSTALL_MOD_DIR),extra) +PHONY += _emodinst_ +_emodinst_: + $(Q)mkdir -p $(MODLIB)/$(install-dir) + $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modinst + +# Run depmod only is we have System.map and depmod is executable +quiet_cmd_depmod = DEPMOD $(KERNELRELEASE) + cmd_depmod = if [ -r System.map -a -x $(DEPMOD) ]; then \ + $(DEPMOD) -ae -F System.map \ + $(if $(strip $(INSTALL_MOD_PATH)), \ + -b $(INSTALL_MOD_PATH) -r) \ + $(KERNELRELEASE); \ + fi + +PHONY += _emodinst_post +_emodinst_post: _emodinst_ + $(call cmd,depmod) + +clean-dirs := $(addprefix _clean_,$(KBUILD_EXTMOD)) + +PHONY += $(clean-dirs) clean +$(clean-dirs): + $(Q)$(MAKE) $(clean)=$(patsubst _clean_%,%,$@) + +clean: rm-dirs := $(MODVERDIR) +clean: $(clean-dirs) + $(call cmd,rmdirs) + @find $(KBUILD_EXTMOD) $(RCS_FIND_IGNORE) \ + \( -name '*.[oas]' -o -name '*.ko' -o -name '.*.cmd' \ + -o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \) \ + -type f -print | xargs rm -f + +help: + @echo ' Building external modules.' + @echo ' Syntax: make -C path/to/kernel/src M=$$PWD target' + @echo '' + @echo ' modules - default target, build the module(s)' + @echo ' modules_install - install the module' + @echo ' clean - remove generated files in module directory only' + @echo '' + +# Dummies... +PHONY += prepare scripts +prepare: ; +scripts: ; +endif # KBUILD_EXTMOD + +# Generate tags for editors +# --------------------------------------------------------------------------- + +#We want __srctree to totally vanish out when KBUILD_OUTPUT is not set +#(which is the most common case IMHO) to avoid unneeded clutter in the big tags file. +#Adding $(srctree) adds about 20M on i386 to the size of the output file! + +ifeq ($(src),$(obj)) +__srctree = +else +__srctree = $(srctree)/ +endif + +ifeq ($(ALLSOURCE_ARCHS),) +ifeq ($(ARCH),um) +ALLINCLUDE_ARCHS := $(ARCH) $(SUBARCH) +else +ALLINCLUDE_ARCHS := $(ARCH) +endif +else +#Allow user to specify only ALLSOURCE_PATHS on the command line, keeping existing behaviour. +ALLINCLUDE_ARCHS := $(ALLSOURCE_ARCHS) +endif + +ALLSOURCE_ARCHS := $(ARCH) + +define all-sources + ( find $(__srctree) $(RCS_FIND_IGNORE) \ + \( -name include -o -name arch \) -prune -o \ + -name '*.[chS]' -print; \ + for ARCH in $(ALLSOURCE_ARCHS) ; do \ + find $(__srctree)arch/$${ARCH} $(RCS_FIND_IGNORE) \ + -name '*.[chS]' -print; \ + done ; \ + find $(__srctree)security/selinux/include $(RCS_FIND_IGNORE) \ + -name '*.[chS]' -print; \ + find $(__srctree)include $(RCS_FIND_IGNORE) \ + \( -name config -o -name 'asm-*' \) -prune \ + -o -name '*.[chS]' -print; \ + for ARCH in $(ALLINCLUDE_ARCHS) ; do \ + find $(__srctree)include/asm-$${ARCH} $(RCS_FIND_IGNORE) \ + -name '*.[chS]' -print; \ + done ; \ + find $(__srctree)include/asm-generic $(RCS_FIND_IGNORE) \ + -name '*.[chS]' -print ) +endef + +quiet_cmd_cscope-file = FILELST cscope.files + cmd_cscope-file = (echo \-k; echo \-q; $(all-sources)) > cscope.files + +quiet_cmd_cscope = MAKE cscope.out + cmd_cscope = cscope -b + +cscope: FORCE + $(call cmd,cscope-file) + $(call cmd,cscope) + +quiet_cmd_TAGS = MAKE $@ +define cmd_TAGS + rm -f $@; \ + ETAGSF=`etags --version | grep -i exuberant >/dev/null && \ + echo "-I __initdata,__exitdata,__acquires,__releases \ + -I EXPORT_SYMBOL,EXPORT_SYMBOL_GPL \ + --extra=+f --c-kinds=+px"`; \ + $(all-sources) | xargs etags $$ETAGSF -a +endef + +TAGS: FORCE + $(call cmd,TAGS) + + +quiet_cmd_tags = MAKE $@ +define cmd_tags + rm -f $@; \ + CTAGSF=`ctags --version | grep -i exuberant >/dev/null && \ + echo "-I __initdata,__exitdata,__acquires,__releases \ + -I EXPORT_SYMBOL,EXPORT_SYMBOL_GPL \ + --extra=+f --c-kinds=+px"`; \ + $(all-sources) | xargs ctags $$CTAGSF -a +endef + +tags: FORCE + $(call cmd,tags) + + +# Scripts to check various things for consistency +# --------------------------------------------------------------------------- + +includecheck: + find * $(RCS_FIND_IGNORE) \ + -name '*.[hcS]' -type f -print | sort \ + | xargs $(PERL) -w scripts/checkincludes.pl + +versioncheck: + find * $(RCS_FIND_IGNORE) \ + -name '*.[hcS]' -type f -print | sort \ + | xargs $(PERL) -w scripts/checkversion.pl + +namespacecheck: + $(PERL) $(srctree)/scripts/namespace.pl + +endif #ifeq ($(config-targets),1) +endif #ifeq ($(mixed-targets),1) + +PHONY += checkstack +checkstack: + $(OBJDUMP) -d busybox $$(find . -name '*.ko') | \ + $(PERL) $(src)/scripts/checkstack.pl $(ARCH) + +kernelrelease: + $(if $(wildcard .kernelrelease), $(Q)echo $(KERNELRELEASE), \ + $(error kernelrelease not valid - run 'make *config' to update it)) +kernelversion: + @echo $(KERNELVERSION) + +# Single targets +# --------------------------------------------------------------------------- +# Single targets are compatible with: +# - build whith mixed source and output +# - build with separate output dir 'make O=...' +# - external modules +# +# target-dir => where to store outputfile +# build-dir => directory in kernel source tree to use + +ifeq ($(KBUILD_EXTMOD),) + build-dir = $(patsubst %/,%,$(dir $@)) + target-dir = $(dir $@) +else + zap-slash=$(filter-out .,$(patsubst %/,%,$(dir $@))) + build-dir = $(KBUILD_EXTMOD)$(if $(zap-slash),/$(zap-slash)) + target-dir = $(if $(KBUILD_EXTMOD),$(dir $<),$(dir $@)) +endif + +%.s: %.c prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) +%.i: %.c prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) +%.o: %.c prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) +%.lst: %.c prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) +%.s: %.S prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) +%.o: %.S prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) + +# Modules +/ %/: prepare scripts FORCE + $(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \ + $(build)=$(build-dir) +%.ko: prepare scripts FORCE + $(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \ + $(build)=$(build-dir) $(@:.ko=.o) + $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modpost + +# FIXME Should go into a make.lib or something +# =========================================================================== + +quiet_cmd_rmdirs = $(if $(wildcard $(rm-dirs)),CLEAN $(wildcard $(rm-dirs))) + cmd_rmdirs = rm -rf $(rm-dirs) + +quiet_cmd_rmfiles = $(if $(wildcard $(rm-files)),CLEAN $(wildcard $(rm-files))) + cmd_rmfiles = rm -f $(rm-files) + + +a_flags = -Wp,-MD,$(depfile) $(AFLAGS) $(AFLAGS_KERNEL) \ + $(NOSTDINC_FLAGS) $(CPPFLAGS) \ + $(modkern_aflags) $(EXTRA_AFLAGS) $(AFLAGS_$(*F).o) + +quiet_cmd_as_o_S = AS $@ +cmd_as_o_S = $(CC) $(a_flags) -c -o $@ $< + +# read all saved command lines + +targets := $(wildcard $(sort $(targets))) +cmd_files := $(wildcard .*.cmd $(foreach f,$(targets),$(dir $(f)).$(notdir $(f)).cmd)) + +ifneq ($(cmd_files),) + $(cmd_files): ; # Do not try to update included dependency files + include $(cmd_files) +endif + +# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.clean obj=dir +# Usage: +# $(Q)$(MAKE) $(clean)=dir +clean := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.clean obj + +endif # skip-makefile + +PHONY += FORCE +FORCE: + +-include $(srctree)/Makefile.custom + +# Declare the contents of the .PHONY variable as phony. We keep that +# information in a variable se we can use it in if_changed and friends. +.PHONY: $(PHONY) diff --git a/Makefile.custom b/Makefile.custom new file mode 100644 index 000000000..e976e739e --- /dev/null +++ b/Makefile.custom @@ -0,0 +1,145 @@ +# ========================================================================== +# Build system +# ========================================================================== + +%.bflt: %_unstripped + $(CROSS_COMPILE)elf2flt $(ELF2FLTFLAGS) $< -o $@ + +busybox.links: $(srctree)/applets/busybox.mkll $(objtree)/include/autoconf.h $(srctree)/include/applets.h + $(Q)-$(SHELL) $^ >$@ + +.PHONY: install +ifeq ($(CONFIG_INSTALL_APPLET_SYMLINKS),y) +INSTALL_OPTS:= --symlinks +endif +ifeq ($(CONFIG_INSTALL_APPLET_HARDLINKS),y) +INSTALL_OPTS:= --hardlinks +endif +install: $(srctree)/applets/install.sh busybox busybox.links + $(Q)DO_INSTALL_LIBS="$(strip $(LIBBUSYBOX_SONAME) $(DO_INSTALL_LIBS))" \ + $(SHELL) $< $(CONFIG_PREFIX) $(INSTALL_OPTS) +ifeq ($(strip $(CONFIG_FEATURE_SUID)),y) + @echo + @echo + @echo -------------------------------------------------- + @echo You will probably need to make your busybox binary + @echo setuid root to ensure all configured applets will + @echo work properly. + @echo -------------------------------------------------- + @echo +endif + +uninstall: busybox.links + rm -f $(CONFIG_PREFIX)/bin/busybox + for i in `cat busybox.links` ; do rm -f $(CONFIG_PREFIX)$$i; done +ifneq ($(strip $(DO_INSTALL_LIBS)),n) + for i in $(LIBBUSYBOX_SONAME) $(DO_INSTALL_LIBS); do \ + rm -f $(CONFIG_PREFIX)$$i; \ + done +endif + +check test: busybox + bindir=$(objtree) srcdir=$(srctree)/testsuite SED="$(SED)" \ + $(SHELL) $(srctree)/testsuite/runtest $(if $(KBUILD_VERBOSE:1=),-v) + +.PHONY: release +release: distclean + cd ..; \ + rm -r -f busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION); \ + cp -a busybox busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) && { \ + find busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)/ -type d \ + -name .svn \ + -print \ + -exec rm -r -f {} \; ; \ + find busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)/ -type f \ + -name .\#* \ + -print \ + -exec rm -f {} \; ; \ + tar -czf busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION).tar.gz \ + busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)/ ; } + +.PHONY: checkhelp +checkhelp: + $(Q)$(srctree)/scripts/checkhelp.awk \ + $(patsubst %,$(srctree)/%,$(wildcard $(patsubst %,%/Config.in,$(busybox-dirs) ./))) + +.PHONY: sizes +sizes: busybox_unstripped + $(NM) --size-sort $(<) + +.PHONY: bloatcheck +bloatcheck: busybox_old busybox_unstripped + @$(srctree)/scripts/bloat-o-meter busybox_old busybox_unstripped + +.PHONY: baseline +baseline: busybox_unstripped + @mv busybox_unstripped busybox_old + +.PHONY: objsizes +objsizes: busybox_unstripped + $(srctree)/scripts/objsizes + +.PHONY: bigdata +bigdata: busybox_unstripped + nm --size-sort busybox_unstripped | grep -vi ' [tr] ' | tail -20 + +# Documentation Targets +.PHONY: doc +doc: docs/busybox.pod docs/BusyBox.txt docs/BusyBox.1 docs/BusyBox.html + +docs/busybox.pod: $(srctree)/docs/busybox_header.pod \ + $(srctree)/include/usage.h \ + $(srctree)/docs/busybox_footer.pod \ + $(srctree)/docs/autodocifier.pl + $(disp_doc) + $(Q)-mkdir -p docs + $(Q)-( cat $(srctree)/docs/busybox_header.pod ; \ + $(srctree)/docs/autodocifier.pl $(srctree)/include/usage.h ; \ + cat $(srctree)/docs/busybox_footer.pod ; ) > docs/busybox.pod + +docs/BusyBox.txt: docs/busybox.pod + $(disp_doc) + $(Q)-mkdir -p docs + $(Q)-pod2text $< > $@ + +docs/BusyBox.1: docs/busybox.pod + $(disp_doc) + $(Q)-mkdir -p docs + $(Q)-pod2man --center=BusyBox --release="version $(VERSION)" \ + $< > $@ + +docs/BusyBox.html: docs/busybox.net/BusyBox.html + $(disp_doc) + $(Q)-mkdir -p docs + $(Q)-rm -f docs/BusyBox.html + $(Q)-cp docs/busybox.net/BusyBox.html docs/BusyBox.html + +docs/busybox.net/BusyBox.html: docs/busybox.pod + $(Q)-mkdir -p docs/busybox.net + $(Q)-pod2html --noindex $< > \ + docs/busybox.net/BusyBox.html + $(Q)-rm -f pod2htm* + +# documentation, cross-reference +# Modern distributions already ship synopsis packages (e.g. debian) +# If you have an old distribution go to http://synopsis.fresco.org/ +syn_tgt = $(wildcard $(patsubst %,%/*.c,$(busybox-alldirs))) +syn = $(patsubst %.c, %.syn, $(syn_tgt)) + +comma:= , +brace_open:= ( +brace_close:= ) + +SYN_CPPFLAGS := $(strip $(CPPFLAGS) $(EXTRA_CPPFLAGS)) +SYN_CPPFLAGS := $(subst $(brace_open),\$(brace_open),$(SYN_CPPFLAGS)) +SYN_CPPFLAGS := $(subst $(brace_close),\$(brace_close),$(SYN_CPPFLAGS)) +#SYN_CPPFLAGS := $(subst ",\",$(SYN_CPPFLAGS)) +#") +#SYN_CPPFLAGS := [$(patsubst %,'%'$(comma),$(SYN_CPPFLAGS))''] + +%.syn: %.c + synopsis -p C -l Comments.SSDFilter,Comments.Previous -Wp,preprocess=True,cppflags="'$(SYN_CPPFLAGS)'" -o $@ $< + +.PHONY: html +html: $(syn) + synopsis -f HTML -Wf,title="'BusyBox Documentation'" -o $@ $^ diff --git a/Makefile.flags b/Makefile.flags new file mode 100644 index 000000000..0261d34df --- /dev/null +++ b/Makefile.flags @@ -0,0 +1,28 @@ +# ========================================================================== +# Build system +# ========================================================================== + +BB_VER = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) + +# -std=gnu99 needed for [U]LLONG_MAX on some systems + +CPPFLAGS += \ + -std=gnu99 \ + -Iinclude -Ilibbb \ + $(if $(KBUILD_SRC),-Iinclude2 -I$(srctree)/include) -I$(srctree)/libbb \ + -include include/autoconf.h \ + -D_GNU_SOURCE -DNDEBUG \ + $(if $(CONFIG_LFS),-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64) \ + -D"BB_VER=KBUILD_STR($(BB_VER))" -DBB_BT=AUTOCONF_TIMESTAMP + +CFLAGS += \ + -Wall -Wstrict-prototypes -Wshadow -Werror -Wundef \ + -funsigned-char -fno-builtin-strlen -finline-limit=0 -static-libgcc \ + -Os -falign-functions=1 -falign-jumps=1 -falign-loops=1 \ + -fomit-frame-pointer -ffunction-sections -fdata-sections + +ifeq ($(CONFIG_DEBUG),y) +CFLAGS += -g +LDFLAGS += -g +endif + diff --git a/Makefile.help b/Makefile.help new file mode 100644 index 000000000..a1ff4fcb8 --- /dev/null +++ b/Makefile.help @@ -0,0 +1,42 @@ +# ========================================================================== +# Build system +# ========================================================================== + +help: + @echo 'Cleaning:' + @echo ' clean - delete temporary files created by build' + @echo ' distclean - delete all non-source files (including .config)' + @echo + @echo 'Build:' + @echo ' all - Executable and documentation' + @echo ' busybox - the swiss-army executable' + @echo ' doc - docs/BusyBox.{txt,html,1}' + @echo ' html - create html-based cross-reference' + @echo + @echo 'Configuration:' + @echo ' allnoconfig - disable all symbols in .config' + @echo ' allyesconfig - enable all symbols in .config (see defconfig)' + @echo ' allbareconfig - enable all applets without any sub-features' + @echo ' config - text based configurator (of last resort)' + @echo ' defconfig - set .config to largest generic configuration' + @echo ' menuconfig - interactive curses-based configurator' + @echo ' oldconfig - resolve any unresolved symbols in .config' + @echo ' hosttools - build sed for the host.' + @echo ' You can use these commands if the commands on the host' + @echo ' is unusable. Afterwards use it like:' + @echo ' make SED="$(objtree)/sed"' + @echo + @echo 'Installation:' + @echo ' install - install busybox into $(PREFIX)' + @echo ' uninstall' + @echo + @echo 'Development:' + @echo ' baseline - create busybox_old for bloatcheck.' + @echo ' bloatcheck - show size difference between old and new versions' + @echo ' check - run the test suite for all applets' + @echo ' checkhelp - check for missing help-entries in Config.in' + @echo ' randconfig - generate a random configuration' + @echo ' release - create a distribution tarball' + @echo ' sizes - show size of all enabled busybox symbols' + @echo ' objsizes - show size of each .o object built' + @echo diff --git a/README b/README new file mode 100644 index 000000000..0ed196a53 --- /dev/null +++ b/README @@ -0,0 +1,198 @@ +Please see the LICENSE file for details on copying and usage. +Please refer to the INSTALL file for instructions on how to build. + +What is busybox: + + BusyBox combines tiny versions of many common UNIX utilities into a single + small executable. It provides minimalist replacements for most of the + utilities you usually find in bzip2, coreutils, dhcp, diffutils, e2fsprogs, + file, findutils, gawk, grep, inetutils, less, modutils, net-tools, procps, + sed, shadow, sysklogd, sysvinit, tar, util-linux, and vim. The utilities + in BusyBox often have fewer options than their full-featured cousins; + however, the options that are included provide the expected functionality + and behave very much like their larger counterparts. + + BusyBox has been written with size-optimization and limited resources in + mind, both to produce small binaries and to reduce run-time memory usage. + Busybox is also extremely modular so you can easily include or exclude + commands (or features) at compile time. This makes it easy to customize + embedded systems; to create a working system, just add /dev, /etc, and a + Linux kernel. Busybox (usually together with uClibc) has also been used as + a component of "thin client" desktop systems, live-CD distributions, rescue + disks, installers, and so on. + + BusyBox provides a fairly complete POSIX environment for any small system, + both embedded environments and more full featured systems concerned about + space. Busybox is slowly working towards implementing the full Single Unix + Specification V3 (http://www.opengroup.org/onlinepubs/009695399/), but isn't + there yet (and for size reasons will probably support at most UTF-8 for + internationalization). We are also interested in passing the Linux Test + Project (http://ltp.sourceforge.net). + +---------------- + +Using busybox: + + BusyBox is extremely configurable. This allows you to include only the + components and options you need, thereby reducing binary size. Run 'make + config' or 'make menuconfig' to select the functionality that you wish to + enable. (See 'make help' for more commands.) + + The behavior of busybox is determined by the name it's called under: as + "cp" it behaves like cp, as "sed" it behaves like sed, and so on. Called + as "busybox" it takes the second argument as the name of the applet to + run (I.E. "./busybox ls -l /proc"). + + The "standalone shell" mode is an easy way to try out busybox; this is a + command shell that calls the builtin applets without needing them to be + installed in the path. (Note that this requires /proc to be mounted, if + testing from a boot floppy or in a chroot environment.) + + The build automatically generates a file "busybox.links", which is used by + 'make install' to create symlinks to the BusyBox binary for all compiled in + commands. This uses the PREFIX environment variable to specify where to + install, and installs hardlinks or symlinks depending on the configuration + preferences. (You can also manually run the install script at + "applets/install.sh"). + +---------------- + +Downloading the current source code: + + Source for the latest released version, as well as daily snapshots, can always + be downloaded from + + http://busybox.net/downloads/ + + You can browse the up to the minute source code and change history online. + + http://www.busybox.net/cgi-bin/viewcvs.cgi/trunk/busybox/ + + Anonymous SVN access is available. For instructions, check out: + + http://busybox.net/subversion.html + + For those that are actively contributing and would like to check files in, + see: + + http://busybox.net/developer.html + + The developers also have a bug and patch tracking system + (http://bugs.busybox.net) although posting a bug/patch to the mailing list + is generally a faster way of getting it fixed, and the complete archive of + what happened is the subversion changelog. + +---------------- + +getting help: + + when you find you need help, you can check out the busybox mailing list + archives at http://busybox.net/lists/busybox/ or even join + the mailing list if you are interested. + +---------------- + +bugs: + + if you find bugs, please submit a detailed bug report to the busybox mailing + list at busybox@busybox.net. a well-written bug report should include a + transcript of a shell session that demonstrates the bad behavior and enables + anyone else to duplicate the bug on their own machine. the following is such + an example: + + to: busybox@busybox.net + from: diligent@testing.linux.org + subject: /bin/date doesn't work + + package: busybox + version: 1.00 + + when i execute busybox 'date' it produces unexpected results. + with gnu date i get the following output: + + $ date + fri oct 8 14:19:41 mdt 2004 + + but when i use busybox date i get this instead: + + $ date + illegal instruction + + i am using debian unstable, kernel version 2.4.25-vrs2 on a netwinder, + and the latest uclibc from cvs. thanks for the wonderful program! + + -diligent + + note the careful description and use of examples showing not only what + busybox does, but also a counter example showing what an equivalent app + does (or pointing to the text of a relevant standard). Bug reports lacking + such detail may never be fixed... Thanks for understanding. + +---------------- + +Portability: + + Busybox is developed and tested on Linux 2.4 and 2.6 kernels, compiled + with gcc (the unit-at-a-time optimizations in version 3.4 and later are + worth upgrading to get, but older versions should work), and linked against + uClibc (0.9.27 or greater) or glibc (2.2 or greater). In such an + environment, the full set of busybox features should work, and if + anything doesn't we want to know about it so we can fix it. + + There are many other environments out there, in which busybox may build + and run just fine. We just don't test them. Since busybox consists of a + large number of more or less independent applets, portability is a question + of which features work where. Some busybox applets (such as cat and rm) are + highly portable and likely to work just about anywhere, while others (such as + insmod and losetup) require recent Linux kernels with recent C libraries. + + Earlier versions of Linux and glibc may or may not work, for any given + configuration. Linux 2.2 or earlier should mostly work (there's still + some support code in things like mount.c) but this is no longer regularly + tested, and inherently won't support certain features (such as long files + and --bind mounts). The same is true for glibc 2.0 and 2.1: expect a higher + testing and debugging burden using such old infrastructure. (The busybox + developers are not very interested in supporting these older versions, but + will probably accept small self-contained patches to fix simple problems.) + + Some environments are not recommended. Early versions of uClibc were buggy + and missing many features: upgrade. Linking against libc5 or dietlibc is + not supported and not interesting to the busybox developers. (The first is + obsolete and has no known size or feature advantages over uClibc, the second + has known bugs that its developers have actively refused to fix.) Ancient + Linux kernels (2.0.x and earlier) are similarly uninteresting. + + In theory it's possible to use Busybox under other operating systems (such as + MacOS X, Solaris, Cygwin, or the BSD Fork Du Jour). This generally involves + a different kernel and a different C library at the same time. While it + should be possible to port the majority of the code to work in one of + these environments, don't be suprised if it doesn't work out of the box. If + you're into that sort of thing, start small (selecting just a few applets) + and work your way up. + + Shaun Jackman has recently (2005) ported busybox to a combination of newlib + and libgloss, and some of his patches have been integrated. This platform + may join glibc/uclibc and Linux as a supported combination with the 1.1 + release, but is not supported in 1.0. + +Supported hardware: + + BusyBox in general will build on any architecture supported by gcc. We + support both 32 and 64 bit platforms, and both big and little endian + systems. + + Under 2.4 Linux kernels, kernel module loading was implemented in a + platform-specific manner. Busybox's insmod utility has been reported to + work under ARM, CRIS, H8/300, x86, ia64, x86_64, m68k, MIPS, PowerPC, S390, + SH3/4/5, Sparc, v850e, and x86_64. Anything else probably won't work. + + The module loading mechanism for the 2.6 kernel is much more generic, and + we believe 2.6.x kernel module loading support should work on all + architectures supported by the kernel. + +---------------- + +Please feed suggestions, bug reports, insults, and bribes back to the busybox +maintainer: + Denis Vlasenko + diff --git a/TODO b/TODO new file mode 100644 index 000000000..f0b720ba4 --- /dev/null +++ b/TODO @@ -0,0 +1,309 @@ +Busybox TODO + +Stuff that needs to be done. This is organized by who plans to get around to +doing it eventually, but that doesn't mean they "own" the item. If you want to +do one of these bounce an email off the person it's listed under to see if they +have any suggestions how they plan to go about it, and to minimize conflicts +between your work and theirs. But otherwise, all of these are fair game. + +Rob Landley : + Add BB_NOMMU to platform.h and migrate __uClinux__ tests to that. + #if defined __UCLIBC__ && !defined __ARCH_USE_MMU__ + Add a libbb/platform.c + Implement fdprintf() for platforms that haven't got one. + Implement bb_realpath() that can handle NULL on non-glibc. + Cleanup bb_asprintf() + + Migrate calloc() and bb_calloc() occurrences to bb_xzalloc(). + Remove obsolete _() wrapper crud for internationalization we don't do. + Figure out where we need utf8 support, and add it. + + sh + The command shell situation is a big mess. We have three or four different + shells that don't really share any code, and the "standalone shell" doesn't + work all that well (especially not in a chroot environment), due to apps not + being reentrant. I'm writing a new shell (bbsh) to unify the various + shells and configurably add the minimal set of bash features people + actually use. The hardest part is it has to configure down as small as + lash while providing lash's features. The rest is easy in comparison. + bzip2 + Compression-side support. + init + General cleanup (should use ENABLE_FEATURE_INIT_SYSLOG and ENABLE_FEATURE_INIT_DEBUG). + depmod + busybox lacks a way to update module deps when running from firmware without the + use of the depmod.pl (perl is to bloated for most embedded setups) and or orig + modutils. The orig depmod is rather pointless to have to add to a firmware image + in when we already have a insmod/rmmod and friends. + Unify base64 handling. + There's base64 encoding and decoding going on in: + networking/wget.c:base64enc() + coreutils/uudecode.c:read_base64() + coreutils/uuencode.c:tbl_base64[] + networking/httpd.c:decodeBase64() + And probably elsewhere. That needs to be unified into libbb functions. + Do a SUSv3 audit + Look at the full Single Unix Specification version 3 (available online at + "http://www.opengroup.org/onlinepubs/009695399/nfindex.html") and + figure out which of our apps are compliant, and what we're missing that + we might actually care about. + + Even better would be some kind of automated compliance test harness that + exercises each command line option and the various corner cases. + Internationalization + How much internationalization should we do? + + The low hanging fruit is UTF-8 character set support. We should do this. + (Vodz pointed out the shell's cmdedit as needing work here. What else?) + + We also have lots of hardwired english text messages. Consolidating this + into some kind of message table not only makes translation easier, but + also allows us to consolidate redundant (or close) strings. + + We probably don't want to be bloated with locale support. (Not unless we + can cleanly export it from our underlying C library without having to + concern ourselves with it directly. Perhaps a few specific things like a + config option for "date" are low hanging fruit here?) + + What level should things happen at? How much do we care about + internationalizing the text console when X11 and xterms are so much better + at it? (There's some infrastructure here we don't implement: The + "unicode_start" and "unicode_stop" shell scripts need "vt-is-UTF8" and a + --unicode option to loadkeys. That implies a real loadkeys/dumpkeys + implementation to replace loadkmap/dumpkmap. Plus messing with console font + loading. Is it worth it, or do we just say "use X"?) + + Individual compilation of applets. + It would be nice if busybox had the option to compile to individual applets, + for people who want an alternate implementation less bloated than the gnu + utils (or simply with less political baggage), but without it being one big + executable. + + Turning libbb into a real dll is another possibility, especially if libbb + could export some of the other library interfaces we've already more or less + got the code for (like zlib). + buildroot - Make a "dogfood" option + Busybox 1.1 will be capable of replacing most gnu packages for real world + use, such as developing software or in a live CD. It needs wider testing. + + Busybox should now be able to replace bzip2, coreutils, e2fsprogs, file, + findutils, gawk, grep, inetutils, less, modutils, net-tools, patch, procps, + sed, shadow, sysklogd, sysvinit, tar, util-linux, and vim. The resulting + system should be self-hosting (I.E. able to rebuild itself from source + code). This means it would need (at least) binutils, gcc, and make, or + equivalents. + + It would be a good "eating our own dogfood" test if buildroot had the option + of using a "make allyesconfig" busybox instead of the all of the above + packages. Anything that's wrong with the resulting system, we can fix. (It + would be nice to be able to upgrade busybox to be able to replace bash and + diffutils as well, but we're not there yet.) + + One example of an existing system that does this already is Firmware Linux: + http://www.landley.net/code/firmware + initramfs + Busybox should have a sample initramfs build script. This depends on + bbsh, mdev, and switch_root. + mkdep + Write a mkdep that doesn't segfault if there's a directory it doesn't + have permission to read, isn't based on manually editing the output of + lexx and yacc, doesn't make such a mess under include/config, etc. + Group globals into unions of structures. + Go through and turn all the global and static variables into structures, + and have all those structures be in a big union shared between processes, + so busybox uses less bss. (This is a big win on nommu machines.) See + sed.c and mdev.c for examples. + Go through bugs.busybox.net and close out all of that somehow. + This one's open to everybody, but I'll wind up doing it... + + +Bernhard Fischer suggests to look at these: + New debug options: + -Wlarger-than-127 + Cleanup any big users + -Wunused-parameter + Facilitate applet PROTOTYPES to provide means for having applets that + do a) not take any arguments b) need only one of argc or argv c) need + both argc and argv. All of these three options should go for the most + feature complete denominator. + Collate BUFSIZ IOBUF_SIZE MY_BUF_SIZE PIPE_PROGRESS_SIZE BUFSIZE PIPESIZE + make bb_common_bufsiz1 configurable, size wise. + make pipesize configurable, size wise. + Use bb_common_bufsiz1 throughout applets! + Add chrt applet. Please CC Bernhard if you suggest a patch. + +As yet unclaimed: + +---- +find + doesn't understand (), lots of susv3 stuff. +---- +diff + Make sure we handle empty files properly: + From the patch man page: + + you can remove a file by sending out a context diff that compares + the file to be deleted with an empty file dated the Epoch. The + file will be removed unless patch is conforming to POSIX and the + -E or --remove-empty-files option is not given. +--- +patch + Should have simple fuzz factor support to apply patches at an offset which + shouldn't take up too much space. + + And while we're at it, a new patch filename quoting format is apparently + coming soon: http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 +--- +man + It would be nice to have a man command. Not one that handles troff or + anything, just one that can handle preformatted ascii man pages, possibly + compressed. This could probably be a script in the extras directory that + calls cat/zcat/bzcat | less + + (How doclifter might work into this is anybody's guess.) +--- +ar + Write support? +--- + +Architectural issues: + +bb_close() with fsync() + We should have a bb_close() in place of normal close, with a CONFIG_ option + to not just check the return value of close() for an error, but fsync(). + Close can't reliably report anything useful because if write() accepted the + data then it either went out to the network or it's in cache or a pipe + buffer. Either way, there's no guarantee it'll make it to its final + destination before close() gets called, so there's no guarantee that any + error will be reported. + + You need to call fsync() if you care about errors that occur after write(), + but that can have a big performance impact. So make it a config option. +--- +Unify archivers + Lots of archivers have the same general infrastructure. The directory + traversal code should be factored out, and the guts of each archiver could + be some setup code and a series of callbacks for "add this file", + "add this directory", "add this symlink" and so on. + + This could clean up tar and zip, and make it cheaper to add cpio and ar + write support, and possibly even cheaply add things like mkisofs or + mksquashfs someday, if they become relevant. +--- +Text buffer support. + Several existing applets (sort, vi, less...) read + a whole file into memory and act on it. There might be an opportunity + for shared code in there that could be moved into libbb... +--- +Memory Allocation + We have a CONFIG_BUFFER mechanism that lets us select whether to do memory + allocation on the stack or the heap. Unfortunately, we're not using it much. + We need to audit our memory allocations and turn a lot of malloc/free calls + into RESERVE_CONFIG_BUFFER/RELEASE_CONFIG_BUFFER. + For a start, see e.g. make EXTRA_CFLAGS=-Wlarger-than-64 + + And while we're at it, many of the CONFIG_FEATURE_CLEAN_UP #ifdefs will be + optimized out by the compiler in the stack allocation case (since there's no + free for an alloca()), and this means that various cleanup loops that just + call free might also be optimized out by the compiler if written right, so + we can yank those #ifdefs too, and generally clean up the code. +--- +Switch CONFIG_SYMBOLS to ENABLE_SYMBOLS + + In busybox 1.0 and earlier, configuration was done by CONFIG_SYMBOLS + that were either defined or undefined to indicate whether the symbol was + selected in the .config file. They were used with #ifdefs, ala: + + #ifdef CONFIG_SYMBOL + if (other_test) { + do_code(); + } + #endif + + In 1.1, we have new ENABLE_SYMBOLS which are always defined (as 0 or 1), + meaning you can still use them for preprocessor tests by replacing + "#ifdef CONFIG_SYMBOL" with "#if ENABLE_SYMBOL". But more importantly, we + can use them as a true or false test in normal C code: + + if (ENABLE_SYMBOL && other_test) { + do_code(); + } + + (Optimizing away if() statements that resolve to a constant value + is known as "dead code elimination", an optimization so old and simple that + Turbo Pascal for DOS did it twenty years ago. Even modern mini-compilers + like the Tiny C Compiler (tcc) and the Small Device C Compiler (SDCC) + perform dead code elimination.) + + Right now, busybox.h is #including both "config.h" (defining the + CONFIG_SYMBOLS) and "bb_config.h" (defining the ENABLE_SYMBOLS). At some + point in the future, it would be nice to wean ourselves off of the + CONFIG versions. (Among other things, some defective build environments + leak the Linux kernel's CONFIG_SYMBOLS into the system's standard #include + files. We've experienced collisions before.) +--- +FEATURE_CLEAN_UP + This is more an unresolved issue than a to-do item. More thought is needed. + + Normally we rely on exit() to free memory, close files, and unmap segments + for us. This makes most calls to free(), close(), and unmap() optional in + busybox applets that don't intend to run for very long, and optional stuff + can be omitted to save size. + + The idea was raised that we could simulate fork/exit with setjmp/longjmp + for _really_ brainless embedded systems, or speed up the standalone shell + by not forking. Doing so would require a reliable FEATURE_CLEAN_UP. + Unfortunately, this isn't as easy as it sounds. + + The problem is, lots of things exit(), sometimes unexpectedly (xmalloc()) + and sometimes reliably (bb_perror_msg_and_die() or show_usage()). This + jumps out of the normal flow control and bypasses any cleanup code we + put at the end of our applets. + + It's possible to add hooks to libbb functions like xmalloc() and xopen() + to add their entries to a linked list, which could be traversed and + freed/closed automatically. (This would need to be able to free just the + entries after a checkpoint to be usable for a forkless standalone shell. + You don't want to free the shell's own resources.) + + Right now, FEATURE_CLEAN_UP is more or less a debugging aid, to make things + like valgrind happy. It's also documentation of _what_ we're trusting + exit() to clean up for us. But new infrastructure to auto-free stuff would + render the existing FEATURE_CLEAN_UP code redundant. + + For right now, exit() handles it just fine. + + + +Minor stuff: + watchdog.c could autodetect the timer duration via: + if(!ioctl (fd, WDIOC_GETTIMEOUT, &tmo)) timer_duration = 1 + (tmo / 2); + Unfortunately, that needs linux/watchdog.h and that contains unfiltered + kernel types on some distros, which breaks the build. +--- + use bb_error_msg where appropriate: See + egrep "(printf.*\([[:space:]]*(stderr|2)|[^_]write.*\([[:space:]]*(stderr|2))" +--- + use bb_perror_msg where appropriate: See + egrep "[^_]perror" +--- + Remove superfluous fmt occurances: e.g. + fprintf(stderr, "%s: %s not found\n", "unalias", *argptr); + -> fprintf(stderr, "unalias: %s not found\n", *argptr); +--- + possible code duplication ingroup() and is_a_group_member() +--- + Move __get_hz() to a better place and (re)use it in route.c, ash.c, msh.c +--- + + +Code cleanup: + +Replace deprecated functions. + +bzero() -> memset() +--- +sigblock(), siggetmask(), sigsetmask(), sigmask() -> sigprocmask et al +--- +vdprintf() -> similar sized functionality +--- diff --git a/applets/Kbuild b/applets/Kbuild new file mode 100644 index 000000000..c95c09d97 --- /dev/null +++ b/applets/Kbuild @@ -0,0 +1,21 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +obj-y:= +obj-y += applets.o +obj-y += busybox.o + +# Generated file needs additional love + +applets/applets.o: include/usage_compressed.h + +hostprogs-y += usage +always := $(hostprogs-y) +HOSTCFLAGS_usage.o = -I$(srctree)/include + +include/usage_compressed.h: $(srctree)/include/usage.h applets/usage + @echo ' GEN include/usage_compressed.h' + @$(srctree)/applets/usage_compressed include/usage_compressed.h applets diff --git a/applets/applets.c b/applets/applets.c new file mode 100644 index 000000000..23f9e4f92 --- /dev/null +++ b/applets/applets.c @@ -0,0 +1,483 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) tons of folks. Tracking down who wrote what + * isn't something I'm going to worry about... If you wrote something + * here, please feel free to acknowledge your work. + * + * Based in part on code from sash, Copyright (c) 1999 by David I. Bell + * Permission has been granted to redistribute this code under the GPL. + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include + +/* Apparently uclibc defines __GLIBC__ (compat trick?). Oh well. */ +#if ENABLE_STATIC && defined(__GLIBC__) && !defined(__UCLIBC__) +#warning Static linking against glibc produces buggy executables +#warning (glibc does not cope well with ld --gc-sections). +#warning See sources.redhat.com/bugzilla/show_bug.cgi?id=3400 +#warning Note that glibc is utterly unsuitable for static linking anyway. +#endif + +#if ENABLE_SHOW_USAGE && !ENABLE_FEATURE_COMPRESS_USAGE +static const char usage_messages[] = +#define MAKE_USAGE +#include "usage.h" +#include "applets.h" +; +#undef MAKE_USAGE +#else +#define usage_messages 0 +#endif /* ENABLE_SHOW_USAGE */ + +#undef APPLET +#undef APPLET_NOUSAGE +#undef PROTOTYPES +#include "applets.h" + +static struct BB_applet *applet_using; + +/* The -1 arises because of the {0,NULL,0,-1} entry above. */ +const size_t NUM_APPLETS = (sizeof (applets) / sizeof (struct BB_applet) - 1); + + +#ifdef CONFIG_FEATURE_SUID_CONFIG + +#include + +#define CONFIG_FILE "/etc/busybox.conf" + +/* applets [] is const, so we have to define this "override" structure */ +static struct BB_suid_config +{ + struct BB_applet *m_applet; + + uid_t m_uid; + gid_t m_gid; + mode_t m_mode; + + struct BB_suid_config *m_next; +} *suid_config; + +static int suid_cfg_readable; + +/* check if u is member of group g */ +static int ingroup(uid_t u, gid_t g) +{ + struct group *grp = getgrgid(g); + + if (grp) { + char **mem; + + for (mem = grp->gr_mem; *mem; mem++) { + struct passwd *pwd = getpwnam(*mem); + + if (pwd && (pwd->pw_uid == u)) + return 1; + } + } + return 0; +} + +/* This should probably be a libbb routine. In that case, + * I'd probably rename it to something like bb_trimmed_slice. + */ +static char *get_trimmed_slice(char *s, char *e) +{ + /* First, consider the value at e to be nul and back up until we + * reach a non-space char. Set the char after that (possibly at + * the original e) to nul. */ + while (e-- > s) { + if (!isspace(*e)) { + break; + } + } + e[1] = 0; + + /* Next, advance past all leading space and return a ptr to the + * first non-space char; possibly the terminating nul. */ + return skip_whitespace(s); +} + + +#define parse_error(x) { err=x; goto pe_label; } + +/* Don't depend on the tools to combine strings. */ +static const char config_file[] = CONFIG_FILE; + +/* There are 4 chars + 1 nul for each of user/group/other. */ +static const char mode_chars[] = "Ssx-\0Ssx-\0Ttx-"; + +/* We don't supply a value for the nul, so an index adjustment is + * necessary below. Also, we use unsigned short here to save some + * space even though these are really mode_t values. */ +static const unsigned short mode_mask[] = { + /* SST sst xxx --- */ + S_ISUID, S_ISUID|S_IXUSR, S_IXUSR, 0, /* user */ + S_ISGID, S_ISGID|S_IXGRP, S_IXGRP, 0, /* group */ + 0, S_IXOTH, S_IXOTH, 0 /* other */ +}; + +static void parse_config_file(void) +{ + struct BB_suid_config *sct_head; + struct BB_suid_config *sct; + struct BB_applet *applet; + FILE *f; + char *err; + char *s; + char *e; + int i, lc, section; + char buffer[256]; + struct stat st; + + assert(!suid_config); /* Should be set to NULL by bss init. */ + + if ((stat(config_file, &st) != 0) /* No config file? */ + || !S_ISREG(st.st_mode) /* Not a regular file? */ + || (st.st_uid != 0) /* Not owned by root? */ + || (st.st_mode & (S_IWGRP | S_IWOTH)) /* Writable by non-root? */ + || !(f = fopen(config_file, "r")) /* Cannot open? */ + ) { + return; + } + + suid_cfg_readable = 1; + sct_head = NULL; + section = lc = 0; + + do { + s = buffer; + + if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */ + if (ferror(f)) { /* Make sure it wasn't a read error. */ + parse_error("reading"); + } + fclose(f); + suid_config = sct_head; /* Success, so set the pointer. */ + return; + } + + lc++; /* Got a (partial) line. */ + + /* If a line is too long for our buffer, we consider it an error. + * The following test does mistreat one corner case though. + * If the final line of the file does not end with a newline and + * yet exactly fills the buffer, it will be treated as too long + * even though there isn't really a problem. But it isn't really + * worth adding code to deal with such an unlikely situation, and + * we do err on the side of caution. Besides, the line would be + * too long if it did end with a newline. */ + if (!strchr(s, '\n') && !feof(f)) { + parse_error("line too long"); + } + + /* Trim leading and trailing whitespace, ignoring comments, and + * check if the resulting string is empty. */ + if (!*(s = get_trimmed_slice(s, strchrnul(s, '#')))) { + continue; + } + + /* Check for a section header. */ + + if (*s == '[') { + /* Unlike the old code, we ignore leading and trailing + * whitespace for the section name. We also require that + * there are no stray characters after the closing bracket. */ + if (!(e = strchr(s, ']')) /* Missing right bracket? */ + || e[1] /* Trailing characters? */ + || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */ + ) { + parse_error("section header"); + } + /* Right now we only have one section so just check it. + * If more sections are added in the future, please don't + * resort to cascading ifs with multiple strcasecmp calls. + * That kind of bloated code is all too common. A loop + * and a string table would be a better choice unless the + * number of sections is very small. */ + if (strcasecmp(s, "SUID") == 0) { + section = 1; + continue; + } + section = -1; /* Unknown section so set to skip. */ + continue; + } + + /* Process sections. */ + + if (section == 1) { /* SUID */ + /* Since we trimmed leading and trailing space above, we're + * now looking for strings of the form + * [::space::]*=[::space::]* + * where both key and value could contain inner whitespace. */ + + /* First get the key (an applet name in our case). */ + if (!!(e = strchr(s, '='))) { + s = get_trimmed_slice(s, e); + } + if (!e || !*s) { /* Missing '=' or empty key. */ + parse_error("keyword"); + } + + /* Ok, we have an applet name. Process the rhs if this + * applet is currently built in and ignore it otherwise. + * Note: This can hide config file bugs which only pop + * up when the busybox configuration is changed. */ + if ((applet = find_applet_by_name(s))) { + /* Note: We currently don't check for duplicates! + * The last config line for each applet will be the + * one used since we insert at the head of the list. + * I suppose this could be considered a feature. */ + sct = xmalloc(sizeof(struct BB_suid_config)); + sct->m_applet = applet; + sct->m_mode = 0; + sct->m_next = sct_head; + sct_head = sct; + + /* Get the specified mode. */ + + e = skip_whitespace(e+1); + + for (i=0 ; i < 3 ; i++) { + const char *q; + if (!*(q = strchrnul(mode_chars + 5*i, *e++))) { + parse_error("mode"); + } + /* Adjust by -i to account for nul. */ + sct->m_mode |= mode_mask[(q - mode_chars) - i]; + } + + /* Now get the the user/group info. */ + + s = skip_whitespace(e); + + /* Note: We require whitespace between the mode and the + * user/group info. */ + if ((s == e) || !(e = strchr(s, '.'))) { + parse_error("."); + } + *e++ = 0; + + /* We can't use get_ug_id here since it would exit() + * if a uid or gid was not found. Oh well... */ + { + char *e2; + + sct->m_uid = strtoul(s, &e2, 10); + if (*e2 || (s == e2)) { + struct passwd *pwd = getpwnam(s); + if (!pwd) { + parse_error("user"); + } + sct->m_uid = pwd->pw_uid; + } + + sct->m_gid = strtoul(e, &e2, 10); + if (*e2 || (e == e2)) { + struct group *grp; + if (!(grp = getgrnam(e))) { + parse_error("group"); + } + sct->m_gid = grp->gr_gid; + } + } + } + continue; + } + + /* Unknown sections are ignored. */ + + /* Encountering configuration lines prior to seeing a + * section header is treated as an error. This is how + * the old code worked, but it may not be desirable. + * We may want to simply ignore such lines in case they + * are used in some future version of busybox. */ + if (!section) { + parse_error("keyword outside section"); + } + + } while (1); + + pe_label: + fprintf(stderr, "Parse error in %s, line %d: %s\n", + config_file, lc, err); + + fclose(f); + /* Release any allocated memory before returning. */ + while (sct_head) { + sct = sct_head->m_next; + free(sct_head); + sct_head = sct; + } + return; +} + +#else +#define parse_config_file() +#endif /* CONFIG_FEATURE_SUID_CONFIG */ + +#ifdef CONFIG_FEATURE_SUID +static void check_suid(struct BB_applet *applet) +{ + uid_t ruid = getuid(); /* real [ug]id */ + uid_t rgid = getgid(); + +#ifdef CONFIG_FEATURE_SUID_CONFIG + if (suid_cfg_readable) { + struct BB_suid_config *sct; + + for (sct = suid_config; sct; sct = sct->m_next) { + if (sct->m_applet == applet) + break; + } + if (sct) { + mode_t m = sct->m_mode; + + if (sct->m_uid == ruid) /* same uid */ + m >>= 6; + else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid)) /* same group / in group */ + m >>= 3; + + if (!(m & S_IXOTH)) /* is x bit not set ? */ + bb_error_msg_and_die("you have no permission to run this applet!"); + + if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { /* *both* have to be set for sgid */ + xsetgid(sct->m_gid); + } else xsetgid(rgid); /* no sgid -> drop */ + + if (sct->m_mode & S_ISUID) xsetuid(sct->m_uid); + else xsetuid(ruid); /* no suid -> drop */ + } else { + /* default: drop all privileges */ + xsetgid(rgid); + xsetuid(ruid); + } + return; + } else { +#ifndef CONFIG_FEATURE_SUID_CONFIG_QUIET + static int onetime = 0; + + if (!onetime) { + onetime = 1; + fprintf(stderr, "Using fallback suid method\n"); + } +#endif + } +#endif + + if (applet->need_suid == _BB_SUID_ALWAYS) { + if (geteuid()) bb_error_msg_and_die("applet requires root privileges!"); + } else if (applet->need_suid == _BB_SUID_NEVER) { + xsetgid(rgid); /* drop all privileges */ + xsetuid(ruid); + } +} +#else +#define check_suid(x) +#endif /* CONFIG_FEATURE_SUID */ + + + +#ifdef CONFIG_FEATURE_COMPRESS_USAGE + +#include "usage_compressed.h" +#include "unarchive.h" + +static const char *unpack_usage_messages(void) +{ + int input[2], output[2], pid; + char *buf; + + if(pipe(input) < 0 || pipe(output) < 0) + exit(1); + + pid = fork(); + switch (pid) { + case -1: /* error */ + exit(1); + case 0: /* child */ + close(input[1]); + close(output[0]); + uncompressStream(input[0], output[1]); + exit(0); + } + /* parent */ + + close(input[0]); + close(output[1]); + pid = fork(); + switch (pid) { + case -1: /* error */ + exit(1); + case 0: /* child */ + full_write(input[1], packed_usage, sizeof(packed_usage)); + exit(0); + } + /* parent */ + close(input[1]); + + buf = xmalloc(SIZEOF_usage_messages); + full_read(output[0], buf, SIZEOF_usage_messages); + return buf; +} + +#else +#define unpack_usage_messages() usage_messages +#endif /* ENABLE_FEATURE_COMPRESS_USAGE */ + +void bb_show_usage(void) +{ + if (ENABLE_SHOW_USAGE) { + const char *format_string; + const char *usage_string = unpack_usage_messages(); + int i; + + for (i = applet_using - applets; i > 0;) + if (!*usage_string++) --i; + + format_string = "%s\n\nUsage: %s %s\n\n"; + if (*usage_string == '\b') + format_string = "%s\n\nNo help available.\n\n"; + fprintf(stderr, format_string, bb_msg_full_version, + applet_using->name, usage_string); + } + + exit(xfunc_error_retval); +} + +static int applet_name_compare(const void *name, const void *vapplet) +{ + const struct BB_applet *applet = vapplet; + + return strcmp(name, applet->name); +} + +extern const size_t NUM_APPLETS; + +struct BB_applet *find_applet_by_name(const char *name) +{ + return bsearch(name, applets, NUM_APPLETS, sizeof(struct BB_applet), + applet_name_compare); +} + +void run_applet_by_name(const char *name, int argc, char **argv) +{ + if (ENABLE_FEATURE_SUID_CONFIG) parse_config_file(); + + if (!strncmp(name, "busybox", 7)) busybox_main(argc, argv); + /* Do a binary search to find the applet entry given the name. */ + applet_using = find_applet_by_name(name); + if (applet_using) { + applet_name = applet_using->name; + if(argc==2 && !strcmp(argv[1], "--help")) bb_show_usage(); + if(ENABLE_FEATURE_SUID) check_suid(applet_using); + exit((*(applet_using->main))(argc, argv)); + } +} diff --git a/applets/busybox.c b/applets/busybox.c new file mode 100644 index 000000000..bb9eb3af7 --- /dev/null +++ b/applets/busybox.c @@ -0,0 +1,148 @@ +/* vi: set sw=4 ts=4: */ +/* + * BusyBox' main applet dispatcher. + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ +#include "busybox.h" + +const char *applet_name ATTRIBUTE_EXTERNALLY_VISIBLE; + +#ifdef CONFIG_FEATURE_INSTALLER +/* + * directory table + * this should be consistent w/ the enum, busybox.h::Location, + * or else... + */ +static const char usr_bin [] ="/usr/bin"; +static const char usr_sbin[] ="/usr/sbin"; + +static const char* const install_dir[] = { + &usr_bin [8], /* "", equivalent to "/" for concat_path_file() */ + &usr_bin [4], /* "/bin" */ + &usr_sbin[4], /* "/sbin" */ + usr_bin, + usr_sbin +}; + +/* abstract link() */ +typedef int (*__link_f)(const char *, const char *); + +/* create (sym)links for each applet */ +static void install_links(const char *busybox, int use_symbolic_links) +{ + __link_f Link = link; + + char *fpc; + int i; + int rc; + + if (use_symbolic_links) + Link = symlink; + + for (i = 0; applets[i].name != NULL; i++) { + fpc = concat_path_file( + install_dir[applets[i].location], applets[i].name); + rc = Link(busybox, fpc); + if (rc!=0 && errno!=EEXIST) { + bb_perror_msg("%s", fpc); + } + free(fpc); + } +} + +#else +#define install_links(x,y) +#endif /* CONFIG_FEATURE_INSTALLER */ + +int main(int argc, char **argv) +{ + const char *s; + + applet_name=argv[0]; + if (*applet_name == '-') applet_name++; + for (s = applet_name; *s ;) + if (*(s++) == '/') applet_name = s; + + /* Set locale for everybody except 'init' */ + if (ENABLE_LOCALE_SUPPORT && getpid() != 1) + setlocale(LC_ALL, ""); + + run_applet_by_name(applet_name, argc, argv); + bb_error_msg_and_die("applet not found"); +} + +int busybox_main(int argc, char **argv) +{ + /* + * This style of argument parsing doesn't scale well + * in the event that busybox starts wanting more --options. + * If someone has a cleaner approach, by all means implement it. + */ + if (ENABLE_FEATURE_INSTALLER && argc > 1 && !strcmp(argv[1], "--install")) { + int use_symbolic_links = 0; + int rc = 0; + char *busybox; + + /* to use symlinks, or not to use symlinks... */ + if (argc > 2) { + if ((strcmp(argv[2], "-s") == 0)) { + use_symbolic_links = 1; + } + } + + /* link */ +// XXX: FIXME: this is broken. Why not just use argv[0] ? + busybox = xreadlink("/proc/self/exe"); + if (busybox) { + install_links(busybox, use_symbolic_links); + free(busybox); + } else { + rc = 1; + } + return rc; + } + + /* Deal with --help. (Also print help when called with no arguments) */ + + if (argc==1 || !strcmp(argv[1],"--help") ) { + if (argc>2) { + applet_name = argv[2]; + run_applet_by_name(applet_name, 2, argv); + } else { + const struct BB_applet *a; + int col, output_width; + + if (ENABLE_FEATURE_AUTOWIDTH) { + /* Obtain the terminal width. */ + get_terminal_width_height(0, &output_width, NULL); + /* leading tab and room to wrap */ + output_width -= sizeof("start-stop-daemon, ") + 8; + } else output_width = 80 - sizeof("start-stop-daemon, ") - 8; + + printf("%s\n" + "Copyright (C) 1998-2006  Erik Andersen, Rob Landley, and others.\n" + "Licensed under GPLv2.  See source distribution for full notice.\n\n" + "Usage: busybox [function] [arguments]...\n" + " or: [function] [arguments]...\n\n" + "\tBusyBox is a multi-call binary that combines many common Unix\n" + "\tutilities into a single executable. Most people will create a\n" + "\tlink to busybox for each function they wish to use and BusyBox\n" + "\twill act like whatever it was invoked as!\n" + "\nCurrently defined functions:\n", bb_msg_full_version); + + col=0; + for(a = applets; a->name;) { + col += printf("%s%s", (col ? ", " : "\t"), (a++)->name); + if (col > output_width && a->name) { + printf(",\n"); + col = 0; + } + } + printf("\n\n"); + exit(0); + } + } else run_applet_by_name(argv[1], argc-1, argv+1); + + bb_error_msg_and_die("applet not found"); +} diff --git a/applets/busybox.mkll b/applets/busybox.mkll new file mode 100755 index 000000000..6d61f7e82 --- /dev/null +++ b/applets/busybox.mkll @@ -0,0 +1,24 @@ +#!/bin/sh +# Make busybox links list file. + +# input $1: full path to Config.h +# input $2: full path to applets.h +# output (stdout): list of pathnames that should be linked to busybox + +# Maintainer: Larry Doolittle + +export LC_ALL=POSIX +export LC_CTYPE=POSIX + +CONFIG_H=${1:-include/autoconf.h} +APPLETS_H=${2:-include/applets.h} +$HOSTCC -E -DMAKE_LINKS -include $CONFIG_H $APPLETS_H | + awk '/^[ \t]*LINK/{ + dir=substr($2,8) + gsub("_","/",dir) + if(dir=="/ROOT") dir="" + file=$3 + gsub("\"","",file) + if (file=="busybox") next + print tolower(dir) "/" file + }' diff --git a/applets/individual.c b/applets/individual.c new file mode 100644 index 000000000..072168352 --- /dev/null +++ b/applets/individual.c @@ -0,0 +1,27 @@ +/* Minimal wrapper to build an individual busybox applet. + * + * Copyright 2005 Rob Landley +#include +//Ok to remove? #include "bb_config.h" +#include "usage.h" + +int main(int argc, char *argv[]) +{ + applet_name=argv[0]; + + return APPLET_main(argc,argv); +} + +void bb_show_usage(void) +{ + printf(APPLET_full_usage "\n"); + + exit(1); +} diff --git a/applets/install.sh b/applets/install.sh new file mode 100755 index 000000000..4ec96c254 --- /dev/null +++ b/applets/install.sh @@ -0,0 +1,94 @@ +#!/bin/sh + +export LC_ALL=POSIX +export LC_CTYPE=POSIX + +prefix=${1} +if [ -z "$prefix" ]; then + echo "usage: applets/install.sh DESTINATION [--symlinks/--hardlinks]" + exit 1; +fi +h=`sort busybox.links | uniq` +cleanup="0" +noclobber="0" +case "$2" in + --hardlinks) linkopts="-f";; + --symlinks) linkopts="-fs";; + --cleanup) cleanup="1";; + --noclobber) noclobber="1";; + "") h="";; + *) echo "Unknown install option: $2"; exit 1;; +esac + +if [ -n "$DO_INSTALL_LIBS" ] && [ "$DO_INSTALL_LIBS" != "n" ]; then + # get the target dir for the libs + # assume it starts with lib + libdir=$($CC -print-file-name=libc.so | \ + sed -n 's%^.*\(/lib[^\/]*\)/libc.so%\1%p') + if test -z "$libdir"; then + libdir=/lib + fi + + mkdir -p $prefix/$libdir || exit 1 + for i in $DO_INSTALL_LIBS; do + rm -f $prefix/$libdir/$i || exit 1 + if [ -f $i ]; then + cp -a $i $prefix/$libdir/ || exit 1 + chmod 0644 $prefix/$libdir/$i || exit 1 + fi + done +fi + +if [ "$cleanup" = "1" ] && [ -e "$prefix/bin/busybox" ]; then + inode=`ls -i "$prefix/bin/busybox" | awk '{print $1}'` + sub_shell_it=` + cd "$prefix" + for d in usr/sbin usr/bin sbin bin ; do + pd=$PWD + if [ -d "$d" ]; then + cd $d + ls -iL . | grep "^ *$inode" | awk '{print $2}' | env -i xargs rm -f + fi + cd "$pd" + done + ` +fi + +rm -f $prefix/bin/busybox || exit 1 +mkdir -p $prefix/bin || exit 1 +install -m 755 busybox $prefix/bin/busybox || exit 1 + +for i in $h ; do + appdir=`dirname $i` + mkdir -p $prefix/$appdir || exit 1 + if [ "$2" = "--hardlinks" ]; then + bb_path="$prefix/bin/busybox" + else + case "$appdir" in + /) + bb_path="bin/busybox" + ;; + /bin) + bb_path="busybox" + ;; + /sbin) + bb_path="../bin/busybox" + ;; + /usr/bin|/usr/sbin) + bb_path="../../bin/busybox" + ;; + *) + echo "Unknown installation directory: $appdir" + exit 1 + ;; + esac + fi + if [ "$noclobber" = "0" ] || [ ! -e "$prefix$i" ]; then + echo " $prefix$i -> $bb_path" + ln $linkopts $bb_path $prefix$i || exit 1 + else + echo " $prefix$i already exists" + fi +done + +exit 0 diff --git a/applets/usage.c b/applets/usage.c new file mode 100644 index 000000000..d888fa93d --- /dev/null +++ b/applets/usage.c @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +#include + +#include "../include/autoconf.h" +#include "../include/busybox.h" + +static const char usage_messages[] = +#define MAKE_USAGE +#include "usage.h" +#include "applets.h" +; + +int main(void) +{ + write(1, usage_messages, sizeof(usage_messages)); + return 0; +} diff --git a/applets/usage_compressed b/applets/usage_compressed new file mode 100755 index 000000000..ab164aa12 --- /dev/null +++ b/applets/usage_compressed @@ -0,0 +1,19 @@ +#!/bin/sh + +target="$1" +loc="$2" + +test "$target" || exit 1 +test "$loc" || loc=. +test -x "$loc/usage" || exit 1 +test "$SED" || SED=sed + +sz=`"$loc/usage" | wc -c` || exit 1 + +exec >"$target" + +echo 'static const char packed_usage[] = ' +"$loc/usage" | bzip2 -1 | od -v -t x1 \ +| $SED -e 's/^[^ ]*//' -e 's/ \(..\)/\\x\1/g' -e 's/^\(.*\)$/"\1"/' +echo ';' +echo '#define SIZEOF_usage_messages' `expr 0 + $sz` diff --git a/arch/i386/Makefile b/arch/i386/Makefile new file mode 100644 index 000000000..595868ec5 --- /dev/null +++ b/arch/i386/Makefile @@ -0,0 +1,5 @@ +# ========================================================================== +# Build system +# ========================================================================== + +CFLAGS += -march=i386 -mpreferred-stack-boundary=2 diff --git a/archival/Config.in b/archival/Config.in new file mode 100644 index 000000000..de493efa2 --- /dev/null +++ b/archival/Config.in @@ -0,0 +1,300 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Archival Utilities" + +config AR + bool "ar" + default n + help + ar is an archival utility program used to create, modify, and + extract contents from archives. An archive is a single file holding + a collection of other files in a structure that makes it possible to + retrieve the original individual files (called archive members). + The original files' contents, mode (permissions), timestamp, owner, + and group are preserved in the archive, and can be restored on + extraction. + + The stored filename is limited to 15 characters. (for more information + see long filename support). + ar has 60 bytes of overheads for every stored file. + + This implementation of ar can extract archives, it cannot create or + modify them. + On an x86 system, the ar applet adds about 1K. + + Unless you have a specific application which requires ar, you should + probably say N here. + +config FEATURE_AR_LONG_FILENAMES + bool "Enable support for long filenames (not need for debs)" + default n + depends on AR + help + By default the ar format can only store the first 15 characters of the + filename, this option removes that limitation. + It supports the GNU ar long filename method which moves multiple long + filenames into a the data section of a new ar entry. + +config BUNZIP2 + bool "bunzip2" + default n + help + bunzip2 is a compression utility using the Burrows-Wheeler block + sorting text compression algorithm, and Huffman coding. Compression + is generally considerably better than that achieved by more + conventional LZ77/LZ78-based compressors, and approaches the + performance of the PPM family of statistical compressors. + + The BusyBox bunzip2 applet is limited to de-compression only. + On an x86 system, this applet adds about 11K. + + Unless you have a specific application which requires bunzip2, you + should probably say N here. + +config CPIO + bool "cpio" + default n + help + cpio is an archival utility program used to create, modify, and extract + contents from archives. + cpio has 110 bytes of overheads for every stored file. + + This implementation of cpio can extract cpio archives created in the + "newc" or "crc" format, it cannot create or modify them. + + Unless you have a specific application which requires cpio, you should + probably say N here. + +config DPKG + bool "dpkg" + default n + help + dpkg is a medium-level tool to install, build, remove and manage Debian packages. + + This implementation of dpkg has a number of limitations, you should use the + official dpkg if possible. + +config DPKG_DEB + bool "dpkg_deb" + default n + help + dpkg-deb packs, unpacks and provides information about Debian archives. + + This implementation of dpkg-deb cannot pack archives. + + Unless you have a specific application which requires dpkg-deb, you should + probably say N here. + +config FEATURE_DPKG_DEB_EXTRACT_ONLY + bool "extract only (-x)" + default n + depends on DPKG_DEB + help + This reduces dpkg-deb to the equivalent of "ar -p data.tar.gz | tar -zx". + However it saves space as none of the extra dpkg-deb, ar or tar options are + needed, they are linked to internally. + +config GUNZIP + bool "gunzip" + default n + help + gunzip is used to decompress archives created by gzip. + You can use the `-t' option to test the integrity of + an archive, without decompressing it. + +config FEATURE_GUNZIP_UNCOMPRESS + bool "Uncompress support" + default n + depends on GUNZIP + help + Enable if you want gunzip to have the ability to decompress + archives created by the program compress (not much + used anymore). + +config GZIP + bool "gzip" + default n + help + gzip is used to compress files. + It's probably the most widely used UNIX compression program. + +config RPM2CPIO + bool "rpm2cpio" + default n + help + Converts an RPM file into a CPIO archive. + +config RPM + bool "rpm" + default n + help + Mini RPM applet - queries and extracts + +config TAR + bool "tar" + default n + help + tar is an archiving program. It's commonly used with gzip to + create compressed archives. It's probably the most widely used + UNIX archive program. + +config FEATURE_TAR_CREATE + bool "Enable archive creation" + default y + depends on TAR + help + If you enable this option you'll be able to create + tar archives using the `-c' option. + +config FEATURE_TAR_BZIP2 + bool "Enable -j option to handle .tar.bz2 files" + default n + depends on TAR + help + If you enable this option you'll be able to extract + archives compressed with bzip2. + +config FEATURE_TAR_LZMA + bool "Enable -a option to handle .tar.lzma files" + default n + depends on TAR + help + If you enable this option you'll be able to extract + archives compressed with lzma. + +config FEATURE_TAR_FROM + bool "Enable -X (exclude from) and -T (include from) options)" + default n + depends on TAR + help + If you enable this option you'll be able to specify + a list of files to include or exclude from an archive. + +config FEATURE_TAR_GZIP + bool "Enable -z option" + default y + depends on TAR + help + If you enable this option tar will be able to call gzip, + when creating or extracting tar gziped archives. + +config FEATURE_TAR_COMPRESS + bool "Enable -Z option" + default n + depends on TAR + help + If you enable this option tar will be able to call uncompress, + when extracting .tar.Z archives. + +config FEATURE_TAR_OLDGNU_COMPATIBILITY + bool "Enable support for old tar header format" + default N + depends on TAR + help + This option is required to unpack archives created in + the old GNU format; help to kill this old format by + repacking your ancient archives with the new format. + +config FEATURE_TAR_GNU_EXTENSIONS + bool "Enable support for some GNU tar extensions" + default y + depends on TAR + help + With this option busybox supports GNU long filenames and + linknames. + +config FEATURE_TAR_LONG_OPTIONS + bool "Enable long options" + default n + depends on TAR && GETOPT_LONG + help + Enable use of long options, increases size by about 400 Bytes + +config UNCOMPRESS + bool "uncompress" + default n + help + uncompress is used to decompress archives created by compress. + Not much used anymore, replaced by gzip/gunzip. + +config UNLZMA + bool "unlzma" + default n + help + unlzma is a compression utility using the Lempel-Ziv-Markov chain + compression algorithm, and range coding. Compression + is generally considerably better than that achieved by the bzip2 + compressors. + + The BusyBox unlzma applet is limited to de-compression only. + On an x86 system, this applet adds about 4K. + + Unless you have a specific application which requires unlzma, you + should probably say N here. + +config FEATURE_LZMA_FAST + bool "Optimze unlzma for speed" + default n + depends on UNLZMA + help + This option reduces decompression time by about 33% at the cost of + a 2K bigger binary. + +config UNZIP + bool "unzip" + default n + help + unzip will list or extract files from a ZIP archive, + commonly found on DOS/WIN systems. The default behavior + (with no options) is to extract the archive into the + current directory. Use the `-d' option to extract to a + directory of your choice. + +comment "Common options for cpio and tar" + depends on CPIO || TAR + +config FEATURE_UNARCHIVE_TAPE + bool "Enable tape drive support" + default n + depends on CPIO || TAR + help + I don't think this is needed anymore. + +comment "Common options for dpkg and dpkg_deb" + depends on DPKG || DPKG_DEB + +config FEATURE_DEB_TAR_GZ + bool "gzip debian packages (normal)" + default y if DPKG || DPKG_DEB + depends on DPKG || DPKG_DEB + help + This is the default compression method inside the debian ar file. + + If you want compatibility with standard .deb's you should say yes here. + +config FEATURE_DEB_TAR_BZ2 + bool "bzip2 debian packages" + default n + depends on DPKG || DPKG_DEB + help + This allows dpkg and dpkg-deb to extract deb's that are compressed internally + with bzip2 instead of gzip. + + You only want this if you are creating your own custom debian packages that + use an internal control.tar.bz2 or data.tar.bz2. + +config FEATURE_DEB_TAR_LZMA + bool "lzma debian packages" + default n + depends on DPKG || DPKG_DEB + help + This allows dpkg and dpkg-deb to extract deb's that are compressed + internally with lzma instead of gzip. + + You only want this if you are creating your own custom debian + packages that use an internal control.tar.lzma or data.tar.lzma. + +endmenu diff --git a/archival/Kbuild b/archival/Kbuild new file mode 100644 index 000000000..f85e0c2a7 --- /dev/null +++ b/archival/Kbuild @@ -0,0 +1,22 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +libs-y += libunarchive/ + +lib-y:= +lib-$(CONFIG_AR) += ar.o +lib-$(CONFIG_BUNZIP2) += bunzip2.o +lib-$(CONFIG_UNLZMA) += unlzma.o +lib-$(CONFIG_CPIO) += cpio.o +lib-$(CONFIG_DPKG) += dpkg.o +lib-$(CONFIG_DPKG_DEB) += dpkg_deb.o +lib-$(CONFIG_GUNZIP) += gunzip.o +lib-$(CONFIG_GZIP) += gzip.o +lib-$(CONFIG_RPM2CPIO) += rpm2cpio.o +lib-$(CONFIG_RPM) += rpm.o +lib-$(CONFIG_TAR) += tar.o +lib-$(CONFIG_UNCOMPRESS) += uncompress.o +lib-$(CONFIG_UNZIP) += unzip.o diff --git a/archival/ar.c b/archival/ar.c new file mode 100644 index 000000000..521e03799 --- /dev/null +++ b/archival/ar.c @@ -0,0 +1,93 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini ar implementation for busybox + * + * Copyright (C) 2000 by Glenn McGrath + * Written by Glenn McGrath 1 June 2000 + * + * Based in part on BusyBox tar, Debian dpkg-deb and GNU ar. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * There is no single standard to adhere to so ar may not portable + * between different systems + * http://www.unix-systems.org/single_unix_specification_v2/xcu/ar.html + */ + +#include "busybox.h" +#include "unarchive.h" + +static void header_verbose_list_ar(const file_header_t *file_header) +{ + const char *mode = bb_mode_string(file_header->mode); + char *mtime; + + mtime = ctime(&file_header->mtime); + mtime[16] = ' '; + memmove(&mtime[17], &mtime[20], 4); + mtime[21] = '\0'; + printf("%s %d/%d%7d %s %s\n", &mode[1], file_header->uid, file_header->gid, + (int) file_header->size, &mtime[4], file_header->name); +} + +#define AR_CTX_PRINT 0x01 +#define AR_CTX_LIST 0x02 +#define AR_CTX_EXTRACT 0x04 +#define AR_OPT_PRESERVE_DATE 0x08 +#define AR_OPT_VERBOSE 0x10 +#define AR_OPT_CREATE 0x20 +#define AR_OPT_INSERT 0x40 + +int ar_main(int argc, char **argv) +{ + archive_handle_t *archive_handle; + unsigned opt; + static const char msg_unsupported_err[] = + "Archive %s not supported. Install binutils 'ar'."; + char magic[8]; + + archive_handle = init_handle(); + + /* Prepend '-' to the first argument if required */ + opt_complementary = "--:p:t:x:-1:?:p--tx:t--px:x--pt"; + opt = getopt32(argc, argv, "ptxovcr"); + + if (opt & AR_CTX_PRINT) { + archive_handle->action_data = data_extract_to_stdout; + } + if (opt & AR_CTX_LIST) { + archive_handle->action_header = header_list; + } + if (opt & AR_CTX_EXTRACT) { + archive_handle->action_data = data_extract_all; + } + if (opt & AR_OPT_PRESERVE_DATE) { + archive_handle->flags |= ARCHIVE_PRESERVE_DATE; + } + if (opt & AR_OPT_VERBOSE) { + archive_handle->action_header = header_verbose_list_ar; + } + if (opt & AR_OPT_CREATE) { + bb_error_msg_and_die(msg_unsupported_err, "creation"); + } + if (opt & AR_OPT_INSERT) { + bb_error_msg_and_die(msg_unsupported_err, "insertion"); + } + + archive_handle->src_fd = xopen(argv[optind++], O_RDONLY); + + while (optind < argc) { + archive_handle->filter = filter_accept_list; + llist_add_to(&(archive_handle->accept), argv[optind++]); + } + + xread(archive_handle->src_fd, magic, 7); + if (strncmp(magic, "!", 7) != 0) { + bb_error_msg_and_die("invalid ar magic"); + } + archive_handle->offset += 7; + + while (get_header_ar(archive_handle) == EXIT_SUCCESS); + + return EXIT_SUCCESS; +} diff --git a/archival/bunzip2.c b/archival/bunzip2.c new file mode 100644 index 000000000..757001fe4 --- /dev/null +++ b/archival/bunzip2.c @@ -0,0 +1,66 @@ +/* vi: set sw=4 ts=4: */ +/* + * Modified for busybox by Glenn McGrath + * Added support output to stdout by Thomas Lundquist + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "unarchive.h" + +#define BUNZIP2_OPT_STDOUT 1 +#define BUNZIP2_OPT_FORCE 2 + +int bunzip2_main(int argc, char **argv) +{ + USE_DESKTOP(long long) int status; + char *filename; + unsigned opt; + int src_fd, dst_fd; + + opt = getopt32(argc, argv, "cf"); + + /* Set input filename and number */ + filename = argv[optind]; + if ((filename) && (filename[0] != '-') && (filename[1] != '\0')) { + /* Open input file */ + src_fd = xopen(filename, O_RDONLY); + } else { + src_fd = STDIN_FILENO; + filename = 0; + } + + /* if called as bzcat force the stdout flag */ + if ((opt & BUNZIP2_OPT_STDOUT) || applet_name[2] == 'c') + filename = 0; + + /* Check that the input is sane. */ + if (isatty(src_fd) && (opt & BUNZIP2_OPT_FORCE) == 0) { + bb_error_msg_and_die("compressed data not read from terminal, " + "use -f to force it"); + } + + if (filename) { + struct stat stat_buf; + /* extension = filename+strlen(filename)-4 is buggy: + * strlen may be less than 4 */ + char *extension = strrchr(filename, '.'); + if (!extension || strcmp(extension, ".bz2") != 0) { + bb_error_msg_and_die("invalid extension"); + } + xstat(filename, &stat_buf); + *extension = '\0'; + dst_fd = xopen3(filename, O_WRONLY | O_CREAT | O_TRUNC, + stat_buf.st_mode); + } else dst_fd = STDOUT_FILENO; + status = uncompressStream(src_fd, dst_fd); + if (filename) { + if (status >= 0) filename[strlen(filename)] = '.'; + if (unlink(filename) < 0) { + bb_error_msg_and_die("cannot remove %s", filename); + } + } + + return status; +} diff --git a/archival/cpio.c b/archival/cpio.c new file mode 100644 index 000000000..73651ba10 --- /dev/null +++ b/archival/cpio.c @@ -0,0 +1,86 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini cpio implementation for busybox + * + * Copyright (C) 2001 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Limitations: + * Doesn't check CRC's + * Only supports new ASCII and CRC formats + * + */ +#include +#include +#include +#include +#include "unarchive.h" +#include "busybox.h" + +#define CPIO_OPT_EXTRACT 0x01 +#define CPIO_OPT_TEST 0x02 +#define CPIO_OPT_UNCONDITIONAL 0x04 +#define CPIO_OPT_VERBOSE 0x08 +#define CPIO_OPT_FILE 0x10 +#define CPIO_OPT_CREATE_LEADING_DIR 0x20 +#define CPIO_OPT_PRESERVE_MTIME 0x40 + +int cpio_main(int argc, char **argv) +{ + archive_handle_t *archive_handle; + char *cpio_filename = NULL; + unsigned opt; + + /* Initialise */ + archive_handle = init_handle(); + archive_handle->src_fd = STDIN_FILENO; + archive_handle->seek = seek_by_read; + archive_handle->flags = ARCHIVE_EXTRACT_NEWER | ARCHIVE_PRESERVE_DATE; + + opt = getopt32(argc, argv, "ituvF:dm", &cpio_filename); + + /* One of either extract or test options must be given */ + if ((opt & (CPIO_OPT_TEST | CPIO_OPT_EXTRACT)) == 0) { + bb_show_usage(); + } + + if (opt & CPIO_OPT_TEST) { + /* if both extract and test options are given, ignore extract option */ + if (opt & CPIO_OPT_EXTRACT) { + opt &= ~CPIO_OPT_EXTRACT; + } + archive_handle->action_header = header_list; + } + if (opt & CPIO_OPT_EXTRACT) { + archive_handle->action_data = data_extract_all; + } + if (opt & CPIO_OPT_UNCONDITIONAL) { + archive_handle->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL; + archive_handle->flags &= ~ARCHIVE_EXTRACT_NEWER; + } + if (opt & CPIO_OPT_VERBOSE) { + if (archive_handle->action_header == header_list) { + archive_handle->action_header = header_verbose_list; + } else { + archive_handle->action_header = header_list; + } + } + if (cpio_filename) { /* CPIO_OPT_FILE */ + archive_handle->src_fd = xopen(cpio_filename, O_RDONLY); + archive_handle->seek = seek_by_jump; + } + if (opt & CPIO_OPT_CREATE_LEADING_DIR) { + archive_handle->flags |= ARCHIVE_CREATE_LEADING_DIRS; + } + + while (optind < argc) { + archive_handle->filter = filter_accept_list; + llist_add_to(&(archive_handle->accept), argv[optind]); + optind++; + } + + while (get_header_cpio(archive_handle) == EXIT_SUCCESS); + + return EXIT_SUCCESS; +} diff --git a/archival/dpkg.c b/archival/dpkg.c new file mode 100644 index 000000000..9024d41d2 --- /dev/null +++ b/archival/dpkg.c @@ -0,0 +1,1819 @@ +/* vi: set sw=4 ts=4: */ +/* + * mini dpkg implementation for busybox. + * this is not meant as a replacement for dpkg + * + * written by glenn mcgrath with the help of others + * copyright (c) 2001 by glenn mcgrath + * + * started life as a busybox implementation of udpkg + * + * licensed under gplv2 or later, see file license in this tarball for details. + */ + +/* + * known difference between busybox dpkg and the official dpkg that i don't + * consider important, its worth keeping a note of differences anyway, just to + * make it easier to maintain. + * - the first value for the confflile: field isnt placed on a new line. + * - when installing a package the status: field is placed at the end of the + * section, rather than just after the package: field. + * + * bugs that need to be fixed + * - (unknown, please let me know when you find any) + * + */ + +#include "busybox.h" +#include "unarchive.h" + +/* note: if you vary hash_prime sizes be aware, + * 1) tweaking these will have a big effect on how much memory this program uses. + * 2) for computational efficiency these hash tables should be at least 20% + * larger than the maximum number of elements stored in it. + * 3) all _hash_prime's must be a prime number or chaos is assured, if your looking + * for a prime, try http://www.utm.edu/research/primes/lists/small/10000.txt + * 4) if you go bigger than 15 bits you may get into trouble (untested) as its + * sometimes cast to an unsigned int, if you go to 16 bit you will overlap + * int's and chaos is assured, 16381 is the max prime for 14 bit field + */ + +/* NAME_HASH_PRIME, Stores package names and versions, + * I estimate it should be at least 50% bigger than PACKAGE_HASH_PRIME, + * as there a lot of duplicate version numbers */ +#define NAME_HASH_PRIME 16381 + +/* PACKAGE_HASH_PRIME, Maximum number of unique packages, + * It must not be smaller than STATUS_HASH_PRIME, + * Currently only packages from status_hashtable are stored in here, but in + * future this may be used to store packages not only from a status file, + * but an available_hashtable, and even multiple packages files. + * Package can be stored more than once if they have different versions. + * e.g. The same package may have different versions in the status file + * and available file */ +#define PACKAGE_HASH_PRIME 10007 +typedef struct edge_s { + unsigned int operator:3; + unsigned int type:4; + unsigned int name:14; + unsigned int version:14; +} edge_t; + +typedef struct common_node_s { + unsigned int name:14; + unsigned int version:14; + unsigned int num_of_edges:14; + edge_t **edge; +} common_node_t; + +/* Currently it doesnt store packages that have state-status of not-installed + * So it only really has to be the size of the maximum number of packages + * likely to be installed at any one time, so there is a bit of leeway here */ +#define STATUS_HASH_PRIME 8191 +typedef struct status_node_s { + unsigned int package:14; /* has to fit PACKAGE_HASH_PRIME */ + unsigned int status:14; /* has to fit STATUS_HASH_PRIME */ +} status_node_t; + +/* Were statically declared here, but such a big bss is nommu-unfriendly */ +static char **name_hashtable; /* [NAME_HASH_PRIME + 1] */ +static common_node_t **package_hashtable; /* [PACKAGE_HASH_PRIME + 1] */ +static status_node_t **status_hashtable; /* [STATUS_HASH_PRIME + 1] */ + +/* Even numbers are for 'extras', like ored dependencies or null */ +enum edge_type_e { + EDGE_NULL = 0, + EDGE_PRE_DEPENDS = 1, + EDGE_OR_PRE_DEPENDS = 2, + EDGE_DEPENDS = 3, + EDGE_OR_DEPENDS = 4, + EDGE_REPLACES = 5, + EDGE_PROVIDES = 7, + EDGE_CONFLICTS = 9, + EDGE_SUGGESTS = 11, + EDGE_RECOMMENDS = 13, + EDGE_ENHANCES = 15 +}; +enum operator_e { + VER_NULL = 0, + VER_EQUAL = 1, + VER_LESS = 2, + VER_LESS_EQUAL = 3, + VER_MORE = 4, + VER_MORE_EQUAL = 5, + VER_ANY = 6 +}; + +enum dpkg_opt_e { + dpkg_opt_purge = 1, + dpkg_opt_remove = 2, + dpkg_opt_unpack = 4, + dpkg_opt_configure = 8, + dpkg_opt_install = 16, + dpkg_opt_package_name = 32, + dpkg_opt_filename = 64, + dpkg_opt_list_installed = 128, + dpkg_opt_force_ignore_depends = 256 +}; + +typedef struct deb_file_s { + char *control_file; + char *filename; + unsigned int package:14; +} deb_file_t; + + +static void make_hash(const char *key, unsigned int *start, unsigned int *decrement, const int hash_prime) +{ + unsigned long int hash_num = key[0]; + int len = strlen(key); + int i; + + /* Maybe i should have uses a "proper" hashing algorithm here instead + * of making one up myself, seems to be working ok though. */ + for (i = 1; i < len; i++) { + /* shifts the ascii based value and adds it to previous value + * shift amount is mod 24 because long int is 32 bit and data + * to be shifted is 8, don't want to shift data to where it has + * no effect*/ + hash_num += ((key[i] + key[i-1]) << ((key[i] * i) % 24)); + } + *start = (unsigned int) hash_num % hash_prime; + *decrement = (unsigned int) 1 + (hash_num % (hash_prime - 1)); +} + +/* this adds the key to the hash table */ +static int search_name_hashtable(const char *key) +{ + unsigned int probe_address = 0; + unsigned int probe_decrement = 0; +// char *temp; + + make_hash(key, &probe_address, &probe_decrement, NAME_HASH_PRIME); + while (name_hashtable[probe_address] != NULL) { + if (strcmp(name_hashtable[probe_address], key) == 0) { + return probe_address; + } else { + probe_address -= probe_decrement; + if ((int)probe_address < 0) { + probe_address += NAME_HASH_PRIME; + } + } + } + name_hashtable[probe_address] = xstrdup(key); + return probe_address; +} + +/* this DOESNT add the key to the hashtable + * TODO make it consistent with search_name_hashtable + */ +static unsigned int search_status_hashtable(const char *key) +{ + unsigned int probe_address = 0; + unsigned int probe_decrement = 0; + + make_hash(key, &probe_address, &probe_decrement, STATUS_HASH_PRIME); + while (status_hashtable[probe_address] != NULL) { + if (strcmp(key, name_hashtable[package_hashtable[status_hashtable[probe_address]->package]->name]) == 0) { + break; + } else { + probe_address -= probe_decrement; + if ((int)probe_address < 0) { + probe_address += STATUS_HASH_PRIME; + } + } + } + return probe_address; +} + +/* Need to rethink version comparison, maybe the official dpkg has something i can use ? */ +static int version_compare_part(const char *version1, const char *version2) +{ + int upstream_len1 = 0; + int upstream_len2 = 0; + char *name1_char; + char *name2_char; + int len1 = 0; + int len2 = 0; + int tmp_int; + int ver_num1; + int ver_num2; + int ret; + + if (version1 == NULL) { + version1 = xstrdup(""); + } + if (version2 == NULL) { + version2 = xstrdup(""); + } + upstream_len1 = strlen(version1); + upstream_len2 = strlen(version2); + + while ((len1 < upstream_len1) || (len2 < upstream_len2)) { + /* Compare non-digit section */ + tmp_int = strcspn(&version1[len1], "0123456789"); + name1_char = xstrndup(&version1[len1], tmp_int); + len1 += tmp_int; + tmp_int = strcspn(&version2[len2], "0123456789"); + name2_char = xstrndup(&version2[len2], tmp_int); + len2 += tmp_int; + tmp_int = strcmp(name1_char, name2_char); + free(name1_char); + free(name2_char); + if (tmp_int != 0) { + ret = tmp_int; + goto cleanup_version_compare_part; + } + + /* Compare digits */ + tmp_int = strspn(&version1[len1], "0123456789"); + name1_char = xstrndup(&version1[len1], tmp_int); + len1 += tmp_int; + tmp_int = strspn(&version2[len2], "0123456789"); + name2_char = xstrndup(&version2[len2], tmp_int); + len2 += tmp_int; + ver_num1 = atoi(name1_char); + ver_num2 = atoi(name2_char); + free(name1_char); + free(name2_char); + if (ver_num1 < ver_num2) { + ret = -1; + goto cleanup_version_compare_part; + } + else if (ver_num1 > ver_num2) { + ret = 1; + goto cleanup_version_compare_part; + } + } + ret = 0; +cleanup_version_compare_part: + return ret; +} + +/* if ver1 < ver2 return -1, + * if ver1 = ver2 return 0, + * if ver1 > ver2 return 1, + */ +static int version_compare(const unsigned int ver1, const unsigned int ver2) +{ + char *ch_ver1 = name_hashtable[ver1]; + char *ch_ver2 = name_hashtable[ver2]; + + char epoch1, epoch2; + char *deb_ver1, *deb_ver2; + char *ver1_ptr, *ver2_ptr; + char *upstream_ver1; + char *upstream_ver2; + int result; + + /* Compare epoch */ + if (ch_ver1[1] == ':') { + epoch1 = ch_ver1[0]; + ver1_ptr = strchr(ch_ver1, ':') + 1; + } else { + epoch1 = '0'; + ver1_ptr = ch_ver1; + } + if (ch_ver2[1] == ':') { + epoch2 = ch_ver2[0]; + ver2_ptr = strchr(ch_ver2, ':') + 1; + } else { + epoch2 = '0'; + ver2_ptr = ch_ver2; + } + if (epoch1 < epoch2) { + return -1; + } + else if (epoch1 > epoch2) { + return 1; + } + + /* Compare upstream version */ + upstream_ver1 = xstrdup(ver1_ptr); + upstream_ver2 = xstrdup(ver2_ptr); + + /* Chop off debian version, and store for later use */ + deb_ver1 = strrchr(upstream_ver1, '-'); + deb_ver2 = strrchr(upstream_ver2, '-'); + if (deb_ver1) { + deb_ver1[0] = '\0'; + deb_ver1++; + } + if (deb_ver2) { + deb_ver2[0] = '\0'; + deb_ver2++; + } + result = version_compare_part(upstream_ver1, upstream_ver2); + if (!result) + /* Compare debian versions */ + result = version_compare_part(deb_ver1, deb_ver2); + + free(upstream_ver1); + free(upstream_ver2); + return result; +} + +static int test_version(const unsigned int version1, const unsigned int version2, const unsigned int operator) +{ + const int version_result = version_compare(version1, version2); + switch (operator) { + case VER_ANY: + return TRUE; + case VER_EQUAL: + if (version_result == 0) { + return TRUE; + } + break; + case VER_LESS: + if (version_result < 0) { + return TRUE; + } + break; + case VER_LESS_EQUAL: + if (version_result <= 0) { + return TRUE; + } + break; + case VER_MORE: + if (version_result > 0) { + return TRUE; + } + break; + case VER_MORE_EQUAL: + if (version_result >= 0) { + return TRUE; + } + break; + } + return FALSE; +} + + +static int search_package_hashtable(const unsigned int name, const unsigned int version, const unsigned int operator) +{ + unsigned int probe_address = 0; + unsigned int probe_decrement = 0; + + make_hash(name_hashtable[name], &probe_address, &probe_decrement, PACKAGE_HASH_PRIME); + while (package_hashtable[probe_address] != NULL) { + if (package_hashtable[probe_address]->name == name) { + if (operator == VER_ANY) { + return probe_address; + } + if (test_version(package_hashtable[probe_address]->version, version, operator)) { + return probe_address; + } + } + probe_address -= probe_decrement; + if ((int)probe_address < 0) { + probe_address += PACKAGE_HASH_PRIME; + } + } + return probe_address; +} + +/* + * This function searches through the entire package_hashtable looking + * for a package which provides "needle". It returns the index into + * the package_hashtable for the providing package. + * + * needle is the index into name_hashtable of the package we are + * looking for. + * + * start_at is the index in the package_hashtable to start looking + * at. If start_at is -1 then start at the beginning. This is to allow + * for repeated searches since more than one package might provide + * needle. + * + * FIXME: I don't think this is very efficient, but I thought I'd keep + * it simple for now until it proves to be a problem. + */ +static int search_for_provides(int needle, int start_at) { + int i, j; + common_node_t *p; + for (i = start_at + 1; i < PACKAGE_HASH_PRIME; i++) { + p = package_hashtable[i]; + if (p == NULL) continue; + for (j = 0; j < p->num_of_edges; j++) + if (p->edge[j]->type == EDGE_PROVIDES && p->edge[j]->name == needle) + return i; + } + return -1; +} + +/* + * Add an edge to a node + */ +static void add_edge_to_node(common_node_t *node, edge_t *edge) +{ + node->num_of_edges++; + node->edge = xrealloc(node->edge, sizeof(edge_t) * (node->num_of_edges + 1)); + node->edge[node->num_of_edges - 1] = edge; +} + +/* + * Create one new node and one new edge for every dependency. + * + * Dependencies which contain multiple alternatives are represented as + * an EDGE_OR_PRE_DEPENDS or EDGE_OR_DEPENDS node, followed by a + * number of EDGE_PRE_DEPENDS or EDGE_DEPENDS nodes. The name field of + * the OR edge contains the full dependency string while the version + * field contains the number of EDGE nodes which follow as part of + * this alternative. + */ +static void add_split_dependencies(common_node_t *parent_node, const char *whole_line, unsigned int edge_type) +{ + char *line = xstrdup(whole_line); + char *line2; + char *line_ptr1 = NULL; + char *line_ptr2 = NULL; + char *field; + char *field2; + char *version; + edge_t *edge; + edge_t *or_edge; + int offset_ch; + + field = strtok_r(line, ",", &line_ptr1); + do { + /* skip leading spaces */ + field += strspn(field, " "); + line2 = xstrdup(field); + field2 = strtok_r(line2, "|", &line_ptr2); + if ((edge_type == EDGE_DEPENDS || edge_type == EDGE_PRE_DEPENDS) && + (strcmp(field, field2) != 0)) { + or_edge = (edge_t *)xmalloc(sizeof(edge_t)); + or_edge->type = edge_type + 1; + } else { + or_edge = NULL; + } + + if (or_edge) { + or_edge->name = search_name_hashtable(field); + or_edge->version = 0; // tracks the number of altenatives + + add_edge_to_node(parent_node, or_edge); + } + + do { + edge = (edge_t *) xmalloc(sizeof(edge_t)); + edge->type = edge_type; + + /* Skip any extra leading spaces */ + field2 += strspn(field2, " "); + + /* Get dependency version info */ + version = strchr(field2, '('); + if (version == NULL) { + edge->operator = VER_ANY; + /* Get the versions hash number, adding it if the number isnt already in there */ + edge->version = search_name_hashtable("ANY"); + } else { + /* Skip leading ' ' or '(' */ + version += strspn(field2, " "); + version += strspn(version, "("); + /* Calculate length of any operator characters */ + offset_ch = strspn(version, "<=>"); + /* Determine operator */ + if (offset_ch > 0) { + if (strncmp(version, "=", offset_ch) == 0) { + edge->operator = VER_EQUAL; + } + else if (strncmp(version, "<<", offset_ch) == 0) { + edge->operator = VER_LESS; + } + else if (strncmp(version, "<=", offset_ch) == 0) { + edge->operator = VER_LESS_EQUAL; + } + else if (strncmp(version, ">>", offset_ch) == 0) { + edge->operator = VER_MORE; + } + else if (strncmp(version, ">=", offset_ch) == 0) { + edge->operator = VER_MORE_EQUAL; + } else { + bb_error_msg_and_die("illegal operator"); + } + } + /* skip to start of version numbers */ + version += offset_ch; + version += strspn(version, " "); + + /* Truncate version at trailing ' ' or ')' */ + version[strcspn(version, " )")] = '\0'; + /* Get the versions hash number, adding it if the number isnt already in there */ + edge->version = search_name_hashtable(version); + } + + /* Get the dependency name */ + field2[strcspn(field2, " (")] = '\0'; + edge->name = search_name_hashtable(field2); + + if (or_edge) + or_edge->version++; + + add_edge_to_node(parent_node, edge); + } while ((field2 = strtok_r(NULL, "|", &line_ptr2)) != NULL); + free(line2); + } while ((field = strtok_r(NULL, ",", &line_ptr1)) != NULL); + free(line); + + return; +} + +static void free_package(common_node_t *node) +{ + unsigned i; + if (node) { + for (i = 0; i < node->num_of_edges; i++) { + free(node->edge[i]); + } + free(node->edge); + free(node); + } +} + +/* + * Gets the next package field from package_buffer, seperated into the field name + * and field value, it returns the int offset to the first character of the next field + */ +static int read_package_field(const char *package_buffer, char **field_name, char **field_value) +{ + int offset_name_start = 0; + int offset_name_end = 0; + int offset_value_start = 0; + int offset_value_end = 0; + int offset = 0; + int next_offset; + int name_length; + int value_length; + int exit_flag = FALSE; + + if (package_buffer == NULL) { + *field_name = NULL; + *field_value = NULL; + return -1; + } + while (1) { + next_offset = offset + 1; + switch (package_buffer[offset]) { + case '\0': + exit_flag = TRUE; + break; + case ':': + if (offset_name_end == 0) { + offset_name_end = offset; + offset_value_start = next_offset; + } + /* TODO: Name might still have trailing spaces if ':' isnt + * immediately after name */ + break; + case '\n': + /* TODO: The char next_offset may be out of bounds */ + if (package_buffer[next_offset] != ' ') { + exit_flag = TRUE; + break; + } + case '\t': + case ' ': + /* increment the value start point if its a just filler */ + if (offset_name_start == offset) { + offset_name_start++; + } + if (offset_value_start == offset) { + offset_value_start++; + } + break; + } + if (exit_flag) { + /* Check that the names are valid */ + offset_value_end = offset; + name_length = offset_name_end - offset_name_start; + value_length = offset_value_end - offset_value_start; + if (name_length == 0) { + break; + } + if ((name_length > 0) && (value_length > 0)) { + break; + } + + /* If not valid, start fresh with next field */ + exit_flag = FALSE; + offset_name_start = offset + 1; + offset_name_end = 0; + offset_value_start = offset + 1; + offset_value_end = offset + 1; + offset++; + } + offset++; + } + if (name_length == 0) { + *field_name = NULL; + } else { + *field_name = xstrndup(&package_buffer[offset_name_start], name_length); + } + if (value_length > 0) { + *field_value = xstrndup(&package_buffer[offset_value_start], value_length); + } else { + *field_value = NULL; + } + return next_offset; +} + +static unsigned int fill_package_struct(char *control_buffer) +{ + static const char *const field_names[] = { "Package", "Version", + "Pre-Depends", "Depends","Replaces", "Provides", + "Conflicts", "Suggests", "Recommends", "Enhances", 0 + }; + + common_node_t *new_node = (common_node_t *) xzalloc(sizeof(common_node_t)); + char *field_name; + char *field_value; + int field_start = 0; + int num = -1; + int buffer_length = strlen(control_buffer); + + new_node->version = search_name_hashtable("unknown"); + while (field_start < buffer_length) { + unsigned field_num; + + field_start += read_package_field(&control_buffer[field_start], + &field_name, &field_value); + + if (field_name == NULL) { + goto fill_package_struct_cleanup; /* Oh no, the dreaded goto statement ! */ + } + + field_num = index_in_str_array(field_names, field_name); + switch (field_num) { + case 0: /* Package */ + new_node->name = search_name_hashtable(field_value); + break; + case 1: /* Version */ + new_node->version = search_name_hashtable(field_value); + break; + case 2: /* Pre-Depends */ + add_split_dependencies(new_node, field_value, EDGE_PRE_DEPENDS); + break; + case 3: /* Depends */ + add_split_dependencies(new_node, field_value, EDGE_DEPENDS); + break; + case 4: /* Replaces */ + add_split_dependencies(new_node, field_value, EDGE_REPLACES); + break; + case 5: /* Provides */ + add_split_dependencies(new_node, field_value, EDGE_PROVIDES); + break; + case 6: /* Conflicts */ + add_split_dependencies(new_node, field_value, EDGE_CONFLICTS); + break; + case 7: /* Suggests */ + add_split_dependencies(new_node, field_value, EDGE_SUGGESTS); + break; + case 8: /* Recommends */ + add_split_dependencies(new_node, field_value, EDGE_RECOMMENDS); + break; + case 9: /* Enhances */ + add_split_dependencies(new_node, field_value, EDGE_ENHANCES); + break; + } +fill_package_struct_cleanup: + free(field_name); + free(field_value); + } + + if (new_node->version == search_name_hashtable("unknown")) { + free_package(new_node); + return -1; + } + num = search_package_hashtable(new_node->name, new_node->version, VER_EQUAL); + if (package_hashtable[num] == NULL) { + package_hashtable[num] = new_node; + } else { + free_package(new_node); + } + return num; +} + +/* if num = 1, it returns the want status, 2 returns flag, 3 returns status */ +static unsigned int get_status(const unsigned int status_node, const int num) +{ + char *status_string = name_hashtable[status_hashtable[status_node]->status]; + char *state_sub_string; + unsigned int state_sub_num; + int len; + int i; + + /* set tmp_string to point to the start of the word number */ + for (i = 1; i < num; i++) { + /* skip past a word */ + status_string += strcspn(status_string, " "); + /* skip past the separating spaces */ + status_string += strspn(status_string, " "); + } + len = strcspn(status_string, " \n"); + state_sub_string = xstrndup(status_string, len); + state_sub_num = search_name_hashtable(state_sub_string); + free(state_sub_string); + return state_sub_num; +} + +static void set_status(const unsigned int status_node_num, const char *new_value, const int position) +{ + const unsigned int new_value_len = strlen(new_value); + const unsigned int new_value_num = search_name_hashtable(new_value); + unsigned int want = get_status(status_node_num, 1); + unsigned int flag = get_status(status_node_num, 2); + unsigned int status = get_status(status_node_num, 3); + int want_len = strlen(name_hashtable[want]); + int flag_len = strlen(name_hashtable[flag]); + int status_len = strlen(name_hashtable[status]); + char *new_status; + + switch (position) { + case 1: + want = new_value_num; + want_len = new_value_len; + break; + case 2: + flag = new_value_num; + flag_len = new_value_len; + break; + case 3: + status = new_value_num; + status_len = new_value_len; + break; + default: + bb_error_msg_and_die("DEBUG ONLY: this shouldnt happen"); + } + + new_status = xasprintf("%s %s %s", name_hashtable[want], name_hashtable[flag], name_hashtable[status]); + status_hashtable[status_node_num]->status = search_name_hashtable(new_status); + free(new_status); + return; +} + +static const char *describe_status(int status_num) { + int status_want, status_state ; + if (status_hashtable[status_num] == NULL || status_hashtable[status_num]->status == 0) + return "is not installed or flagged to be installed\n"; + + status_want = get_status(status_num, 1); + status_state = get_status(status_num, 3); + + if (status_state == search_name_hashtable("installed")) { + if (status_want == search_name_hashtable("install")) + return "is installed"; + if (status_want == search_name_hashtable("deinstall")) + return "is marked to be removed"; + if (status_want == search_name_hashtable("purge")) + return "is marked to be purged"; + } + if (status_want == search_name_hashtable("unknown")) + return "is in an indeterminate state"; + if (status_want == search_name_hashtable("install")) + return "is marked to be installed"; + + return "is not installed or flagged to be installed"; +} + + +static void index_status_file(const char *filename) +{ + FILE *status_file; + char *control_buffer; + char *status_line; + status_node_t *status_node = NULL; + unsigned int status_num; + + status_file = xfopen(filename, "r"); + while ((control_buffer = xmalloc_fgets_str(status_file, "\n\n")) != NULL) { + const unsigned int package_num = fill_package_struct(control_buffer); + if (package_num != -1) { + status_node = xmalloc(sizeof(status_node_t)); + /* fill_package_struct doesnt handle the status field */ + status_line = strstr(control_buffer, "Status:"); + if (status_line != NULL) { + status_line += 7; + status_line += strspn(status_line, " \n\t"); + status_line = xstrndup(status_line, strcspn(status_line, "\n")); + status_node->status = search_name_hashtable(status_line); + free(status_line); + } + status_node->package = package_num; + status_num = search_status_hashtable(name_hashtable[package_hashtable[status_node->package]->name]); + status_hashtable[status_num] = status_node; + } + free(control_buffer); + } + fclose(status_file); + return; +} + +static void write_buffer_no_status(FILE *new_status_file, const char *control_buffer) +{ + char *name; + char *value; + int start = 0; + while (1) { + start += read_package_field(&control_buffer[start], &name, &value); + if (name == NULL) { + break; + } + if (strcmp(name, "Status") != 0) { + fprintf(new_status_file, "%s: %s\n", name, value); + } + } + return; +} + +/* This could do with a cleanup */ +static void write_status_file(deb_file_t **deb_file) +{ + FILE *old_status_file = xfopen("/var/lib/dpkg/status", "r"); + FILE *new_status_file = xfopen("/var/lib/dpkg/status.udeb", "w"); + char *package_name; + char *status_from_file; + char *control_buffer = NULL; + char *tmp_string; + int status_num; + int field_start = 0; + int write_flag; + int i = 0; + + /* Update previously known packages */ + while ((control_buffer = xmalloc_fgets_str(old_status_file, "\n\n")) != NULL) { + if ((tmp_string = strstr(control_buffer, "Package:")) == NULL) { + continue; + } + + tmp_string += 8; + tmp_string += strspn(tmp_string, " \n\t"); + package_name = xstrndup(tmp_string, strcspn(tmp_string, "\n")); + write_flag = FALSE; + tmp_string = strstr(control_buffer, "Status:"); + if (tmp_string != NULL) { + /* Seperate the status value from the control buffer */ + tmp_string += 7; + tmp_string += strspn(tmp_string, " \n\t"); + status_from_file = xstrndup(tmp_string, strcspn(tmp_string, "\n")); + } else { + status_from_file = NULL; + } + + /* Find this package in the status hashtable */ + status_num = search_status_hashtable(package_name); + if (status_hashtable[status_num] != NULL) { + const char *status_from_hashtable = name_hashtable[status_hashtable[status_num]->status]; + if (strcmp(status_from_file, status_from_hashtable) != 0) { + /* New status isnt exactly the same as old status */ + const int state_status = get_status(status_num, 3); + if ((strcmp("installed", name_hashtable[state_status]) == 0) || + (strcmp("unpacked", name_hashtable[state_status]) == 0)) { + /* We need to add the control file from the package */ + i = 0; + while (deb_file[i] != NULL) { + if (strcmp(package_name, name_hashtable[package_hashtable[deb_file[i]->package]->name]) == 0) { + /* Write a status file entry with a modified status */ + /* remove trailing \n's */ + write_buffer_no_status(new_status_file, deb_file[i]->control_file); + set_status(status_num, "ok", 2); + fprintf(new_status_file, "Status: %s\n\n", + name_hashtable[status_hashtable[status_num]->status]); + write_flag = TRUE; + break; + } + i++; + } + /* This is temperary, debugging only */ + if (deb_file[i] == NULL) { + bb_error_msg_and_die("ALERT: cannot find a control file, " + "your status file may be broken, status may be " + "incorrect for %s", package_name); + } + } + else if (strcmp("not-installed", name_hashtable[state_status]) == 0) { + /* Only write the Package, Status, Priority and Section lines */ + fprintf(new_status_file, "Package: %s\n", package_name); + fprintf(new_status_file, "Status: %s\n", status_from_hashtable); + + while (1) { + char *field_name; + char *field_value; + field_start += read_package_field(&control_buffer[field_start], &field_name, &field_value); + if (field_name == NULL) { + break; + } + if ((strcmp(field_name, "Priority") == 0) || + (strcmp(field_name, "Section") == 0)) { + fprintf(new_status_file, "%s: %s\n", field_name, field_value); + } + } + write_flag = TRUE; + fputs("\n", new_status_file); + } + else if (strcmp("config-files", name_hashtable[state_status]) == 0) { + /* only change the status line */ + while (1) { + char *field_name; + char *field_value; + field_start += read_package_field(&control_buffer[field_start], &field_name, &field_value); + if (field_name == NULL) { + break; + } + /* Setup start point for next field */ + if (strcmp(field_name, "Status") == 0) { + fprintf(new_status_file, "Status: %s\n", status_from_hashtable); + } else { + fprintf(new_status_file, "%s: %s\n", field_name, field_value); + } + } + write_flag = TRUE; + fputs("\n", new_status_file); + } + } + } + /* If the package from the status file wasnt handle above, do it now*/ + if (! write_flag) { + fprintf(new_status_file, "%s\n\n", control_buffer); + } + + free(status_from_file); + free(package_name); + free(control_buffer); + } + + /* Write any new packages */ + for (i = 0; deb_file[i] != NULL; i++) { + status_num = search_status_hashtable(name_hashtable[package_hashtable[deb_file[i]->package]->name]); + if (strcmp("reinstreq", name_hashtable[get_status(status_num, 2)]) == 0) { + write_buffer_no_status(new_status_file, deb_file[i]->control_file); + set_status(status_num, "ok", 2); + fprintf(new_status_file, "Status: %s\n\n", name_hashtable[status_hashtable[status_num]->status]); + } + } + fclose(old_status_file); + fclose(new_status_file); + + + /* Create a separate backfile to dpkg */ + if (rename("/var/lib/dpkg/status", "/var/lib/dpkg/status.udeb.bak") == -1) { + struct stat stat_buf; + xstat("/var/lib/dpkg/status", &stat_buf); + /* Its ok if renaming the status file fails because status + * file doesnt exist, maybe we are starting from scratch */ + bb_error_msg("no status file found, creating new one"); + } + + if (rename("/var/lib/dpkg/status.udeb", "/var/lib/dpkg/status") == -1) { + bb_error_msg_and_die("DANGER: cannot create status file, " + "you need to manually repair your status file"); + } +} + +/* This function returns TRUE if the given package can satisfy a + * dependency of type depend_type. + * + * A pre-depends is satisfied only if a package is already installed, + * which a regular depends can be satisfied by a package which we want + * to install. + */ +static int package_satisfies_dependency(int package, int depend_type) +{ + int status_num = search_status_hashtable(name_hashtable[package_hashtable[package]->name]); + + /* status could be unknown if package is a pure virtual + * provides which cannot satisfy any dependency by itself. + */ + if (status_hashtable[status_num] == NULL) + return 0; + + switch (depend_type) { + case EDGE_PRE_DEPENDS: return get_status(status_num, 3) == search_name_hashtable("installed"); + case EDGE_DEPENDS: return get_status(status_num, 1) == search_name_hashtable("install"); + } + return 0; +} + +static int check_deps(deb_file_t **deb_file, int deb_start, int dep_max_count) +{ + int *conflicts = NULL; + int conflicts_num = 0; + int i = deb_start; + int j; + + /* Check for conflicts + * TODO: TEST if conflicts with other packages to be installed + * + * Add install packages and the packages they provide + * to the list of files to check conflicts for + */ + + /* Create array of package numbers to check against + * installed package for conflicts*/ + while (deb_file[i] != NULL) { + const unsigned int package_num = deb_file[i]->package; + conflicts = xrealloc(conflicts, sizeof(int) * (conflicts_num + 1)); + conflicts[conflicts_num] = package_num; + conflicts_num++; + /* add provides to conflicts list */ + for (j = 0; j < package_hashtable[package_num]->num_of_edges; j++) { + if (package_hashtable[package_num]->edge[j]->type == EDGE_PROVIDES) { + const int conflicts_package_num = search_package_hashtable( + package_hashtable[package_num]->edge[j]->name, + package_hashtable[package_num]->edge[j]->version, + package_hashtable[package_num]->edge[j]->operator); + if (package_hashtable[conflicts_package_num] == NULL) { + /* create a new package */ + common_node_t *new_node = (common_node_t *) xzalloc(sizeof(common_node_t)); + new_node->name = package_hashtable[package_num]->edge[j]->name; + new_node->version = package_hashtable[package_num]->edge[j]->version; + package_hashtable[conflicts_package_num] = new_node; + } + conflicts = xrealloc(conflicts, sizeof(int) * (conflicts_num + 1)); + conflicts[conflicts_num] = conflicts_package_num; + conflicts_num++; + } + } + i++; + } + + /* Check conflicts */ + i = 0; + while (deb_file[i] != NULL) { + const common_node_t *package_node = package_hashtable[deb_file[i]->package]; + int status_num = 0; + status_num = search_status_hashtable(name_hashtable[package_node->name]); + + if (get_status(status_num, 3) == search_name_hashtable("installed")) { + i++; + continue; + } + + for (j = 0; j < package_node->num_of_edges; j++) { + const edge_t *package_edge = package_node->edge[j]; + + if (package_edge->type == EDGE_CONFLICTS) { + const unsigned int package_num = + search_package_hashtable(package_edge->name, + package_edge->version, + package_edge->operator); + int result = 0; + if (package_hashtable[package_num] != NULL) { + status_num = search_status_hashtable(name_hashtable[package_hashtable[package_num]->name]); + + if (get_status(status_num, 1) == search_name_hashtable("install")) { + result = test_version(package_hashtable[deb_file[i]->package]->version, + package_edge->version, package_edge->operator); + } + } + + if (result) { + bb_error_msg_and_die("package %s conflicts with %s", + name_hashtable[package_node->name], + name_hashtable[package_edge->name]); + } + } + } + i++; + } + + + /* Check dependendcies */ + for (i = 0; i < PACKAGE_HASH_PRIME; i++) { + int status_num = 0; + int number_of_alternatives = 0; + const edge_t * root_of_alternatives = NULL; + const common_node_t *package_node = package_hashtable[i]; + + /* If the package node does not exist then this + * package is a virtual one. In which case there are + * no dependencies to check. + */ + if (package_node == NULL) continue; + + status_num = search_status_hashtable(name_hashtable[package_node->name]); + + /* If there is no status then this package is a + * virtual one provided by something else. In which + * case there are no dependencies to check. + */ + if (status_hashtable[status_num] == NULL) continue; + + /* If we don't want this package installed then we may + * as well ignore it's dependencies. + */ + if (get_status(status_num, 1) != search_name_hashtable("install")) { + continue; + } + + /* This code is tested only for EDGE_DEPENDS, since I + * have no suitable pre-depends available. There is no + * reason that it shouldn't work though :-) + */ + for (j = 0; j < package_node->num_of_edges; j++) { + const edge_t *package_edge = package_node->edge[j]; + unsigned int package_num; + + if (package_edge->type == EDGE_OR_PRE_DEPENDS || + package_edge->type == EDGE_OR_DEPENDS) { /* start an EDGE_OR_ list */ + number_of_alternatives = package_edge->version; + root_of_alternatives = package_edge; + continue; + } else if (number_of_alternatives == 0) { /* not in the middle of an EDGE_OR_ list */ + number_of_alternatives = 1; + root_of_alternatives = NULL; + } + + package_num = search_package_hashtable(package_edge->name, package_edge->version, package_edge->operator); + + if (package_edge->type == EDGE_PRE_DEPENDS || + package_edge->type == EDGE_DEPENDS) { + int result=1; + status_num = 0; + + /* If we are inside an alternative then check + * this edge is the right type. + * + * EDGE_DEPENDS == OR_DEPENDS -1 + * EDGE_PRE_DEPENDS == OR_PRE_DEPENDS -1 + */ + if (root_of_alternatives && package_edge->type != root_of_alternatives->type - 1) + bb_error_msg_and_die("fatal error, package dependencies corrupt: %d != %d - 1", + package_edge->type, root_of_alternatives->type); + + if (package_hashtable[package_num] != NULL) + result = !package_satisfies_dependency(package_num, package_edge->type); + + if (result) { /* check for other package which provide what we are looking for */ + int provider = -1; + + while ((provider = search_for_provides(package_edge->name, provider)) > -1) { + if (package_hashtable[provider] == NULL) { + puts("Have a provider but no package information for it"); + continue; + } + result = !package_satisfies_dependency(provider, package_edge->type); + + if (result == 0) + break; + } + } + + /* It must be already installed, or to be installed */ + number_of_alternatives--; + if (result && number_of_alternatives == 0) { + if (root_of_alternatives) + bb_error_msg_and_die( + "package %s %sdepends on %s, " + "which cannot be satisfied", + name_hashtable[package_node->name], + package_edge->type == EDGE_PRE_DEPENDS ? "pre-" : "", + name_hashtable[root_of_alternatives->name]); + else + bb_error_msg_and_die( + "package %s %sdepends on %s, which %s\n", + name_hashtable[package_node->name], + package_edge->type == EDGE_PRE_DEPENDS ? "pre-" : "", + name_hashtable[package_edge->name], + describe_status(status_num)); + } else if (result == 0 && number_of_alternatives) { + /* we've found a package which + * satisfies the dependency, + * so skip over the rest of + * the alternatives. + */ + j += number_of_alternatives; + number_of_alternatives = 0; + } + } + } + } + free(conflicts); + return TRUE; +} + +static char **create_list(const char *filename) +{ + FILE *list_stream; + char **file_list = NULL; + char *line = NULL; + int count = 0; + + /* don't use [xw]fopen here, handle error ourself */ + list_stream = fopen(filename, "r"); + if (list_stream == NULL) { + return NULL; + } + + while ((line = xmalloc_getline(list_stream)) != NULL) { + file_list = xrealloc(file_list, sizeof(char *) * (count + 2)); + file_list[count] = line; + count++; + } + fclose(list_stream); + + if (count == 0) { + return NULL; + } else { + file_list[count] = NULL; + return file_list; + } +} + +/* maybe i should try and hook this into remove_file.c somehow */ +static int remove_file_array(char **remove_names, char **exclude_names) +{ + struct stat path_stat; + int match_flag; + int remove_flag = FALSE; + int i,j; + + if (remove_names == NULL) { + return FALSE; + } + for (i = 0; remove_names[i] != NULL; i++) { + match_flag = FALSE; + if (exclude_names != NULL) { + for (j = 0; exclude_names[j] != 0; j++) { + if (strcmp(remove_names[i], exclude_names[j]) == 0) { + match_flag = TRUE; + break; + } + } + } + if (!match_flag) { + if (lstat(remove_names[i], &path_stat) < 0) { + continue; + } + if (S_ISDIR(path_stat.st_mode)) { + if (rmdir(remove_names[i]) != -1) { + remove_flag = TRUE; + } + } else { + if (unlink(remove_names[i]) != -1) { + remove_flag = TRUE; + } + } + } + } + return remove_flag; +} + +static int run_package_script(const char *package_name, const char *script_type) +{ + struct stat path_stat; + char *script_path; + int result; + + script_path = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, script_type); + + /* If the file doesnt exist is isnt a fatal */ + result = lstat(script_path, &path_stat) < 0 ? EXIT_SUCCESS : system(script_path); + free(script_path); + return result; +} + +static const char *all_control_files[] = {"preinst", "postinst", "prerm", "postrm", + "list", "md5sums", "shlibs", "conffiles", "config", "templates", NULL }; + +static char **all_control_list(const char *package_name) +{ + unsigned i = 0; + char **remove_files; + + /* Create a list of all /var/lib/dpkg/info/ files */ + remove_files = xzalloc(sizeof(all_control_files)); + while (all_control_files[i]) { + remove_files[i] = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, all_control_files[i]); + i++; + } + + return remove_files; +} + +static void free_array(char **array) +{ + + if (array) { + unsigned i = 0; + while (array[i]) { + free(array[i]); + i++; + } + free(array); + } +} + +/* This function lists information on the installed packages. It loops through + * the status_hashtable to retrieve the info. This results in smaller code than + * scanning the status file. The resulting list, however, is unsorted. + */ +static void list_packages(void) +{ + int i; + + puts(" Name Version"); + puts("+++-==============-=============="); + + /* go through status hash, dereference package hash and finally strings */ + for (i=0; istatus]; + name_str = name_hashtable[package_hashtable[status_hashtable[i]->package]->name]; + vers_str = name_hashtable[package_hashtable[status_hashtable[i]->package]->version]; + + /* get abbreviation for status field 1 */ + s1 = stat_str[0] == 'i' ? 'i' : 'r'; + + /* get abbreviation for status field 2 */ + for (j=0, spccnt=0; stat_str[j] && spccnt<2; j++) { + if (stat_str[j] == ' ') spccnt++; + } + s2 = stat_str[j]; + + /* print out the line formatted like Debian dpkg */ + printf("%c%c %-14s %s\n", s1, s2, name_str, vers_str); + } + } +} + +static void remove_package(const unsigned int package_num, int noisy) +{ + const char *package_name = name_hashtable[package_hashtable[package_num]->name]; + const char *package_version = name_hashtable[package_hashtable[package_num]->version]; + const unsigned int status_num = search_status_hashtable(package_name); + const int package_name_length = strlen(package_name); + char **remove_files; + char **exclude_files; + char list_name[package_name_length + 25]; + char conffile_name[package_name_length + 30]; + int return_value; + + if (noisy) + printf("Removing %s (%s)...\n", package_name, package_version); + + /* run prerm script */ + return_value = run_package_script(package_name, "prerm"); + if (return_value == -1) { + bb_error_msg_and_die("script failed, prerm failure"); + } + + /* Create a list of files to remove, and a separate list of those to keep */ + sprintf(list_name, "/var/lib/dpkg/info/%s.list", package_name); + remove_files = create_list(list_name); + + sprintf(conffile_name, "/var/lib/dpkg/info/%s.conffiles", package_name); + exclude_files = create_list(conffile_name); + + /* Some directories can't be removed straight away, so do multiple passes */ + while (remove_file_array(remove_files, exclude_files)) /*repeat */; + free_array(exclude_files); + free_array(remove_files); + + /* Create a list of files in /var/lib/dpkg/info/.* to keep */ + exclude_files = xzalloc(sizeof(char*) * 3); + exclude_files[0] = xstrdup(conffile_name); + exclude_files[1] = xasprintf("/var/lib/dpkg/info/%s.postrm", package_name); + + /* Create a list of all /var/lib/dpkg/info/ files */ + remove_files = all_control_list(package_name); + + remove_file_array(remove_files, exclude_files); + free_array(remove_files); + free_array(exclude_files); + + /* rename .conffile to .list */ + rename(conffile_name, list_name); + + /* Change package status */ + set_status(status_num, "config-files", 3); +} + +static void purge_package(const unsigned int package_num) +{ + const char *package_name = name_hashtable[package_hashtable[package_num]->name]; + const char *package_version = name_hashtable[package_hashtable[package_num]->version]; + const unsigned int status_num = search_status_hashtable(package_name); + char **remove_files; + char **exclude_files; + char list_name[strlen(package_name) + 25]; + + printf("Purging %s (%s)...\n", package_name, package_version); + + /* run prerm script */ + if (run_package_script(package_name, "prerm") != 0) { + bb_error_msg_and_die("script failed, prerm failure"); + } + + /* Create a list of files to remove */ + sprintf(list_name, "/var/lib/dpkg/info/%s.list", package_name); + remove_files = create_list(list_name); + + exclude_files = xzalloc(sizeof(char*)); + + /* Some directories cant be removed straight away, so do multiple passes */ + while (remove_file_array(remove_files, exclude_files)) /* repeat */; + free_array(remove_files); + + /* Create a list of all /var/lib/dpkg/info/ files */ + remove_files = all_control_list(package_name); + remove_file_array(remove_files, exclude_files); + free_array(remove_files); + free(exclude_files); + + /* run postrm script */ + if (run_package_script(package_name, "postrm") == -1) { + bb_error_msg_and_die("postrm fialure.. set status to what?"); + } + + /* Change package status */ + set_status(status_num, "not-installed", 3); +} + +static archive_handle_t *init_archive_deb_ar(const char *filename) +{ + archive_handle_t *ar_handle; + + /* Setup an ar archive handle that refers to the gzip sub archive */ + ar_handle = init_handle(); + ar_handle->filter = filter_accept_list_reassign; + ar_handle->src_fd = xopen(filename, O_RDONLY); + + return ar_handle; +} + +static void init_archive_deb_control(archive_handle_t *ar_handle) +{ + archive_handle_t *tar_handle; + + /* Setup the tar archive handle */ + tar_handle = init_handle(); + tar_handle->src_fd = ar_handle->src_fd; + + /* We don't care about data.tar.* or debian-binary, just control.tar.* */ +#ifdef CONFIG_FEATURE_DEB_TAR_GZ + llist_add_to(&(ar_handle->accept), "control.tar.gz"); +#endif +#ifdef CONFIG_FEATURE_DEB_TAR_BZ2 + llist_add_to(&(ar_handle->accept), "control.tar.bz2"); +#endif + + /* Assign the tar handle as a subarchive of the ar handle */ + ar_handle->sub_archive = tar_handle; + + return; +} + +static void init_archive_deb_data(archive_handle_t *ar_handle) +{ + archive_handle_t *tar_handle; + + /* Setup the tar archive handle */ + tar_handle = init_handle(); + tar_handle->src_fd = ar_handle->src_fd; + + /* We don't care about control.tar.* or debian-binary, just data.tar.* */ +#ifdef CONFIG_FEATURE_DEB_TAR_GZ + llist_add_to(&(ar_handle->accept), "data.tar.gz"); +#endif +#ifdef CONFIG_FEATURE_DEB_TAR_BZ2 + llist_add_to(&(ar_handle->accept), "data.tar.bz2"); +#endif + + /* Assign the tar handle as a subarchive of the ar handle */ + ar_handle->sub_archive = tar_handle; + + return; +} + +static char *deb_extract_control_file_to_buffer(archive_handle_t *ar_handle, llist_t *myaccept) +{ + ar_handle->sub_archive->action_data = data_extract_to_buffer; + ar_handle->sub_archive->accept = myaccept; + ar_handle->sub_archive->filter = filter_accept_list; + + unpack_ar_archive(ar_handle); + close(ar_handle->src_fd); + + return ar_handle->sub_archive->buffer; +} + +static void data_extract_all_prefix(archive_handle_t *archive_handle) +{ + char *name_ptr = archive_handle->file_header->name; + + name_ptr += strspn(name_ptr, "./"); + if (name_ptr[0] != '\0') { + archive_handle->file_header->name = xasprintf("%s%s", archive_handle->buffer, name_ptr); + data_extract_all(archive_handle); + } + return; +} + +static void unpack_package(deb_file_t *deb_file) +{ + const char *package_name = name_hashtable[package_hashtable[deb_file->package]->name]; + const unsigned int status_num = search_status_hashtable(package_name); + const unsigned int status_package_num = status_hashtable[status_num]->package; + char *info_prefix; + char *list_filename; + archive_handle_t *archive_handle; + FILE *out_stream; + llist_t *accept_list = NULL; + int i = 0; + + /* If existing version, remove it first */ + if (strcmp(name_hashtable[get_status(status_num, 3)], "installed") == 0) { + /* Package is already installed, remove old version first */ + printf("Preparing to replace %s %s (using %s)...\n", package_name, + name_hashtable[package_hashtable[status_package_num]->version], + deb_file->filename); + remove_package(status_package_num, 0); + } else { + printf("Unpacking %s (from %s)...\n", package_name, deb_file->filename); + } + + /* Extract control.tar.gz to /var/lib/dpkg/info/.filename */ + info_prefix = xasprintf("/var/lib/dpkg/info/%s.", package_name); + archive_handle = init_archive_deb_ar(deb_file->filename); + init_archive_deb_control(archive_handle); + + while (all_control_files[i]) { + char *c = xasprintf("./%s", all_control_files[i]); + llist_add_to(&accept_list, c); + i++; + } + archive_handle->sub_archive->accept = accept_list; + archive_handle->sub_archive->filter = filter_accept_list; + archive_handle->sub_archive->action_data = data_extract_all_prefix; + archive_handle->sub_archive->buffer = info_prefix; + archive_handle->sub_archive->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL; + unpack_ar_archive(archive_handle); + + /* Run the preinst prior to extracting */ + if (run_package_script(package_name, "preinst") != 0) { + /* when preinst returns exit code != 0 then quit installation process */ + bb_error_msg_and_die("subprocess pre-installation script returned error"); + } + + /* Extract data.tar.gz to the root directory */ + archive_handle = init_archive_deb_ar(deb_file->filename); + init_archive_deb_data(archive_handle); + archive_handle->sub_archive->action_data = data_extract_all_prefix; + archive_handle->sub_archive->buffer = "/"; + archive_handle->sub_archive->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL; + unpack_ar_archive(archive_handle); + + /* Create the list file */ + list_filename = xasprintf("/var/lib/dpkg/info/%s.list", package_name); + out_stream = xfopen(list_filename, "w"); + while (archive_handle->sub_archive->passed) { + /* the leading . has been stripped by data_extract_all_prefix already */ + fputs(archive_handle->sub_archive->passed->data, out_stream); + fputc('\n', out_stream); + archive_handle->sub_archive->passed = archive_handle->sub_archive->passed->link; + } + fclose(out_stream); + + /* change status */ + set_status(status_num, "install", 1); + set_status(status_num, "unpacked", 3); + + free(info_prefix); + free(list_filename); +} + +static void configure_package(deb_file_t *deb_file) +{ + const char *package_name = name_hashtable[package_hashtable[deb_file->package]->name]; + const char *package_version = name_hashtable[package_hashtable[deb_file->package]->version]; + const int status_num = search_status_hashtable(package_name); + + printf("Setting up %s (%s)...\n", package_name, package_version); + + /* Run the postinst script */ + if (run_package_script(package_name, "postinst") != 0) { + /* TODO: handle failure gracefully */ + bb_error_msg_and_die("postrm failure.. set status to what?"); + } + /* Change status to reflect success */ + set_status(status_num, "install", 1); + set_status(status_num, "installed", 3); +} + +int dpkg_main(int argc, char **argv) +{ + deb_file_t **deb_file = NULL; + status_node_t *status_node; + int opt; + int package_num; + int dpkg_opt = 0; + int deb_count = 0; + int state_status; + int status_num; + int i; + + name_hashtable = xzalloc(sizeof(name_hashtable[0]) * (NAME_HASH_PRIME + 1)); + package_hashtable = xzalloc(sizeof(package_hashtable[0]) * (PACKAGE_HASH_PRIME + 1)); + status_hashtable = xzalloc(sizeof(status_hashtable[0]) * (STATUS_HASH_PRIME + 1)); + + while ((opt = getopt(argc, argv, "CF:ilPru")) != -1) { + switch (opt) { + case 'C': // equivalent to --configure in official dpkg + dpkg_opt |= dpkg_opt_configure; + dpkg_opt |= dpkg_opt_package_name; + break; + case 'F': // equivalent to --force in official dpkg + if (strcmp(optarg, "depends") == 0) { + dpkg_opt |= dpkg_opt_force_ignore_depends; + } + break; + case 'i': + dpkg_opt |= dpkg_opt_install; + dpkg_opt |= dpkg_opt_filename; + break; + case 'l': + dpkg_opt |= dpkg_opt_list_installed; + break; + case 'P': + dpkg_opt |= dpkg_opt_purge; + dpkg_opt |= dpkg_opt_package_name; + break; + case 'r': + dpkg_opt |= dpkg_opt_remove; + dpkg_opt |= dpkg_opt_package_name; + break; + case 'u': /* Equivalent to --unpack in official dpkg */ + dpkg_opt |= dpkg_opt_unpack; + dpkg_opt |= dpkg_opt_filename; + break; + default: + bb_show_usage(); + } + } + /* check for non-option argument if expected */ + if ((dpkg_opt == 0) || ((argc == optind) && !(dpkg_opt && dpkg_opt_list_installed))) { + bb_show_usage(); + } + +/* puts("(Reading database ... xxxxx files and directories installed.)"); */ + index_status_file("/var/lib/dpkg/status"); + + /* if the list action was given print the installed packages and exit */ + if (dpkg_opt & dpkg_opt_list_installed) { + list_packages(); + return EXIT_SUCCESS; + } + + /* Read arguments and store relevant info in structs */ + while (optind < argc) { + /* deb_count = nb_elem - 1 and we need nb_elem + 1 to allocate terminal node [NULL pointer] */ + deb_file = xrealloc(deb_file, sizeof(deb_file_t *) * (deb_count + 2)); + deb_file[deb_count] = (deb_file_t *) xzalloc(sizeof(deb_file_t)); + if (dpkg_opt & dpkg_opt_filename) { + archive_handle_t *archive_handle; + llist_t *control_list = NULL; + + /* Extract the control file */ + llist_add_to(&control_list, "./control"); + archive_handle = init_archive_deb_ar(argv[optind]); + init_archive_deb_control(archive_handle); + deb_file[deb_count]->control_file = deb_extract_control_file_to_buffer(archive_handle, control_list); + if (deb_file[deb_count]->control_file == NULL) { + bb_error_msg_and_die("cannot extract control file"); + } + deb_file[deb_count]->filename = xstrdup(argv[optind]); + package_num = fill_package_struct(deb_file[deb_count]->control_file); + + if (package_num == -1) { + bb_error_msg("invalid control file in %s", argv[optind]); + optind++; + continue; + } + deb_file[deb_count]->package = (unsigned int) package_num; + + /* Add the package to the status hashtable */ + if ((dpkg_opt & dpkg_opt_unpack) || (dpkg_opt & dpkg_opt_install)) { + /* Try and find a currently installed version of this package */ + status_num = search_status_hashtable(name_hashtable[package_hashtable[deb_file[deb_count]->package]->name]); + /* If no previous entry was found initialise a new entry */ + if ((status_hashtable[status_num] == NULL) || + (status_hashtable[status_num]->status == 0)) { + status_node = (status_node_t *) xmalloc(sizeof(status_node_t)); + status_node->package = deb_file[deb_count]->package; + /* reinstreq isnt changed to "ok" until the package control info + * is written to the status file*/ + status_node->status = search_name_hashtable("install reinstreq not-installed"); + status_hashtable[status_num] = status_node; + } else { + set_status(status_num, "install", 1); + set_status(status_num, "reinstreq", 2); + } + } + } + else if (dpkg_opt & dpkg_opt_package_name) { + deb_file[deb_count]->package = search_package_hashtable( + search_name_hashtable(argv[optind]), + search_name_hashtable("ANY"), VER_ANY); + if (package_hashtable[deb_file[deb_count]->package] == NULL) { + bb_error_msg_and_die("package %s is uninstalled or unknown", argv[optind]); + } + package_num = deb_file[deb_count]->package; + status_num = search_status_hashtable(name_hashtable[package_hashtable[package_num]->name]); + state_status = get_status(status_num, 3); + + /* check package status is "installed" */ + if (dpkg_opt & dpkg_opt_remove) { + if ((strcmp(name_hashtable[state_status], "not-installed") == 0) || + (strcmp(name_hashtable[state_status], "config-files") == 0)) { + bb_error_msg_and_die("%s is already removed", name_hashtable[package_hashtable[package_num]->name]); + } + set_status(status_num, "deinstall", 1); + } + else if (dpkg_opt & dpkg_opt_purge) { + /* if package status is "conf-files" then its ok */ + if (strcmp(name_hashtable[state_status], "not-installed") == 0) { + bb_error_msg_and_die("%s is already purged", name_hashtable[package_hashtable[package_num]->name]); + } + set_status(status_num, "purge", 1); + } + } + deb_count++; + optind++; + } + deb_file[deb_count] = NULL; + + /* Check that the deb file arguments are installable */ + if ((dpkg_opt & dpkg_opt_force_ignore_depends) != dpkg_opt_force_ignore_depends) { + if (!check_deps(deb_file, 0, deb_count)) { + bb_error_msg_and_die("dependency check failed"); + } + } + + /* TODO: install or remove packages in the correct dependency order */ + for (i = 0; i < deb_count; i++) { + /* Remove or purge packages */ + if (dpkg_opt & dpkg_opt_remove) { + remove_package(deb_file[i]->package, 1); + } + else if (dpkg_opt & dpkg_opt_purge) { + purge_package(deb_file[i]->package); + } + else if (dpkg_opt & dpkg_opt_unpack) { + unpack_package(deb_file[i]); + } + else if (dpkg_opt & dpkg_opt_install) { + unpack_package(deb_file[i]); + /* package is configured in second pass below */ + } + else if (dpkg_opt & dpkg_opt_configure) { + configure_package(deb_file[i]); + } + } + /* configure installed packages */ + if (dpkg_opt & dpkg_opt_install) { + for (i = 0; i < deb_count; i++) + configure_package(deb_file[i]); + } + + write_status_file(deb_file); + + if (ENABLE_FEATURE_CLEAN_UP) { + for (i = 0; i < deb_count; i++) { + free(deb_file[i]->control_file); + free(deb_file[i]->filename); + free(deb_file[i]); + } + + free(deb_file); + + for (i = 0; i < NAME_HASH_PRIME; i++) { + free(name_hashtable[i]); + } + + for (i = 0; i < PACKAGE_HASH_PRIME; i++) { + if (package_hashtable[i] != NULL) { + free_package(package_hashtable[i]); + } + } + + for (i = 0; i < STATUS_HASH_PRIME; i++) { + free(status_hashtable[i]); + } + + free(status_hashtable); + free(package_hashtable); + free(name_hashtable); + } + + return EXIT_SUCCESS; +} diff --git a/archival/dpkg_deb.c b/archival/dpkg_deb.c new file mode 100644 index 000000000..d11d9df17 --- /dev/null +++ b/archival/dpkg_deb.c @@ -0,0 +1,96 @@ +/* vi: set sw=4 ts=4: */ +/* + * dpkg-deb packs, unpacks and provides information about Debian archives. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include "busybox.h" +#include "unarchive.h" + +#define DPKG_DEB_OPT_CONTENTS 1 +#define DPKG_DEB_OPT_CONTROL 2 +#define DPKG_DEB_OPT_FIELD 4 +#define DPKG_DEB_OPT_EXTRACT 8 +#define DPKG_DEB_OPT_EXTRACT_VERBOSE 16 + +int dpkg_deb_main(int argc, char **argv) +{ + archive_handle_t *ar_archive; + archive_handle_t *tar_archive; + llist_t *control_tar_llist = NULL; + unsigned opt; + char *extract_dir = NULL; + short argcount = 1; + + /* Setup the tar archive handle */ + tar_archive = init_handle(); + + /* Setup an ar archive handle that refers to the gzip sub archive */ + ar_archive = init_handle(); + ar_archive->sub_archive = tar_archive; + ar_archive->filter = filter_accept_list_reassign; + +#ifdef CONFIG_FEATURE_DEB_TAR_GZ + llist_add_to(&(ar_archive->accept), "data.tar.gz"); + llist_add_to(&control_tar_llist, "control.tar.gz"); +#endif + +#ifdef CONFIG_FEATURE_DEB_TAR_BZ2 + llist_add_to(&(ar_archive->accept), "data.tar.bz2"); + llist_add_to(&control_tar_llist, "control.tar.bz2"); +#endif + + opt_complementary = "?c--efXx:e--cfXx:f--ceXx:X--cefx:x--cefX"; + opt = getopt32(argc, argv, "cefXx"); + + if (opt & DPKG_DEB_OPT_CONTENTS) { + tar_archive->action_header = header_verbose_list; + } + if (opt & DPKG_DEB_OPT_CONTROL) { + ar_archive->accept = control_tar_llist; + tar_archive->action_data = data_extract_all; + if (optind + 1 == argc) { + extract_dir = "./DEBIAN"; + } else { + argcount++; + } + } + if (opt & DPKG_DEB_OPT_FIELD) { + /* Print the entire control file + * it should accept a second argument which specifies a + * specific field to print */ + ar_archive->accept = control_tar_llist; + llist_add_to(&(tar_archive->accept), "./control"); + tar_archive->filter = filter_accept_list; + tar_archive->action_data = data_extract_to_stdout; + } + if (opt & DPKG_DEB_OPT_EXTRACT) { + tar_archive->action_header = header_list; + } + if (opt & (DPKG_DEB_OPT_EXTRACT_VERBOSE | DPKG_DEB_OPT_EXTRACT)) { + tar_archive->action_data = data_extract_all; + argcount = 2; + } + + if ((optind + argcount) != argc) { + bb_show_usage(); + } + + tar_archive->src_fd = ar_archive->src_fd = xopen(argv[optind++], O_RDONLY); + + /* Workout where to extract the files */ + /* 2nd argument is a dir name */ + if (argv[optind]) { + extract_dir = argv[optind]; + } + if (extract_dir) { + mkdir(extract_dir, 0777); /* bb_make_directory(extract_dir, 0777, 0) */ + xchdir(extract_dir); + } + unpack_ar_archive(ar_archive); + + /* Cleanup */ + close(ar_archive->src_fd); + + return EXIT_SUCCESS; +} diff --git a/archival/gunzip.c b/archival/gunzip.c new file mode 100644 index 000000000..e24401c71 --- /dev/null +++ b/archival/gunzip.c @@ -0,0 +1,162 @@ +/* vi: set sw=4 ts=4: */ +/* + * Gzip implementation for busybox + * + * Based on GNU gzip v1.2.4 Copyright (C) 1992-1993 Jean-loup Gailly. + * + * Originally adjusted for busybox by Sven Rudolph + * based on gzip sources + * + * Adjusted further by Erik Andersen to support files as + * well as stdin/stdout, and to generally behave itself wrt command line + * handling. + * + * General cleanup to better adhere to the style guide and make use of standard + * busybox functions by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface + * Copyright (C) 1992-1993 Jean-loup Gailly + * The unzip code was written and put in the public domain by Mark Adler. + * Portions of the lzw code are derived from the public domain 'compress' + * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies, + * Ken Turkowski, Dave Mack and Peter Jannesen. + * + * See the license_msg below and the file COPYING for the software license. + * See the file algorithm.doc for the compression algorithms and file formats. + */ + +#include "busybox.h" +#include "unarchive.h" + +#define GUNZIP_OPT_STDOUT 1 +#define GUNZIP_OPT_FORCE 2 +#define GUNZIP_OPT_TEST 4 +#define GUNZIP_OPT_DECOMPRESS 8 +#define GUNZIP_OPT_VERBOSE 0x10 + +int gunzip_main(int argc, char **argv) +{ + USE_DESKTOP(long long) int status; + int exitcode = 0; + unsigned opt; + + opt = getopt32(argc, argv, "cftdv"); + /* if called as zcat */ + if (strcmp(applet_name, "zcat") == 0) { + opt |= GUNZIP_OPT_STDOUT; + } + + do { + struct stat stat_buf; + char *old_path = argv[optind]; + char *delete_path = NULL; + char *new_path = NULL; + int src_fd; + int dst_fd; + + optind++; + + if (old_path == NULL || strcmp(old_path, "-") == 0) { + src_fd = STDIN_FILENO; + opt |= GUNZIP_OPT_STDOUT; + USE_DESKTOP(opt &= ~GUNZIP_OPT_VERBOSE;) + optind = argc; /* we don't handle "gunzip - a.gz b.gz" */ + } else { + src_fd = xopen(old_path, O_RDONLY); + /* Get the time stamp on the input file. */ + fstat(src_fd, &stat_buf); + } + + /* Check that the input is sane. */ + if (isatty(src_fd) && !(opt & GUNZIP_OPT_FORCE)) { + bb_error_msg_and_die + ("compressed data not read from terminal, use -f to force it"); + } + + /* Set output filename and number */ + if (opt & GUNZIP_OPT_TEST) { + dst_fd = xopen(bb_dev_null, O_WRONLY); /* why does test use filenum 2 ? */ + } else if (opt & GUNZIP_OPT_STDOUT) { + dst_fd = STDOUT_FILENO; + } else { + char *extension; + + new_path = xstrdup(old_path); + + extension = strrchr(new_path, '.'); +#ifdef CONFIG_FEATURE_GUNZIP_UNCOMPRESS + if (extension && (strcmp(extension, ".Z") == 0)) { + *extension = '\0'; + } else +#endif + if (extension && (strcmp(extension, ".gz") == 0)) { + *extension = '\0'; + } else if (extension && (strcmp(extension, ".tgz") == 0)) { + extension[2] = 'a'; + extension[3] = 'r'; + } else { + // FIXME: should we die or just skip to next? + bb_error_msg_and_die("invalid extension"); + } + + /* Open output file (with correct permissions) */ + dst_fd = xopen3(new_path, O_WRONLY | O_CREAT | O_TRUNC, + stat_buf.st_mode); + + /* If unzip succeeds remove the old file */ + delete_path = old_path; + } + + status = -1; + /* do the decompression, and cleanup */ + if (xread_char(src_fd) == 0x1f) { + unsigned char magic2; + + magic2 = xread_char(src_fd); + if (ENABLE_FEATURE_GUNZIP_UNCOMPRESS && magic2 == 0x9d) { + status = uncompress(src_fd, dst_fd); + } else if (magic2 == 0x8b) { + check_header_gzip(src_fd); // FIXME: xfunc? _or_die? + status = inflate_gunzip(src_fd, dst_fd); + } else { + bb_error_msg("invalid magic"); + exitcode = 1; + } + if (status < 0) { + bb_error_msg("error inflating"); + exitcode = 1; + } + else if (ENABLE_DESKTOP && (opt & GUNZIP_OPT_VERBOSE)) { + fprintf(stderr, "%s: %u%% - replaced with %s\n", + old_path, (unsigned)(stat_buf.st_size*100 / (status+1)), new_path); + } + } else { + bb_error_msg("invalid magic"); + exitcode = 1; + } + if (status < 0 && new_path) { + /* Unzip failed, remove new path instead of old path */ + delete_path = new_path; + } + + if (dst_fd != STDOUT_FILENO) { + close(dst_fd); + } + if (src_fd != STDIN_FILENO) { + close(src_fd); + } + + /* delete_path will be NULL if in test mode or from stdin */ + if (delete_path && (unlink(delete_path) == -1)) { + bb_error_msg("cannot remove %s", delete_path); + exitcode = 1; + } + + free(new_path); + + } while (optind < argc); + + return exitcode; +} diff --git a/archival/gzip.c b/archival/gzip.c new file mode 100644 index 000000000..c9b67538e --- /dev/null +++ b/archival/gzip.c @@ -0,0 +1,2473 @@ +/* vi: set sw=4 ts=4: */ +/* + * Gzip implementation for busybox + * + * Based on GNU gzip Copyright (C) 1992-1993 Jean-loup Gailly. + * + * Originally adjusted for busybox by Charles P. Wright + * "this is a stripped down version of gzip I put into busybox, it does + * only standard in to standard out with -9 compression. It also requires + * the zcat module for some important functions." + * + * Adjusted further by Erik Andersen to support + * files as well as stdin/stdout, and to generally behave itself wrt + * command line handling. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* TODO: full support for -v for DESKTOP +/usr/bin/gzip -v a bogus aa +a: 85.1% -- replaced with a.gz +gzip: bogus: No such file or directory +aa: 85.1% -- replaced with aa.gz +*/ + +#define SMALL_MEM + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +typedef unsigned char uch; +typedef unsigned short ush; +typedef unsigned long ulg; + +/* Return codes from gzip */ +#define OK 0 +#define ERROR 1 +#define WARNING 2 + +/* Compression methods (see algorithm.doc) */ +/* Only STORED and DEFLATED are supported by this BusyBox module */ +#define STORED 0 +/* methods 4 to 7 reserved */ +#define DEFLATED 8 + +/* To save memory for 16 bit systems, some arrays are overlaid between + * the various modules: + * deflate: prev+head window d_buf l_buf outbuf + * unlzw: tab_prefix tab_suffix stack inbuf outbuf + * For compression, input is done in window[]. For decompression, output + * is done in window except for unlzw. + */ + +#ifndef INBUFSIZ +# ifdef SMALL_MEM +# define INBUFSIZ 0x2000 /* input buffer size */ +# else +# define INBUFSIZ 0x8000 /* input buffer size */ +# endif +#endif +#define INBUF_EXTRA 64 /* required by unlzw() */ + +#ifndef OUTBUFSIZ +# ifdef SMALL_MEM +# define OUTBUFSIZ 8192 /* output buffer size */ +# else +# define OUTBUFSIZ 16384 /* output buffer size */ +# endif +#endif +#define OUTBUF_EXTRA 2048 /* required by unlzw() */ + +#ifndef DIST_BUFSIZE +# ifdef SMALL_MEM +# define DIST_BUFSIZE 0x2000 /* buffer for distances, see trees.c */ +# else +# define DIST_BUFSIZE 0x8000 /* buffer for distances, see trees.c */ +# endif +#endif + +# define DECLARE(type, array, size) static type * array +# define ALLOC(type, array, size) { \ + array = (type*)xzalloc((size_t)(((size)+1L)/2) * 2*sizeof(type)); \ + } +# define FREE(array) {free(array), array=NULL;} + +#define tab_suffix window +#define tab_prefix prev /* hash link (see deflate.c) */ +#define head (prev+WSIZE) /* hash head (see deflate.c) */ + +static long bytes_in; /* number of input bytes */ + +#define isize bytes_in +/* for compatibility with old zip sources (to be cleaned) */ + +typedef int file_t; /* Do not use stdio */ + +#define NO_FILE (-1) /* in memory compression */ + + +#define PACK_MAGIC "\037\036" /* Magic header for packed files */ +#define GZIP_MAGIC "\037\213" /* Magic header for gzip files, 1F 8B */ +#define OLD_GZIP_MAGIC "\037\236" /* Magic header for gzip 0.5 = freeze 1.x */ +#define LZH_MAGIC "\037\240" /* Magic header for SCO LZH Compress files */ +#define PKZIP_MAGIC "\120\113\003\004" /* Magic header for pkzip files */ + +/* gzip flag byte */ +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define CONTINUATION 0x02 /* bit 1 set: continuation of multi-part gzip file */ +#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define COMMENT 0x10 /* bit 4 set: file comment present */ +#define RESERVED 0xC0 /* bit 6,7: reserved */ + +/* internal file attribute */ +#define UNKNOWN 0xffff +#define BINARY 0 +#define ASCII 1 + +#ifndef WSIZE +# define WSIZE 0x8000 /* window size--must be a power of two, and */ +#endif /* at least 32K for zip's deflate method */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +#define MAX_DIST (WSIZE-MIN_LOOKAHEAD) +/* In order to simplify the code, particularly on 16 bit machines, match + * distances are limited to MAX_DIST instead of WSIZE. + */ + +/* put_byte is used for the compressed output */ +#define put_byte(c) {outbuf[outcnt++]=(uch)(c); if (outcnt==OUTBUFSIZ)\ + flush_outbuf();} + +#define seekable() 0 /* force sequential output */ +#define translate_eol 0 /* no option -a yet */ + +/* Diagnostic functions */ +#ifdef DEBUG +# define Assert(cond,msg) {if(!(cond)) bb_error_msg(msg);} +# define Trace(x) fprintf x +# define Tracev(x) {if (verbose) fprintf x ;} +# define Tracevv(x) {if (verbose>1) fprintf x ;} +# define Tracec(c,x) {if (verbose && (c)) fprintf x ;} +# define Tracecv(c,x) {if (verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + +#define WARN(msg) {if (!quiet) fprintf msg ; \ + if (exit_code == OK) exit_code = WARNING;} + +#ifndef MAX_PATH_LEN +# define MAX_PATH_LEN 1024 /* max pathname length */ +#endif + + + /* from zip.c: */ +static int zip(int in, int out); +static int file_read(char *buf, unsigned size); + + /* from deflate.c */ +static void lm_init(ush * flags); +static ulg deflate(void); + + /* from trees.c */ +static void ct_init(ush * attr, int *methodp); +static int ct_tally(int dist, int lc); +static ulg flush_block(char *buf, ulg stored_len, int eof); + + /* from bits.c */ +static void bi_init(file_t zipfile); +static void send_bits(int value, int length); +static unsigned bi_reverse(unsigned value, int length); +static void bi_windup(void); +static void copy_block(char *buf, unsigned len, int header); +static int (*read_buf) (char *buf, unsigned size); + + /* from util.c: */ +static void flush_outbuf(void); + +/* lzw.h -- define the lzw functions. + * Copyright (C) 1992-1993 Jean-loup Gailly. + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + +#ifndef BITS +# define BITS 16 +#endif +#define INIT_BITS 9 /* Initial number of bits per code */ + +#define BIT_MASK 0x1f /* Mask for 'number of compression bits' */ +/* Mask 0x20 is reserved to mean a fourth header byte, and 0x40 is free. + * It's a pity that old uncompress does not check bit 0x20. That makes + * extension of the format actually undesirable because old compress + * would just crash on the new format instead of giving a meaningful + * error message. It does check the number of bits, but it's more + * helpful to say "unsupported format, get a new version" than + * "can only handle 16 bits". + */ + +/* tailor.h -- target dependent definitions + * Copyright (C) 1992-1993 Jean-loup Gailly. + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + +/* The target dependent definitions should be defined here only. + * The target dependent functions should be defined in tailor.c. + */ + + + /* Common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef PATH_SEP +# define PATH_SEP '/' +#endif + +#ifndef OPTIONS_VAR +# define OPTIONS_VAR "GZIP" +#endif + +#ifndef Z_SUFFIX +# define Z_SUFFIX ".gz" +#endif + +#ifdef MAX_EXT_CHARS +# define MAX_SUFFIX MAX_EXT_CHARS +#else +# define MAX_SUFFIX 30 +#endif + + /* global buffers */ + +DECLARE(uch, inbuf, INBUFSIZ + INBUF_EXTRA); +DECLARE(uch, outbuf, OUTBUFSIZ + OUTBUF_EXTRA); +DECLARE(ush, d_buf, DIST_BUFSIZE); +DECLARE(uch, window, 2L * WSIZE); +DECLARE(ush, tab_prefix, 1L << BITS); + +static int foreground; /* set if program run in foreground */ +static int method = DEFLATED; /* compression method */ +static int exit_code = OK; /* program exit code */ +static long time_stamp; /* original time stamp (modification time) */ +static char z_suffix[MAX_SUFFIX + 1]; /* default suffix (can be set with --suffix) */ + +static int ifd; /* input file descriptor */ +static int ofd; /* output file descriptor */ +#ifdef DEBUG +static unsigned insize; /* valid bytes in inbuf */ +#endif +static unsigned outcnt; /* bytes in output buffer */ + +static uint32_t *crc_32_tab; + +/* Output a 16 bit value, lsb first */ +static void put_short(ush w) +{ + if (outcnt < OUTBUFSIZ - 2) { + outbuf[outcnt++] = (uch) ((w) & 0xff); + outbuf[outcnt++] = (uch) ((ush) (w) >> 8); + } else { + put_byte((uch) ((w) & 0xff)); + put_byte((uch) ((ush) (w) >> 8)); + } +} + +/* ======================================================================== + * Signal and error handler. + */ +static void abort_gzip(int ATTRIBUTE_UNUSED ignored) +{ + exit(ERROR); +} + +/* =========================================================================== + * Clear input and output buffers + */ +static void clear_bufs(void) +{ + outcnt = 0; +#ifdef DEBUG + insize = 0; +#endif + bytes_in = 0L; +} + +/* =========================================================================== + * Does the same as write(), but also handles partial pipe writes and checks + * for error return. + */ +static void write_buf(int fd, void *buf, unsigned cnt) +{ + unsigned n; + + while ((n = write(fd, buf, cnt)) != cnt) { + if (n == (unsigned) (-1)) bb_error_msg_and_die(bb_msg_write_error); + cnt -= n; + buf = (void *) ((char *) buf + n); + } +} + +/* =========================================================================== + * Run a set of bytes through the crc shift register. If s is a NULL + * pointer, then initialize the crc shift register contents instead. + * Return the current crc in either case. + */ +static uint32_t updcrc(uch * s, unsigned n) +{ + static uint32_t crc = ~0; /* shift register contents */ + uint32_t c; /* temporary variable */ + + if (s == NULL) { + c = ~0; + } else { + c = crc; + if (n) + do { + c = crc_32_tab[((int) c ^ (*s++)) & 0xff] ^ (c >> 8); + } while (--n); + } + crc = c; + return ~c; +} + +/* bits.c -- output variable-length bit strings + * Copyright (C) 1992-1993 Jean-loup Gailly + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + + +/* + * PURPOSE + * + * Output variable-length bit strings. Compression can be done + * to a file or to memory. (The latter is not supported in this version.) + * + * DISCUSSION + * + * The PKZIP "deflate" file format interprets compressed file data + * as a sequence of bits. Multi-bit strings in the file may cross + * byte boundaries without restriction. + * + * The first bit of each byte is the low-order bit. + * + * The routines in this file allow a variable-length bit value to + * be output right-to-left (useful for literal values). For + * left-to-right output (useful for code strings from the tree routines), + * the bits must have been reversed first with bi_reverse(). + * + * For in-memory compression, the compressed bit stream goes directly + * into the requested output buffer. The input data is read in blocks + * by the mem_read() function. The buffer is limited to 64K on 16 bit + * machines. + * + * INTERFACE + * + * void bi_init (FILE *zipfile) + * Initialize the bit string routines. + * + * void send_bits (int value, int length) + * Write out a bit string, taking the source bits right to + * left. + * + * int bi_reverse (int value, int length) + * Reverse the bits of a bit string, taking the source bits left to + * right and emitting them right to left. + * + * void bi_windup (void) + * Write out any remaining bits in an incomplete byte. + * + * void copy_block(char *buf, unsigned len, int header) + * Copy a stored block to the zip file, storing first the length and + * its one's complement if requested. + * + */ + +/* =========================================================================== + * Local data used by the "bit string" routines. + */ + +static file_t zfile; /* output gzip file */ + +static unsigned short bi_buf; + +/* Output buffer. bits are inserted starting at the bottom (least significant + * bits). + */ + +#define Buf_size (8 * 2*sizeof(char)) +/* Number of bits used within bi_buf. (bi_buf might be implemented on + * more than 16 bits on some systems.) + */ + +static int bi_valid; + +/* Current input function. Set to mem_read for in-memory compression */ + +#ifdef DEBUG +ulg bits_sent; /* bit length of the compressed data */ +#endif + +/* =========================================================================== + * Initialize the bit string routines. + */ +static void bi_init(file_t zipfile) +{ + zfile = zipfile; + bi_buf = 0; + bi_valid = 0; +#ifdef DEBUG + bits_sent = 0L; +#endif + + /* Set the defaults for file compression. They are set by memcompress + * for in-memory compression. + */ + if (zfile != NO_FILE) { + read_buf = file_read; + } +} + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +static void send_bits(int value, int length) +{ +#ifdef DEBUG + Tracev((stderr, " l %2d v %4x ", length, value)); + Assert(length > 0 && length <= 15, "invalid length"); + bits_sent += (ulg) length; +#endif + /* If not enough room in bi_buf, use (valid) bits from bi_buf and + * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * unused bits in value. + */ + if (bi_valid > (int) Buf_size - length) { + bi_buf |= (value << bi_valid); + put_short(bi_buf); + bi_buf = (ush) value >> (Buf_size - bi_valid); + bi_valid += length - Buf_size; + } else { + bi_buf |= value << bi_valid; + bi_valid += length; + } +} + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +static unsigned bi_reverse(unsigned code, int len) +{ + unsigned res = 0; + + do { + res |= code & 1; + code >>= 1, res <<= 1; + } while (--len > 0); + return res >> 1; +} + +/* =========================================================================== + * Write out any remaining bits in an incomplete byte. + */ +static void bi_windup(void) +{ + if (bi_valid > 8) { + put_short(bi_buf); + } else if (bi_valid > 0) { + put_byte(bi_buf); + } + bi_buf = 0; + bi_valid = 0; +#ifdef DEBUG + bits_sent = (bits_sent + 7) & ~7; +#endif +} + +/* =========================================================================== + * Copy a stored block to the zip file, storing first the length and its + * one's complement if requested. + */ +static void copy_block(char *buf, unsigned len, int header) +{ + bi_windup(); /* align on byte boundary */ + + if (header) { + put_short((ush) len); + put_short((ush) ~ len); +#ifdef DEBUG + bits_sent += 2 * 16; +#endif + } +#ifdef DEBUG + bits_sent += (ulg) len << 3; +#endif + while (len--) { + put_byte(*buf++); + } +} + +/* deflate.c -- compress data using the deflation algorithm + * Copyright (C) 1992-1993 Jean-loup Gailly + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + +/* + * PURPOSE + * + * Identify new text as repetitions of old text within a fixed- + * length sliding window trailing behind the new text. + * + * DISCUSSION + * + * The "deflation" process depends on being able to identify portions + * of the input text which are identical to earlier input (within a + * sliding window trailing behind the input currently being processed). + * + * The most straightforward technique turns out to be the fastest for + * most input files: try all possible matches and select the longest. + * The key feature of this algorithm is that insertions into the string + * dictionary are very simple and thus fast, and deletions are avoided + * completely. Insertions are performed at each input character, whereas + * string matches are performed only when the previous match ends. So it + * is preferable to spend more time in matches to allow very fast string + * insertions and avoid deletions. The matching algorithm for small + * strings is inspired from that of Rabin & Karp. A brute force approach + * is used to find longer strings when a small match has been found. + * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze + * (by Leonid Broukhis). + * A previous version of this file used a more sophisticated algorithm + * (by Fiala and Greene) which is guaranteed to run in linear amortized + * time, but has a larger average cost, uses more memory and is patented. + * However the F&G algorithm may be faster for some highly redundant + * files if the parameter max_chain_length (described below) is too large. + * + * ACKNOWLEDGMENTS + * + * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and + * I found it in 'freeze' written by Leonid Broukhis. + * Thanks to many info-zippers for bug reports and testing. + * + * REFERENCES + * + * APPNOTE.TXT documentation file in PKZIP 1.93a distribution. + * + * A description of the Rabin and Karp algorithm is given in the book + * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. + * + * Fiala,E.R., and Greene,D.H. + * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 + * + * INTERFACE + * + * void lm_init (int pack_level, ush *flags) + * Initialize the "longest match" routines for a new file + * + * ulg deflate (void) + * Processes a new input file and return its compressed length. Sets + * the compressed length, crc, deflate flags and internal file + * attributes. + */ + + +/* =========================================================================== + * Configuration parameters + */ + +/* Compile with MEDIUM_MEM to reduce the memory requirements or + * with SMALL_MEM to use as little memory as possible. Use BIG_MEM if the + * entire input file can be held in memory (not possible on 16 bit systems). + * Warning: defining these symbols affects HASH_BITS (see below) and thus + * affects the compression ratio. The compressed output + * is still correct, and might even be smaller in some cases. + */ + +#ifdef SMALL_MEM +# define HASH_BITS 13 /* Number of bits used to hash strings */ +#endif +#ifdef MEDIUM_MEM +# define HASH_BITS 14 +#endif +#ifndef HASH_BITS +# define HASH_BITS 15 + /* For portability to 16 bit machines, do not use values above 15. */ +#endif + +/* To save space (see unlzw.c), we overlay prev+head with tab_prefix and + * window with tab_suffix. Check that we can do this: + */ +#if (WSIZE<<1) > (1< BITS-1 +# error cannot overlay head with tab_prefix1 +#endif +#define HASH_SIZE (unsigned)(1<= HASH_BITS + */ + +static unsigned int prev_length; + +/* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + +static unsigned strstart; /* start of string to insert */ +static unsigned match_start; /* start of matching string */ +static int eofile; /* flag set at end of input file */ +static unsigned lookahead; /* number of valid bytes ahead in window */ + +enum { + max_chain_length = 4096, + +/* To speed up deflation, hash chains are never searched beyond this length. + * A higher limit improves compression ratio but degrades the speed. + */ + + max_lazy_match = 258, + +/* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ + max_insert_length = max_lazy_match, +/* Insert new strings in the hash table only if the match length + * is not greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + good_match = 32, + +/* Use a faster search when the previous match is longer than this */ + + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ + + nice_match = 258 /* Stop searching when current match exceeds this */ + +/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 + * For deflate_fast() (levels <= 3) good is ignored and lazy has a different + * meaning. + */ +}; + +#define EQUAL 0 +/* result of memcmp for equal strings */ + +/* =========================================================================== + * Prototypes for local functions. + */ +static void fill_window(void); + +static int longest_match(IPos cur_match); + +#ifdef DEBUG +static void check_match(IPos start, IPos match, int length); +#endif + +/* =========================================================================== + * Update a hash value with the given input byte + * IN assertion: all calls to to UPDATE_HASH are made with consecutive + * input characters, so that a running hash key can be computed from the + * previous key instead of complete recalculation each time. + */ +#define UPDATE_HASH(h,c) (h = (((h)<= 1 + */ + +/* For MSDOS, OS/2 and 386 Unix, an optimized version is in match.asm or + * match.s. The code is functionally equivalent, so you can use the C version + * if desired. + */ +static int longest_match(IPos cur_match) +{ + unsigned chain_length = max_chain_length; /* max hash chain length */ + uch *scan = window + strstart; /* current string */ + uch *match; /* matched string */ + int len; /* length of current match */ + int best_len = prev_length; /* best match length so far */ + IPos limit = + strstart > (IPos) MAX_DIST ? strstart - (IPos) MAX_DIST : NIL; + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + +/* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ +#if HASH_BITS < 8 || MAX_MATCH != 258 +# error Code too clever +#endif + uch *strend = window + strstart + MAX_MATCH; + uch scan_end1 = scan[best_len - 1]; + uch scan_end = scan[best_len]; + + /* Do not waste too much time if we already have a good match: */ + if (prev_length >= good_match) { + chain_length >>= 2; + } + Assert(strstart <= window_size - MIN_LOOKAHEAD, "insufficient lookahead"); + + do { + Assert(cur_match < strstart, "no future"); + match = window + cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2: + */ + if (match[best_len] != scan_end || + match[best_len - 1] != scan_end1 || + *match != *scan || *++match != scan[1]) + continue; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match++; + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && scan < strend); + + len = MAX_MATCH - (int) (strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) { + match_start = cur_match; + best_len = len; + if (len >= nice_match) + break; + scan_end1 = scan[best_len - 1]; + scan_end = scan[best_len]; + } + } while ((cur_match = prev[cur_match & WMASK]) > limit + && --chain_length != 0); + + return best_len; +} + +#ifdef DEBUG +/* =========================================================================== + * Check that the match at match_start is indeed a match. + */ +static void check_match(IPos start, IPos match, int length) +{ + /* check that the match is indeed a match */ + if (memcmp((char *) window + match, + (char *) window + start, length) != EQUAL) { + bb_error_msg(" start %d, match %d, length %d", start, match, length); + bb_error_msg("invalid match"); + } + if (verbose > 1) { + bb_error_msg("\\[%d,%d]", start - match, length); + do { + putc(window[start++], stderr); + } while (--length != 0); + } +} +#else +# define check_match(start, match, length) +#endif + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead, and sets eofile if end of input file. + * IN assertion: lookahead < MIN_LOOKAHEAD && strstart + lookahead > 0 + * OUT assertions: at least one byte has been read, or eofile is set; + * file reads are performed for at least two bytes (required for the + * translate_eol option). + */ +static void fill_window(void) +{ + unsigned n, m; + unsigned more = + (unsigned) (window_size - (ulg) lookahead - (ulg) strstart); + /* Amount of free space at the end of the window. */ + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (more == (unsigned) EOF) { + /* Very unlikely, but possible on 16 bit machine if strstart == 0 + * and lookahead == 1 (input done one byte at time) + */ + more--; + } else if (strstart >= WSIZE + MAX_DIST) { + /* By the IN assertion, the window is not empty so we can't confuse + * more == 0 with more == 64K on a 16 bit machine. + */ + Assert(window_size == (ulg) 2 * WSIZE, "no sliding with BIG_MEM"); + + memcpy((char *) window, (char *) window + WSIZE, (unsigned) WSIZE); + match_start -= WSIZE; + strstart -= WSIZE; /* we now have strstart >= MAX_DIST: */ + + block_start -= (long) WSIZE; + + for (n = 0; n < HASH_SIZE; n++) { + m = head[n]; + head[n] = (Pos) (m >= WSIZE ? m - WSIZE : NIL); + } + for (n = 0; n < WSIZE; n++) { + m = prev[n]; + prev[n] = (Pos) (m >= WSIZE ? m - WSIZE : NIL); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } + more += WSIZE; + } + /* At this point, more >= 2 */ + if (!eofile) { + n = read_buf((char *) window + strstart + lookahead, more); + if (n == 0 || n == (unsigned) EOF) { + eofile = 1; + } else { + lookahead += n; + } + } +} + +/* =========================================================================== + * Flush the current block, with given end-of-file flag. + * IN assertion: strstart is set to the end of the current match. + */ +#define FLUSH_BLOCK(eof) \ + flush_block(block_start >= 0L ? (char*)&window[(unsigned)block_start] : \ + (char*)NULL, (long)strstart - block_start, (eof)) + +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +static ulg deflate(void) +{ + IPos hash_head; /* head of hash chain */ + IPos prev_match; /* previous match */ + int flush; /* set if current block must be flushed */ + int match_available = 0; /* set if previous match exists */ + unsigned match_length = MIN_MATCH - 1; /* length of best match */ + + /* Process the input block. */ + while (lookahead != 0) { + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + INSERT_STRING(strstart, hash_head); + + /* Find the longest match, discarding those <= prev_length. + */ + prev_length = match_length, prev_match = match_start; + match_length = MIN_MATCH - 1; + + if (hash_head != NIL && prev_length < max_lazy_match && + strstart - hash_head <= MAX_DIST) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + match_length = longest_match(hash_head); + /* longest_match() sets match_start */ + if (match_length > lookahead) + match_length = lookahead; + + /* Ignore a length 3 match if it is too distant: */ + if (match_length == MIN_MATCH && strstart - match_start > TOO_FAR) { + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + match_length--; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (prev_length >= MIN_MATCH && match_length <= prev_length) { + + check_match(strstart - 1, prev_match, prev_length); + + flush = + ct_tally(strstart - 1 - prev_match, prev_length - MIN_MATCH); + + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. + */ + lookahead -= prev_length - 1; + prev_length -= 2; + do { + strstart++; + INSERT_STRING(strstart, hash_head); + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. If lookahead < MIN_MATCH + * these bytes are garbage, but it does not matter since the + * next lookahead bytes will always be emitted as literals. + */ + } while (--prev_length != 0); + match_available = 0; + match_length = MIN_MATCH - 1; + strstart++; + if (flush) + FLUSH_BLOCK(0), block_start = strstart; + + } else if (match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + Tracevv((stderr, "%c", window[strstart - 1])); + if (ct_tally(0, window[strstart - 1])) { + FLUSH_BLOCK(0), block_start = strstart; + } + strstart++; + lookahead--; + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + match_available = 1; + strstart++; + lookahead--; + } + Assert(strstart <= isize && lookahead <= isize, "a bit too far"); + + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + while (lookahead < MIN_LOOKAHEAD && !eofile) + fill_window(); + } + if (match_available) + ct_tally(0, window[strstart - 1]); + + return FLUSH_BLOCK(1); /* eof */ +} + +/* gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface + * Copyright (C) 1992-1993 Jean-loup Gailly + * The unzip code was written and put in the public domain by Mark Adler. + * Portions of the lzw code are derived from the public domain 'compress' + * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies, + * Ken Turkowski, Dave Mack and Peter Jannesen. + * + * See the license_msg below and the file COPYING for the software license. + * See the file algorithm.doc for the compression algorithms and file formats. + */ + +/* Compress files with zip algorithm and 'compress' interface. + * See usage() and help() functions below for all options. + * Outputs: + * file.gz: compressed file with same mode, owner, and utimes + * or stdout with -c option or if stdin used as input. + * If the output file name had to be truncated, the original name is kept + * in the compressed file. + */ + + /* configuration */ + +typedef struct dirent dir_type; + +/* ======================================================================== */ +int gzip_main(int argc, char **argv) +{ + enum { + OPT_tostdout = 0x1, + OPT_force = 0x2, + }; + + unsigned opt; + int result; + int inFileNum; + int outFileNum; + struct stat statBuf; + char *delFileName; + + opt = getopt32(argc, argv, "cf123456789qv" USE_GUNZIP("d")); + //if (opt & 0x1) // -c + //if (opt & 0x2) // -f + /* Ignore 1-9 (compression level) options */ + //if (opt & 0x4) // -1 + //if (opt & 0x8) // -2 + //if (opt & 0x10) // -3 + //if (opt & 0x20) // -4 + //if (opt & 0x40) // -5 + //if (opt & 0x80) // -6 + //if (opt & 0x100) // -7 + //if (opt & 0x200) // -8 + //if (opt & 0x400) // -9 + //if (opt & 0x800) // -q + //if (opt & 0x1000) // -v + if (ENABLE_GUNZIP && (opt & 0x2000)) { // -d + /* FIXME: getopt32 should not depend on optind */ + optind = 1; + return gunzip_main(argc, argv); + } + + foreground = signal(SIGINT, SIG_IGN) != SIG_IGN; + if (foreground) { + (void) signal(SIGINT, abort_gzip); + } +#ifdef SIGTERM + if (signal(SIGTERM, SIG_IGN) != SIG_IGN) { + (void) signal(SIGTERM, abort_gzip); + } +#endif +#ifdef SIGHUP + if (signal(SIGHUP, SIG_IGN) != SIG_IGN) { + (void) signal(SIGHUP, abort_gzip); + } +#endif + + strncpy(z_suffix, Z_SUFFIX, sizeof(z_suffix) - 1); + + /* Allocate all global buffers (for DYN_ALLOC option) */ + ALLOC(uch, inbuf, INBUFSIZ + INBUF_EXTRA); + ALLOC(uch, outbuf, OUTBUFSIZ + OUTBUF_EXTRA); + ALLOC(ush, d_buf, DIST_BUFSIZE); + ALLOC(uch, window, 2L * WSIZE); + ALLOC(ush, tab_prefix, 1L << BITS); + + /* Initialise the CRC32 table */ + crc_32_tab = crc32_filltable(0); + + clear_bufs(); + + if (optind == argc) { + time_stamp = 0; + zip(STDIN_FILENO, STDOUT_FILENO); + } else { + int i; + + for (i = optind; i < argc; i++) { + char *path = NULL; + + clear_bufs(); + if (strcmp(argv[i], "-") == 0) { + time_stamp = 0; + inFileNum = STDIN_FILENO; + outFileNum = STDOUT_FILENO; + } else { + inFileNum = xopen(argv[i], O_RDONLY); + if (fstat(inFileNum, &statBuf) < 0) + bb_perror_msg_and_die("%s", argv[i]); + time_stamp = statBuf.st_ctime; + + if (!(opt & OPT_tostdout)) { + path = xasprintf("%s.gz", argv[i]); + + /* Open output file */ +#if (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 1) && defined O_NOFOLLOW + outFileNum = + open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW); +#else + outFileNum = open(path, O_RDWR | O_CREAT | O_EXCL); +#endif + if (outFileNum < 0) { + bb_perror_msg("%s", path); + free(path); + continue; + } + + /* Set permissions on the file */ + fchmod(outFileNum, statBuf.st_mode); + } else + outFileNum = STDOUT_FILENO; + } + + if (path == NULL && isatty(outFileNum) && !(opt & OPT_force)) { + bb_error_msg + ("compressed data not written to a terminal. Use -f to force compression."); + free(path); + continue; + } + + result = zip(inFileNum, outFileNum); + + if (path != NULL) { + close(inFileNum); + close(outFileNum); + + /* Delete the original file */ + if (result == OK) + delFileName = argv[i]; + else + delFileName = path; + + if (unlink(delFileName) < 0) + bb_perror_msg("%s", delFileName); + } + + free(path); + } + } + + return exit_code; +} + +/* trees.c -- output deflated data using Huffman coding + * Copyright (C) 1992-1993 Jean-loup Gailly + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + +/* + * PURPOSE + * + * Encode various sets of source values using variable-length + * binary code trees. + * + * DISCUSSION + * + * The PKZIP "deflation" process uses several Huffman trees. The more + * common source values are represented by shorter bit sequences. + * + * Each code tree is stored in the ZIP file in a compressed form + * which is itself a Huffman encoding of the lengths of + * all the code strings (in ascending order by source values). + * The actual code strings are reconstructed from the lengths in + * the UNZIP process, as described in the "application note" + * (APPNOTE.TXT) distributed as part of PKWARE's PKZIP program. + * + * REFERENCES + * + * Lynch, Thomas J. + * Data Compression: Techniques and Applications, pp. 53-55. + * Lifetime Learning Publications, 1985. ISBN 0-534-03418-7. + * + * Storer, James A. + * Data Compression: Methods and Theory, pp. 49-50. + * Computer Science Press, 1988. ISBN 0-7167-8156-5. + * + * Sedgewick, R. + * Algorithms, p290. + * Addison-Wesley, 1983. ISBN 0-201-06672-6. + * + * INTERFACE + * + * void ct_init (ush *attr, int *methodp) + * Allocate the match buffer, initialize the various tables and save + * the location of the internal file attribute (ascii/binary) and + * method (DEFLATE/STORE) + * + * void ct_tally (int dist, int lc); + * Save the match info and tally the frequency counts. + * + * long flush_block (char *buf, ulg stored_len, int eof) + * Determine the best encoding for the current block: dynamic trees, + * static trees or store, and output the encoded block to the zip + * file. Returns the total compressed length for the file so far. + * + */ + +/* =========================================================================== + * Constants + */ + +#define MAX_BITS 15 +/* All codes must not exceed MAX_BITS bits */ + +#define MAX_BL_BITS 7 +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +#define LENGTH_CODES 29 +/* number of length codes, not counting the special END_BLOCK code */ + +#define LITERALS 256 +/* number of literal bytes 0..255 */ + +#define END_BLOCK 256 +/* end of block literal code */ + +#define L_CODES (LITERALS+1+LENGTH_CODES) +/* number of Literal or Length codes, including the END_BLOCK code */ + +#define D_CODES 30 +/* number of distance codes */ + +#define BL_CODES 19 +/* number of codes used to transfer the bit lengths */ + +typedef uch extra_bits_t; + +/* extra bits for each length code */ +static const extra_bits_t extra_lbits[LENGTH_CODES] + = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, + 4, 4, 5, 5, 5, 5, 0 +}; + +/* extra bits for each distance code */ +static const extra_bits_t extra_dbits[D_CODES] + = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, + 10, 10, 11, 11, 12, 12, 13, 13 +}; + +/* extra bits for each bit length code */ +static const extra_bits_t extra_blbits[BL_CODES] += { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7 }; + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#ifndef LIT_BUFSIZE +# ifdef SMALL_MEM +# define LIT_BUFSIZE 0x2000 +# else +# ifdef MEDIUM_MEM +# define LIT_BUFSIZE 0x4000 +# else +# define LIT_BUFSIZE 0x8000 +# endif +# endif +#endif +#ifndef DIST_BUFSIZE +# define DIST_BUFSIZE LIT_BUFSIZE +#endif +/* Sizes of match buffers for literals/lengths and distances. There are + * 4 reasons for limiting LIT_BUFSIZE to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input data is + * still in the window so we can still emit a stored block even when input + * comes from standard input. (This can also be done for all blocks if + * LIT_BUFSIZE is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting trees + * more frequently. + * - I can't count above 4 + * The current code is general and allows DIST_BUFSIZE < LIT_BUFSIZE (to save + * memory at the expense of compression). Some optimizations would be possible + * if we rely on DIST_BUFSIZE == LIT_BUFSIZE. + */ +#if LIT_BUFSIZE > INBUFSIZ +#error cannot overlay l_buf and inbuf +#endif +#define REP_3_6 16 +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ +#define REPZ_3_10 17 +/* repeat a zero length 3-10 times (3 bits of repeat count) */ +#define REPZ_11_138 18 +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +/* =========================================================================== + * Local data + */ + +/* Data structure describing a single value and its code string. */ +typedef struct ct_data { + union { + ush freq; /* frequency count */ + ush code; /* bit string */ + } fc; + union { + ush dad; /* father node in Huffman tree */ + ush len; /* length of bit string */ + } dl; +} ct_data; + +#define Freq fc.freq +#define Code fc.code +#define Dad dl.dad +#define Len dl.len + +#define HEAP_SIZE (2*L_CODES+1) +/* maximum heap size */ + +static ct_data dyn_ltree[HEAP_SIZE]; /* literal and length tree */ +static ct_data dyn_dtree[2 * D_CODES + 1]; /* distance tree */ + +static ct_data static_ltree[L_CODES + 2]; + +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see ct_init + * below). + */ + +static ct_data static_dtree[D_CODES]; + +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +static ct_data bl_tree[2 * BL_CODES + 1]; + +/* Huffman tree for the bit lengths */ + +typedef struct tree_desc { + ct_data *dyn_tree; /* the dynamic tree */ + ct_data *static_tree; /* corresponding static tree or NULL */ + const extra_bits_t *extra_bits; /* extra bits for each code or NULL */ + int extra_base; /* base index for extra_bits */ + int elems; /* max number of elements in the tree */ + int max_length; /* max bit length for the codes */ + int max_code; /* largest code with non zero frequency */ +} tree_desc; + +static tree_desc l_desc = + { dyn_ltree, static_ltree, extra_lbits, LITERALS + 1, L_CODES, + MAX_BITS, 0 +}; + +static tree_desc d_desc = + { dyn_dtree, static_dtree, extra_dbits, 0, D_CODES, MAX_BITS, 0 }; + +static tree_desc bl_desc = + { bl_tree, (ct_data *) 0, extra_blbits, 0, BL_CODES, MAX_BL_BITS, + 0 +}; + + +static ush bl_count[MAX_BITS + 1]; + +/* number of codes at each bit length for an optimal tree */ + +static const uch bl_order[BL_CODES] += { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +static int heap[2 * L_CODES + 1]; /* heap used to build the Huffman trees */ +static int heap_len; /* number of elements in the heap */ +static int heap_max; /* element of largest frequency */ + +/* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + +static uch depth[2 * L_CODES + 1]; + +/* Depth of each subtree used as tie breaker for trees of equal frequency */ + +static uch length_code[MAX_MATCH - MIN_MATCH + 1]; + +/* length code for each normalized match length (0 == MIN_MATCH) */ + +static uch dist_code[512]; + +/* distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +static int base_length[LENGTH_CODES]; + +/* First normalized length for each code (0 = MIN_MATCH) */ + +static int base_dist[D_CODES]; + +/* First normalized distance for each code (0 = distance of 1) */ + +#define l_buf inbuf +/* DECLARE(uch, l_buf, LIT_BUFSIZE); buffer for literals or lengths */ + +/* DECLARE(ush, d_buf, DIST_BUFSIZE); buffer for distances */ + +static uch flag_buf[(LIT_BUFSIZE / 8)]; + +/* flag_buf is a bit array distinguishing literals from lengths in + * l_buf, thus indicating the presence or absence of a distance. + */ + +static unsigned last_lit; /* running index in l_buf */ +static unsigned last_dist; /* running index in d_buf */ +static unsigned last_flags; /* running index in flag_buf */ +static uch flags; /* current flags not yet saved in flag_buf */ +static uch flag_bit; /* current bit used in flags */ + +/* bits are filled in flags starting at bit 0 (least significant). + * Note: these flags are overkill in the current code since we don't + * take advantage of DIST_BUFSIZE == LIT_BUFSIZE. + */ + +static ulg opt_len; /* bit length of current block with optimal trees */ +static ulg static_len; /* bit length of current block with static trees */ + +static ulg compressed_len; /* total bit length of compressed file */ + + +static ush *file_type; /* pointer to UNKNOWN, BINARY or ASCII */ +static int *file_method; /* pointer to DEFLATE or STORE */ + +/* =========================================================================== + * Local (static) routines in this file. + */ + +static void init_block(void); +static void pqdownheap(ct_data * tree, int k); +static void gen_bitlen(tree_desc * desc); +static void gen_codes(ct_data * tree, int max_code); +static void build_tree(tree_desc * desc); +static void scan_tree(ct_data * tree, int max_code); +static void send_tree(ct_data * tree, int max_code); +static int build_bl_tree(void); +static void send_all_trees(int lcodes, int dcodes, int blcodes); +static void compress_block(ct_data * ltree, ct_data * dtree); +static void set_file_type(void); + + +#ifndef DEBUG +# define send_code(c, tree) send_bits(tree[c].Code, tree[c].Len) + /* Send a code of the given tree. c and tree must not have side effects */ + +#else /* DEBUG */ +# define send_code(c, tree) \ + { if (verbose>1) bb_error_msg("\ncd %3d ",(c)); \ + send_bits(tree[c].Code, tree[c].Len); } +#endif + +#define d_code(dist) \ + ((dist) < 256 ? dist_code[dist] : dist_code[256+((dist)>>7)]) +/* Mapping from a distance to a distance code. dist is the distance - 1 and + * must not have side effects. dist_code[256] and dist_code[257] are never + * used. + */ + +/* the arguments must not have side effects */ + +/* =========================================================================== + * Allocate the match buffer, initialize the various tables and save the + * location of the internal file attribute (ascii/binary) and method + * (DEFLATE/STORE). + */ +static void ct_init(ush * attr, int *methodp) +{ + int n; /* iterates over tree elements */ + int bits; /* bit counter */ + int length; /* length value */ + int code; /* code value */ + int dist; /* distance index */ + + file_type = attr; + file_method = methodp; + compressed_len = 0L; + + if (static_dtree[0].Len != 0) + return; /* ct_init already called */ + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES - 1; code++) { + base_length[code] = length; + for (n = 0; n < (1 << extra_lbits[code]); n++) { + length_code[length++] = (uch) code; + } + } + Assert(length == 256, "ct_init: length != 256"); + /* Note that the length 255 (match length 258) can be represented + * in two different ways: code 284 + 5 bits or code 285, so we + * overwrite length_code[255] to use the best encoding: + */ + length_code[length - 1] = (uch) code; + + /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ + dist = 0; + for (code = 0; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1 << extra_dbits[code]); n++) { + dist_code[dist++] = (uch) code; + } + } + Assert(dist == 256, "ct_init: dist != 256"); + dist >>= 7; /* from now on, all distances are divided by 128 */ + for (; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { + dist_code[256 + dist++] = (uch) code; + } + } + Assert(dist == 256, "ct_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) + bl_count[bits] = 0; + n = 0; + while (n <= 143) + static_ltree[n++].Len = 8, bl_count[8]++; + while (n <= 255) + static_ltree[n++].Len = 9, bl_count[9]++; + while (n <= 279) + static_ltree[n++].Len = 7, bl_count[7]++; + while (n <= 287) + static_ltree[n++].Len = 8, bl_count[8]++; + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes((ct_data *) static_ltree, L_CODES + 1); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n].Len = 5; + static_dtree[n].Code = bi_reverse(n, 5); + } + + /* Initialize the first block of the first file: */ + init_block(); +} + +/* =========================================================================== + * Initialize a new block. + */ +static void init_block(void) +{ + int n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) + dyn_ltree[n].Freq = 0; + for (n = 0; n < D_CODES; n++) + dyn_dtree[n].Freq = 0; + for (n = 0; n < BL_CODES; n++) + bl_tree[n].Freq = 0; + + dyn_ltree[END_BLOCK].Freq = 1; + opt_len = static_len = 0L; + last_lit = last_dist = last_flags = 0; + flags = 0; + flag_bit = 1; +} + +#define SMALLEST 1 +/* Index within the heap array of least frequent node in the Huffman tree */ + + +/* =========================================================================== + * Remove the smallest element from the heap and recreate the heap with + * one less element. Updates heap and heap_len. + */ +#define pqremove(tree, top) \ +{\ + top = heap[SMALLEST]; \ + heap[SMALLEST] = heap[heap_len--]; \ + pqdownheap(tree, SMALLEST); \ +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +#define smaller(tree, n, m) \ + (tree[n].Freq < tree[m].Freq || \ + (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m])) + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +static void pqdownheap(ct_data * tree, int k) +{ + int v = heap[k]; + int j = k << 1; /* left son of k */ + + while (j <= heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < heap_len && smaller(tree, heap[j + 1], heap[j])) + j++; + + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, heap[j])) + break; + + /* Exchange v with the smallest son */ + heap[k] = heap[j]; + k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + heap[k] = v; +} + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +static void gen_bitlen(tree_desc * desc) +{ + ct_data *tree = desc->dyn_tree; + const extra_bits_t *extra = desc->extra_bits; + int base = desc->extra_base; + int max_code = desc->max_code; + int max_length = desc->max_length; + ct_data *stree = desc->static_tree; + int h; /* heap index */ + int n, m; /* iterate over the tree elements */ + int bits; /* bit length */ + int xbits; /* extra bits */ + ush f; /* frequency */ + int overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) + bl_count[bits] = 0; + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[heap[heap_max]].Len = 0; /* root of the heap */ + + for (h = heap_max + 1; h < HEAP_SIZE; h++) { + n = heap[h]; + bits = tree[tree[n].Dad].Len + 1; + if (bits > max_length) + bits = max_length, overflow++; + tree[n].Len = (ush) bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) + continue; /* not a leaf node */ + + bl_count[bits]++; + xbits = 0; + if (n >= base) + xbits = extra[n - base]; + f = tree[n].Freq; + opt_len += (ulg) f *(bits + xbits); + + if (stree) + static_len += (ulg) f *(stree[n].Len + xbits); + } + if (overflow == 0) + return; + + Trace((stderr, "\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length - 1; + while (bl_count[bits] == 0) + bits--; + bl_count[bits]--; /* move one leaf down the tree */ + bl_count[bits + 1] += 2; /* move one overflow item as its brother */ + bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits != 0; bits--) { + n = bl_count[bits]; + while (n != 0) { + m = heap[--h]; + if (m > max_code) + continue; + if (tree[m].Len != (unsigned) bits) { + Trace((stderr, "code %d bits %d->%d\n", m, tree[m].Len, + bits)); + opt_len += + ((long) bits - (long) tree[m].Len) * (long) tree[m].Freq; + tree[m].Len = (ush) bits; + } + n--; + } + } +} + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +static void gen_codes(ct_data * tree, int max_code) +{ + ush next_code[MAX_BITS + 1]; /* next code value for each bit length */ + ush code = 0; /* running code value */ + int bits; /* bit index */ + int n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (code + bl_count[bits - 1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + Assert(code + bl_count[MAX_BITS] - 1 == (1 << MAX_BITS) - 1, + "inconsistent bit counts"); + Tracev((stderr, "\ngen_codes: max_code %d ", max_code)); + + for (n = 0; n <= max_code; n++) { + int len = tree[n].Len; + + if (len == 0) + continue; + /* Now reverse the bits */ + tree[n].Code = bi_reverse(next_code[len]++, len); + + Tracec(tree != static_ltree, + (stderr, "\nn %3d %c l %2d c %4x (%x) ", n, + (isgraph(n) ? n : ' '), len, tree[n].Code, + next_code[len] - 1)); + } +} + +/* =========================================================================== + * Construct one Huffman tree and assigns the code bit strings and lengths. + * Update the total bit length for the current block. + * IN assertion: the field freq is set for all tree elements. + * OUT assertions: the fields len and code are set to the optimal bit length + * and corresponding code. The length opt_len is updated; static_len is + * also updated if stree is not null. The field max_code is set. + */ +static void build_tree(tree_desc * desc) +{ + ct_data *tree = desc->dyn_tree; + ct_data *stree = desc->static_tree; + int elems = desc->elems; + int n, m; /* iterate over heap elements */ + int max_code = -1; /* largest code with non zero frequency */ + int node = elems; /* next internal node of the tree */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + heap_len = 0, heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n].Freq != 0) { + heap[++heap_len] = max_code = n; + depth[n] = 0; + } else { + tree[n].Len = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (heap_len < 2) { + int new = heap[++heap_len] = (max_code < 2 ? ++max_code : 0); + + tree[new].Freq = 1; + depth[new] = 0; + opt_len--; + if (stree) + static_len -= stree[new].Len; + /* new is 0 or 1 so it does not have extra bits */ + } + desc->max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = heap_len / 2; n >= 1; n--) + pqdownheap(tree, n); + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + do { + pqremove(tree, n); /* n = node of least frequency */ + m = heap[SMALLEST]; /* m = node of next least frequency */ + + heap[--heap_max] = n; /* keep the nodes sorted by frequency */ + heap[--heap_max] = m; + + /* Create a new node father of n and m */ + tree[node].Freq = tree[n].Freq + tree[m].Freq; + depth[node] = (uch) (MAX(depth[n], depth[m]) + 1); + tree[n].Dad = tree[m].Dad = (ush) node; +#ifdef DUMP_BL_TREE + if (tree == bl_tree) { + bb_error_msg("\nnode %d(%d), sons %d(%d) %d(%d)", + node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq); + } +#endif + /* and insert the new node in the heap */ + heap[SMALLEST] = node++; + pqdownheap(tree, SMALLEST); + + } while (heap_len >= 2); + + heap[--heap_max] = heap[SMALLEST]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen((tree_desc *) desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes((ct_data *) tree, max_code); +} + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. Updates opt_len to take into account the repeat + * counts. (The contribution of the bit length codes will be added later + * during the construction of bl_tree.) + */ +static void scan_tree(ct_data * tree, int max_code) +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + if (nextlen == 0) + max_count = 138, min_count = 3; + tree[max_code + 1].Len = (ush) 0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[n + 1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + bl_tree[curlen].Freq += count; + } else if (curlen != 0) { + if (curlen != prevlen) + bl_tree[curlen].Freq++; + bl_tree[REP_3_6].Freq++; + } else if (count <= 10) { + bl_tree[REPZ_3_10].Freq++; + } else { + bl_tree[REPZ_11_138].Freq++; + } + count = 0; + prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +static void send_tree(ct_data * tree, int max_code) +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + +/* tree[max_code+1].Len = -1; *//* guard already set */ + if (nextlen == 0) + max_count = 138, min_count = 3; + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[n + 1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + do { + send_code(curlen, bl_tree); + } while (--count != 0); + + } else if (curlen != 0) { + if (curlen != prevlen) { + send_code(curlen, bl_tree); + count--; + } + Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(REP_3_6, bl_tree); + send_bits(count - 3, 2); + + } else if (count <= 10) { + send_code(REPZ_3_10, bl_tree); + send_bits(count - 3, 3); + + } else { + send_code(REPZ_11_138, bl_tree); + send_bits(count - 11, 7); + } + count = 0; + prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +static int build_bl_tree(void) +{ + int max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree((ct_data *) dyn_ltree, l_desc.max_code); + scan_tree((ct_data *) dyn_dtree, d_desc.max_code); + + /* Build the bit length tree: */ + build_tree((tree_desc *) (&bl_desc)); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) { + if (bl_tree[bl_order[max_blindex]].Len != 0) + break; + } + /* Update opt_len to include the bit length tree and counts */ + opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; + Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", opt_len, static_len)); + + return max_blindex; +} + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +static void send_all_trees(int lcodes, int dcodes, int blcodes) +{ + int rank; /* index in bl_order */ + + Assert(lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + Assert(lcodes <= L_CODES && dcodes <= D_CODES + && blcodes <= BL_CODES, "too many codes"); + Tracev((stderr, "\nbl counts: ")); + send_bits(lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(dcodes - 1, 5); + send_bits(blcodes - 4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(bl_tree[bl_order[rank]].Len, 3); + } + Tracev((stderr, "\nbl tree: sent %ld", bits_sent)); + + send_tree((ct_data *) dyn_ltree, lcodes - 1); /* send the literal tree */ + Tracev((stderr, "\nlit tree: sent %ld", bits_sent)); + + send_tree((ct_data *) dyn_dtree, dcodes - 1); /* send the distance tree */ + Tracev((stderr, "\ndist tree: sent %ld", bits_sent)); +} + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. This function + * returns the total compressed length for the file so far. + */ +static ulg flush_block(char *buf, ulg stored_len, int eof) +{ + ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + int max_blindex; /* index of last bit length code of non zero freq */ + + flag_buf[last_flags] = flags; /* Save the flags for the last 8 items */ + + /* Check if the file is ascii or binary */ + if (*file_type == (ush) UNKNOWN) + set_file_type(); + + /* Construct the literal and distance trees */ + build_tree((tree_desc *) (&l_desc)); + Tracev((stderr, "\nlit data: dyn %ld, stat %ld", opt_len, static_len)); + + build_tree((tree_desc *) (&d_desc)); + Tracev((stderr, "\ndist data: dyn %ld, stat %ld", opt_len, static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(); + + /* Determine the best encoding. Compute first the block length in bytes */ + opt_lenb = (opt_len + 3 + 7) >> 3; + static_lenb = (static_len + 3 + 7) >> 3; + + Trace((stderr, + "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u dist %u ", + opt_lenb, opt_len, static_lenb, static_len, stored_len, + last_lit, last_dist)); + + if (static_lenb <= opt_lenb) + opt_lenb = static_lenb; + + /* If compression failed and this is the first and last block, + * and if the zip file can be seeked (to rewrite the local header), + * the whole file is transformed into a stored file: + */ + if (stored_len <= opt_lenb && eof && compressed_len == 0L && seekable()) { + /* Since LIT_BUFSIZE <= 2*WSIZE, the input data must be there: */ + if (buf == (char *) 0) + bb_error_msg("block vanished"); + + copy_block(buf, (unsigned) stored_len, 0); /* without header */ + compressed_len = stored_len << 3; + *file_method = STORED; + + } else if (stored_len + 4 <= opt_lenb && buf != (char *) 0) { + /* 4: two words for the lengths */ + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + send_bits((STORED_BLOCK << 1) + eof, 3); /* send block type */ + compressed_len = (compressed_len + 3 + 7) & ~7L; + compressed_len += (stored_len + 4) << 3; + + copy_block(buf, (unsigned) stored_len, 1); /* with header */ + + } else if (static_lenb == opt_lenb) { + send_bits((STATIC_TREES << 1) + eof, 3); + compress_block((ct_data *) static_ltree, (ct_data *) static_dtree); + compressed_len += 3 + static_len; + } else { + send_bits((DYN_TREES << 1) + eof, 3); + send_all_trees(l_desc.max_code + 1, d_desc.max_code + 1, + max_blindex + 1); + compress_block((ct_data *) dyn_ltree, (ct_data *) dyn_dtree); + compressed_len += 3 + opt_len; + } + Assert(compressed_len == bits_sent, "bad compressed size"); + init_block(); + + if (eof) { + bi_windup(); + compressed_len += 7; /* align on byte boundary */ + } + Tracev((stderr, "\ncomprlen %lu(%lu) ", compressed_len >> 3, + compressed_len - 7 * eof)); + + return compressed_len >> 3; +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +static int ct_tally(int dist, int lc) +{ + l_buf[last_lit++] = (uch) lc; + if (dist == 0) { + /* lc is the unmatched char */ + dyn_ltree[lc].Freq++; + } else { + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + Assert((ush) dist < (ush) MAX_DIST && + (ush) lc <= (ush) (MAX_MATCH - MIN_MATCH) && + (ush) d_code(dist) < (ush) D_CODES, "ct_tally: bad match"); + + dyn_ltree[length_code[lc] + LITERALS + 1].Freq++; + dyn_dtree[d_code(dist)].Freq++; + + d_buf[last_dist++] = (ush) dist; + flags |= flag_bit; + } + flag_bit <<= 1; + + /* Output the flags if they fill a byte: */ + if ((last_lit & 7) == 0) { + flag_buf[last_flags++] = flags; + flags = 0, flag_bit = 1; + } + /* Try to guess if it is profitable to stop the current block here */ + if ((last_lit & 0xfff) == 0) { + /* Compute an upper bound for the compressed length */ + ulg out_length = (ulg) last_lit * 8L; + ulg in_length = (ulg) strstart - block_start; + int dcode; + + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += + (ulg) dyn_dtree[dcode].Freq * (5L + extra_dbits[dcode]); + } + out_length >>= 3; + Trace((stderr, + "\nlast_lit %u, last_dist %u, in %ld, out ~%ld(%ld%%) ", + last_lit, last_dist, in_length, out_length, + 100L - out_length * 100L / in_length)); + if (last_dist < last_lit / 2 && out_length < in_length / 2) + return 1; + } + return (last_lit == LIT_BUFSIZE - 1 || last_dist == DIST_BUFSIZE); + /* We avoid equality with LIT_BUFSIZE because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +static void compress_block(ct_data * ltree, ct_data * dtree) +{ + unsigned dist; /* distance of matched string */ + int lc; /* match length or unmatched char (if dist == 0) */ + unsigned lx = 0; /* running index in l_buf */ + unsigned dx = 0; /* running index in d_buf */ + unsigned fx = 0; /* running index in flag_buf */ + uch flag = 0; /* current flags */ + unsigned code; /* the code to send */ + int extra; /* number of extra bits to send */ + + if (last_lit != 0) + do { + if ((lx & 7) == 0) + flag = flag_buf[fx++]; + lc = l_buf[lx++]; + if ((flag & 1) == 0) { + send_code(lc, ltree); /* send a literal byte */ + Tracecv(isgraph(lc), (stderr, " '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = length_code[lc]; + send_code(code + LITERALS + 1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra != 0) { + lc -= base_length[code]; + send_bits(lc, extra); /* send the extra length bits */ + } + dist = d_buf[dx++]; + /* Here, dist is the match distance - 1 */ + code = d_code(dist); + Assert(code < D_CODES, "bad d_code"); + + send_code(code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra != 0) { + dist -= base_dist[code]; + send_bits(dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + flag >>= 1; + } while (lx < last_lit); + + send_code(END_BLOCK, ltree); +} + +/* =========================================================================== + * Set the file type to ASCII or BINARY, using a crude approximation: + * binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + * IN assertion: the fields freq of dyn_ltree are set and the total of all + * frequencies does not exceed 64K (to fit in an int on 16 bit machines). + */ +static void set_file_type(void) +{ + int n = 0; + unsigned ascii_freq = 0; + unsigned bin_freq = 0; + + while (n < 7) + bin_freq += dyn_ltree[n++].Freq; + while (n < 128) + ascii_freq += dyn_ltree[n++].Freq; + while (n < LITERALS) + bin_freq += dyn_ltree[n++].Freq; + *file_type = bin_freq > (ascii_freq >> 2) ? BINARY : ASCII; + if (*file_type == BINARY && translate_eol) { + bb_error_msg("-l used on binary file"); + } +} + +/* zip.c -- compress files to the gzip or pkzip format + * Copyright (C) 1992-1993 Jean-loup Gailly + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + + +static uint32_t crc; /* crc on uncompressed file data */ +static long header_bytes; /* number of bytes in gzip header */ + +static void put_long(ulg n) +{ + put_short((n) & 0xffff); + put_short(((ulg) (n)) >> 16); +} + +/* put_header_byte is used for the compressed output + * - for the initial 4 bytes that can't overflow the buffer. + */ +#define put_header_byte(c) {outbuf[outcnt++]=(uch)(c);} + +/* =========================================================================== + * Deflate in to out. + * IN assertions: the input and output buffers are cleared. + * The variables time_stamp and save_orig_name are initialized. + */ +static int zip(int in, int out) +{ + uch my_flags = 0; /* general purpose bit flags */ + ush attr = 0; /* ascii/binary flag */ + ush deflate_flags = 0; /* pkzip -es, -en or -ex equivalent */ + + ifd = in; + ofd = out; + outcnt = 0; + + /* Write the header to the gzip file. See algorithm.doc for the format */ + + method = DEFLATED; + put_header_byte(GZIP_MAGIC[0]); /* magic header */ + put_header_byte(GZIP_MAGIC[1]); + put_header_byte(DEFLATED); /* compression method */ + + put_header_byte(my_flags); /* general flags */ + put_long(time_stamp); + + /* Write deflated file to zip file */ + crc = updcrc(0, 0); + + bi_init(out); + ct_init(&attr, &method); + lm_init(&deflate_flags); + + put_byte((uch) deflate_flags); /* extra flags */ + put_byte(OS_CODE); /* OS identifier */ + + header_bytes = (long) outcnt; + + (void) deflate(); + + /* Write the crc and uncompressed size */ + put_long(crc); + put_long(isize); + header_bytes += 2 * sizeof(long); + + flush_outbuf(); + return OK; +} + + +/* =========================================================================== + * Read a new buffer from the current input file, perform end-of-line + * translation, and update the crc and input file size. + * IN assertion: size >= 2 (for end-of-line translation) + */ +static int file_read(char *buf, unsigned size) +{ + unsigned len; + + Assert(insize == 0, "inbuf not empty"); + + len = read(ifd, buf, size); + if (len == (unsigned) (-1) || len == 0) + return (int) len; + + crc = updcrc((uch *) buf, len); + isize += (ulg) len; + return (int) len; +} + +/* =========================================================================== + * Write the output buffer outbuf[0..outcnt-1] and update bytes_out. + * (used for the compressed data only) + */ +static void flush_outbuf(void) +{ + if (outcnt == 0) + return; + + write_buf(ofd, (char *) outbuf, outcnt); + outcnt = 0; +} diff --git a/archival/libunarchive/Kbuild b/archival/libunarchive/Kbuild new file mode 100644 index 000000000..4e1454184 --- /dev/null +++ b/archival/libunarchive/Kbuild @@ -0,0 +1,59 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2004 by Erik Andersen +# +# Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + +lib-y:= \ +\ + data_skip.o \ + data_extract_all.o \ + data_extract_to_stdout.o \ + data_extract_to_buffer.o \ +\ + filter_accept_all.o \ + filter_accept_list.o \ + filter_accept_reject_list.o \ +\ + header_skip.o \ + header_list.o \ + header_verbose_list.o \ +\ + archive_xread_all_eof.o \ +\ + seek_by_read.o \ + seek_by_jump.o \ +\ + data_align.o \ + find_list_entry.o \ + open_transformer.o \ + init_handle.o + +GUNZIP_FILES:= check_header_gzip.o decompress_unzip.o +DPKG_FILES:= \ + get_header_ar.o \ + unpack_ar_archive.o \ + get_header_tar.o \ + filter_accept_list_reassign.o + +lib-$(CONFIG_AR) += get_header_ar.o unpack_ar_archive.o +lib-$(CONFIG_BUNZIP2) += decompress_bunzip2.o +lib-$(CONFIG_UNLZMA) += decompress_unlzma.o +lib-$(CONFIG_CPIO) += get_header_cpio.o +lib-$(CONFIG_DPKG) += $(DPKG_FILES) +lib-$(CONFIG_DPKG_DEB) += $(DPKG_FILES) +lib-$(CONFIG_FEATURE_DEB_TAR_GZ) += $(GUNZIP_FILES) get_header_tar_gz.o +lib-$(CONFIG_FEATURE_DEB_TAR_BZ2) += decompress_bunzip2.o get_header_tar_bz2.o +lib-$(CONFIG_FEATURE_DEB_TAR_LZMA) += decompress_unlzma.o get_header_tar_lzma.o +lib-$(CONFIG_GUNZIP) += $(GUNZIP_FILES) +lib-$(CONFIG_FEATURE_GUNZIP_UNCOMPRESS) += decompress_uncompress.o +lib-$(CONFIG_RPM2CPIO) += $(GUNZIP_FILES) get_header_cpio.o +lib-$(CONFIG_RPM) += $(GUNZIP_FILES) get_header_cpio.o +lib-$(CONFIG_TAR) += get_header_tar.o +lib-$(CONFIG_FEATURE_TAR_BZIP2) += decompress_bunzip2.o get_header_tar_bz2.o +lib-$(CONFIG_FEATURE_TAR_LZMA) += decompress_unlzma.o get_header_tar_lzma.o +lib-$(CONFIG_FEATURE_TAR_GZIP) += $(GUNZIP_FILES) get_header_tar_gz.o +lib-$(CONFIG_FEATURE_TAR_COMPRESS) += decompress_uncompress.o +lib-$(CONFIG_UNCOMPRESS) += decompress_uncompress.o +lib-$(CONFIG_UNZIP) += $(GUNZIP_FILES) +lib-$(CONFIG_FEATURE_COMPRESS_USAGE) += decompress_bunzip2.o diff --git a/archival/libunarchive/archive_xread_all_eof.c b/archival/libunarchive/archive_xread_all_eof.c new file mode 100644 index 000000000..007f68c6d --- /dev/null +++ b/archival/libunarchive/archive_xread_all_eof.c @@ -0,0 +1,20 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "unarchive.h" +#include "libbb.h" + +ssize_t archive_xread_all_eof(archive_handle_t *archive_handle, + unsigned char *buf, size_t count) +{ + ssize_t size; + + size = full_read(archive_handle->src_fd, buf, count); + if (size != 0 && size != count) { + bb_error_msg_and_die("short read: %u of %u", + (unsigned)size, (unsigned)count); + } + return size; +} diff --git a/archival/libunarchive/check_header_gzip.c b/archival/libunarchive/check_header_gzip.c new file mode 100644 index 000000000..3e42035b9 --- /dev/null +++ b/archival/libunarchive/check_header_gzip.c @@ -0,0 +1,62 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include +#include +#include "libbb.h" +#include "unarchive.h" /* for external decl of check_header_gzip */ + +void check_header_gzip(int src_fd) +{ + union { + unsigned char raw[8]; + struct { + unsigned char method; + unsigned char flags; + unsigned int mtime; + unsigned char xtra_flags; + unsigned char os_flags; + } formatted; + } header; + + xread(src_fd, header.raw, 8); + + /* Check the compression method */ + if (header.formatted.method != 8) { + bb_error_msg_and_die("unknown compression method %d", + header.formatted.method); + } + + if (header.formatted.flags & 0x04) { + /* bit 2 set: extra field present */ + unsigned char extra_short; + + extra_short = xread_char(src_fd) + (xread_char(src_fd) << 8); + while (extra_short > 0) { + /* Ignore extra field */ + xread_char(src_fd); + extra_short--; + } + } + + /* Discard original name if any */ + if (header.formatted.flags & 0x08) { + /* bit 3 set: original file name present */ + while(xread_char(src_fd) != 0); + } + + /* Discard file comment if any */ + if (header.formatted.flags & 0x10) { + /* bit 4 set: file comment present */ + while(xread_char(src_fd) != 0); + } + + /* Read the header checksum */ + if (header.formatted.flags & 0x02) { + xread_char(src_fd); + xread_char(src_fd); + } + + return; +} diff --git a/archival/libunarchive/data_align.c b/archival/libunarchive/data_align.c new file mode 100644 index 000000000..81594d87c --- /dev/null +++ b/archival/libunarchive/data_align.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include + +#include +#include + +#include "libbb.h" +#include "unarchive.h" + +void data_align(archive_handle_t *archive_handle, const unsigned short boundary) +{ + const unsigned short skip_amount = (boundary - (archive_handle->offset % boundary)) % boundary; + + archive_handle->seek(archive_handle, skip_amount); + archive_handle->offset += skip_amount; + + return; +} diff --git a/archival/libunarchive/data_extract_all.c b/archival/libunarchive/data_extract_all.c new file mode 100644 index 000000000..67f8f3534 --- /dev/null +++ b/archival/libunarchive/data_extract_all.c @@ -0,0 +1,119 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +void data_extract_all(archive_handle_t *archive_handle) +{ + file_header_t *file_header = archive_handle->file_header; + int dst_fd; + int res; + + if (archive_handle->flags & ARCHIVE_CREATE_LEADING_DIRS) { + char *name = xstrdup(file_header->name); + bb_make_directory(dirname(name), -1, FILEUTILS_RECUR); + free(name); + } + + /* Check if the file already exists */ + if (archive_handle->flags & ARCHIVE_EXTRACT_UNCONDITIONAL) { + /* Remove the existing entry if it exists */ + if (((file_header->mode & S_IFMT) != S_IFDIR) + && (unlink(file_header->name) == -1) + && (errno != ENOENT) + ) { + bb_perror_msg_and_die("cannot remove old file"); + } + } + else if (archive_handle->flags & ARCHIVE_EXTRACT_NEWER) { + /* Remove the existing entry if its older than the extracted entry */ + struct stat statbuf; + if (lstat(file_header->name, &statbuf) == -1) { + if (errno != ENOENT) { + bb_perror_msg_and_die("cannot stat old file"); + } + } + else if (statbuf.st_mtime <= file_header->mtime) { + if (!(archive_handle->flags & ARCHIVE_EXTRACT_QUIET)) { + bb_error_msg("%s not created: newer or " + "same age file exists", file_header->name); + } + data_skip(archive_handle); + return; + } + else if ((unlink(file_header->name) == -1) && (errno != EISDIR)) { + bb_perror_msg_and_die("cannot remove old file %s", + file_header->name); + } + } + + /* Handle hard links separately + * We identified hard links as regular files of size 0 with a symlink */ + if (S_ISREG(file_header->mode) && (file_header->link_name) + && (file_header->size == 0) + ) { + /* hard link */ + res = link(file_header->link_name, file_header->name); + if ((res == -1) && !(archive_handle->flags & ARCHIVE_EXTRACT_QUIET)) { + bb_perror_msg("cannot create hard link"); + } + } else { + /* Create the filesystem entry */ + switch (file_header->mode & S_IFMT) { + case S_IFREG: { + /* Regular file */ + dst_fd = xopen3(file_header->name, O_WRONLY | O_CREAT | O_EXCL, + file_header->mode); + bb_copyfd_size(archive_handle->src_fd, dst_fd, file_header->size); + close(dst_fd); + break; + } + case S_IFDIR: + res = mkdir(file_header->name, file_header->mode); + if ((errno != EISDIR) && (res == -1) + && !(archive_handle->flags & ARCHIVE_EXTRACT_QUIET) + ) { + bb_perror_msg("extract_archive: %s", file_header->name); + } + break; + case S_IFLNK: + /* Symlink */ + res = symlink(file_header->link_name, file_header->name); + if ((res == -1) + && !(archive_handle->flags & ARCHIVE_EXTRACT_QUIET) + ) { + bb_perror_msg("cannot create symlink " + "from %s to '%s'", + file_header->name, + file_header->link_name); + } + break; + case S_IFSOCK: + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + res = mknod(file_header->name, file_header->mode, file_header->device); + if ((res == -1) + && !(archive_handle->flags & ARCHIVE_EXTRACT_QUIET) + ) { + bb_perror_msg("cannot create node %s", file_header->name); + } + break; + default: + bb_error_msg_and_die("unrecognized file type"); + } + } + + if (!(archive_handle->flags & ARCHIVE_NOPRESERVE_OWN)) { + lchown(file_header->name, file_header->uid, file_header->gid); + } + + if (archive_handle->flags & ARCHIVE_PRESERVE_DATE) { + struct utimbuf t; + t.actime = t.modtime = file_header->mtime; + utime(file_header->name, &t); + } +} diff --git a/archival/libunarchive/data_extract_to_buffer.c b/archival/libunarchive/data_extract_to_buffer.c new file mode 100644 index 000000000..95cb8f576 --- /dev/null +++ b/archival/libunarchive/data_extract_to_buffer.c @@ -0,0 +1,18 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 2002 Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +void data_extract_to_buffer(archive_handle_t *archive_handle) +{ + const unsigned int size = archive_handle->file_header->size; + + archive_handle->buffer = xzalloc(size + 1); + + xread(archive_handle->src_fd, archive_handle->buffer, size); +} diff --git a/archival/libunarchive/data_extract_to_stdout.c b/archival/libunarchive/data_extract_to_stdout.c new file mode 100644 index 000000000..788246ce7 --- /dev/null +++ b/archival/libunarchive/data_extract_to_stdout.c @@ -0,0 +1,12 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "unarchive.h" +#include + +void data_extract_to_stdout(archive_handle_t *archive_handle) +{ + bb_copyfd_size(archive_handle->src_fd, STDOUT_FILENO, archive_handle->file_header->size); +} diff --git a/archival/libunarchive/data_skip.c b/archival/libunarchive/data_skip.c new file mode 100644 index 000000000..dc3505a56 --- /dev/null +++ b/archival/libunarchive/data_skip.c @@ -0,0 +1,16 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "unarchive.h" +#include "libbb.h" + +void data_skip(archive_handle_t *archive_handle) +{ + archive_handle->seek(archive_handle, archive_handle->file_header->size); +} diff --git a/archival/libunarchive/decompress_bunzip2.c b/archival/libunarchive/decompress_bunzip2.c new file mode 100644 index 000000000..d0a4ecb5e --- /dev/null +++ b/archival/libunarchive/decompress_bunzip2.c @@ -0,0 +1,731 @@ +/* vi: set sw=4 ts=4: */ +/* Small bzip2 deflate implementation, by Rob Landley (rob@landley.net). + + Based on bzip2 decompression code by Julian R Seward (jseward@acm.org), + which also acknowledges contributions by Mike Burrows, David Wheeler, + Peter Fenwick, Alistair Moffat, Radford Neal, Ian H. Witten, + Robert Sedgewick, and Jon L. Bentley. + + Licensed under GPLv2 or later, see file LICENSE in this tarball for details. +*/ + +/* + Size and speed optimizations by Manuel Novoa III (mjn3@codepoet.org). + + More efficient reading of Huffman codes, a streamlined read_bunzip() + function, and various other tweaks. In (limited) tests, approximately + 20% faster than bzcat on x86 and about 10% faster on arm. + + Note that about 2/3 of the time is spent in read_unzip() reversing + the Burrows-Wheeler transformation. Much of that time is delay + resulting from cache misses. + + I would ask that anyone benefiting from this work, especially those + using it in commercial products, consider making a donation to my local + non-profit hospice organization (www.hospiceacadiana.com) in the name of + the woman I loved, Toni W. Hagan, who passed away Feb. 12, 2003. + + Manuel + */ + +#include "libbb.h" +#include "unarchive.h" + +/* Constants for Huffman coding */ +#define MAX_GROUPS 6 +#define GROUP_SIZE 50 /* 64 would have been more efficient */ +#define MAX_HUFCODE_BITS 20 /* Longest Huffman code allowed */ +#define MAX_SYMBOLS 258 /* 256 literals + RUNA + RUNB */ +#define SYMBOL_RUNA 0 +#define SYMBOL_RUNB 1 + +/* Status return values */ +#define RETVAL_OK 0 +#define RETVAL_LAST_BLOCK (-1) +#define RETVAL_NOT_BZIP_DATA (-2) +#define RETVAL_UNEXPECTED_INPUT_EOF (-3) +#define RETVAL_UNEXPECTED_OUTPUT_EOF (-4) +#define RETVAL_DATA_ERROR (-5) +#define RETVAL_OUT_OF_MEMORY (-6) +#define RETVAL_OBSOLETE_INPUT (-7) + +/* Other housekeeping constants */ +#define IOBUF_SIZE 4096 + +/* This is what we know about each Huffman coding group */ +struct group_data { + /* We have an extra slot at the end of limit[] for a sentinal value. */ + int limit[MAX_HUFCODE_BITS+1],base[MAX_HUFCODE_BITS],permute[MAX_SYMBOLS]; + int minLen, maxLen; +}; + +/* Structure holding all the housekeeping data, including IO buffers and + memory that persists between calls to bunzip */ + +typedef struct { + /* State for interrupting output loop */ + + int writeCopies,writePos,writeRunCountdown,writeCount,writeCurrent; + + /* I/O tracking data (file handles, buffers, positions, etc.) */ + + int in_fd,out_fd,inbufCount,inbufPos /*,outbufPos*/; + unsigned char *inbuf /*,*outbuf*/; + unsigned int inbufBitCount, inbufBits; + + /* The CRC values stored in the block header and calculated from the data */ + + uint32_t headerCRC, totalCRC, writeCRC; + uint32_t *crc32Table; + /* Intermediate buffer and its size (in bytes) */ + + unsigned int *dbuf, dbufSize; + + /* These things are a bit too big to go on the stack */ + + unsigned char selectors[32768]; /* nSelectors=15 bits */ + struct group_data groups[MAX_GROUPS]; /* Huffman coding tables */ + + /* For I/O error handling */ + + jmp_buf jmpbuf; +} bunzip_data; + +/* Return the next nnn bits of input. All reads from the compressed input + are done through this function. All reads are big endian */ + +static unsigned int get_bits(bunzip_data *bd, char bits_wanted) +{ + unsigned int bits=0; + + /* If we need to get more data from the byte buffer, do so. (Loop getting + one byte at a time to enforce endianness and avoid unaligned access.) */ + + while (bd->inbufBitCountinbufPos==bd->inbufCount) { + if((bd->inbufCount = read(bd->in_fd, bd->inbuf, IOBUF_SIZE)) <= 0) + longjmp(bd->jmpbuf,RETVAL_UNEXPECTED_INPUT_EOF); + bd->inbufPos=0; + } + + /* Avoid 32-bit overflow (dump bit buffer to top of output) */ + + if(bd->inbufBitCount>=24) { + bits=bd->inbufBits&((1<inbufBitCount)-1); + bits_wanted-=bd->inbufBitCount; + bits<<=bits_wanted; + bd->inbufBitCount=0; + } + + /* Grab next 8 bits of input from buffer. */ + + bd->inbufBits=(bd->inbufBits<<8)|bd->inbuf[bd->inbufPos++]; + bd->inbufBitCount+=8; + } + + /* Calculate result */ + + bd->inbufBitCount-=bits_wanted; + bits|=(bd->inbufBits>>bd->inbufBitCount)&((1<dbuf; + dbufSize=bd->dbufSize; + selectors=bd->selectors; + + /* Reset longjmp I/O error handling */ + + i=setjmp(bd->jmpbuf); + if(i) return i; + + /* Read in header signature and CRC, then validate signature. + (last block signature means CRC is for whole file, return now) */ + + i = get_bits(bd,24); + j = get_bits(bd,24); + bd->headerCRC=get_bits(bd,32); + if ((i == 0x177245) && (j == 0x385090)) return RETVAL_LAST_BLOCK; + if ((i != 0x314159) || (j != 0x265359)) return RETVAL_NOT_BZIP_DATA; + + /* We can add support for blockRandomised if anybody complains. There was + some code for this in busybox 1.0.0-pre3, but nobody ever noticed that + it didn't actually work. */ + + if(get_bits(bd,1)) return RETVAL_OBSOLETE_INPUT; + if((origPtr=get_bits(bd,24)) > dbufSize) return RETVAL_DATA_ERROR; + + /* mapping table: if some byte values are never used (encoding things + like ascii text), the compression code removes the gaps to have fewer + symbols to deal with, and writes a sparse bitfield indicating which + values were present. We make a translation table to convert the symbols + back to the corresponding bytes. */ + + t=get_bits(bd, 16); + symTotal=0; + for (i=0;i<16;i++) { + if(t&(1<<(15-i))) { + k=get_bits(bd,16); + for(j=0;j<16;j++) + if(k&(1<<(15-j))) symToByte[symTotal++]=(16*i)+j; + } + } + + /* How many different Huffman coding groups does this block use? */ + + groupCount=get_bits(bd,3); + if (groupCount<2 || groupCount>MAX_GROUPS) return RETVAL_DATA_ERROR; + + /* nSelectors: Every GROUP_SIZE many symbols we select a new Huffman coding + group. Read in the group selector list, which is stored as MTF encoded + bit runs. (MTF=Move To Front, as each value is used it's moved to the + start of the list.) */ + + if(!(nSelectors=get_bits(bd, 15))) return RETVAL_DATA_ERROR; + for(i=0; i=groupCount) return RETVAL_DATA_ERROR; + + /* Decode MTF to get the next selector */ + + uc = mtfSymbol[j]; + for(;j;j--) mtfSymbol[j] = mtfSymbol[j-1]; + mtfSymbol[0]=selectors[i]=uc; + } + + /* Read the Huffman coding tables for each group, which code for symTotal + literal symbols, plus two run symbols (RUNA, RUNB) */ + + symCount=symTotal+2; + for (j=0; j (MAX_HUFCODE_BITS-1)) + return RETVAL_DATA_ERROR; + + /* If first bit is 0, stop. Else second bit indicates whether + to increment or decrement the value. Optimization: grab 2 + bits and unget the second if the first was 0. */ + + k = get_bits(bd,2); + if (k < 2) { + bd->inbufBitCount++; + break; + } + + /* Add one if second bit 1, else subtract 1. Avoids if/else */ + + t+=(((k+1)&2)-1); + } + + /* Correct for the initial -1, to get the final symbol length */ + + length[i]=t+1; + } + + /* Find largest and smallest lengths in this group */ + + minLen=maxLen=length[0]; + for(i = 1; i < symCount; i++) { + if(length[i] > maxLen) maxLen = length[i]; + else if(length[i] < minLen) minLen = length[i]; + } + + /* Calculate permute[], base[], and limit[] tables from length[]. + * + * permute[] is the lookup table for converting Huffman coded symbols + * into decoded symbols. base[] is the amount to subtract from the + * value of a Huffman symbol of a given length when using permute[]. + * + * limit[] indicates the largest numerical value a symbol with a given + * number of bits can have. This is how the Huffman codes can vary in + * length: each code with a value>limit[length] needs another bit. + */ + + hufGroup=bd->groups+j; + hufGroup->minLen = minLen; + hufGroup->maxLen = maxLen; + + /* Note that minLen can't be smaller than 1, so we adjust the base + and limit array pointers so we're not always wasting the first + entry. We do this again when using them (during symbol decoding).*/ + + base=hufGroup->base-1; + limit=hufGroup->limit-1; + + /* Calculate permute[]. Concurently, initialize temp[] and limit[]. */ + + pp=0; + for(i=minLen;i<=maxLen;i++) { + temp[i]=limit[i]=0; + for(t=0;tpermute[pp++] = t; + } + + /* Count symbols coded for at each bit length */ + + for (i=0;ilimit[length] comparison. */ + + limit[i]= (pp << (maxLen - i)) - 1; + pp<<=1; + base[i+1]=pp-(t+=temp[i]); + } + limit[maxLen+1] = INT_MAX; /* Sentinal value for reading next sym. */ + limit[maxLen]=pp+temp[maxLen]-1; + base[minLen]=0; + } + + /* We've finished reading and digesting the block header. Now read this + block's Huffman coded symbols from the file and undo the Huffman coding + and run length encoding, saving the result into dbuf[dbufCount++]=uc */ + + /* Initialize symbol occurrence counters and symbol Move To Front table */ + + for(i=0;i<256;i++) { + byteCount[i] = 0; + mtfSymbol[i]=(unsigned char)i; + } + + /* Loop through compressed symbols. */ + + runPos=dbufCount=selector=0; + for(;;) { + + /* fetch next Huffman coding group from list. */ + + symCount=GROUP_SIZE-1; + if(selector>=nSelectors) return RETVAL_DATA_ERROR; + hufGroup=bd->groups+selectors[selector++]; + base=hufGroup->base-1; + limit=hufGroup->limit-1; +continue_this_group: + + /* Read next Huffman-coded symbol. */ + + /* Note: It is far cheaper to read maxLen bits and back up than it is + to read minLen bits and then an additional bit at a time, testing + as we go. Because there is a trailing last block (with file CRC), + there is no danger of the overread causing an unexpected EOF for a + valid compressed file. As a further optimization, we do the read + inline (falling back to a call to get_bits if the buffer runs + dry). The following (up to got_huff_bits:) is equivalent to + j=get_bits(bd,hufGroup->maxLen); + */ + + while (bd->inbufBitCountmaxLen) { + if(bd->inbufPos==bd->inbufCount) { + j = get_bits(bd,hufGroup->maxLen); + goto got_huff_bits; + } + bd->inbufBits=(bd->inbufBits<<8)|bd->inbuf[bd->inbufPos++]; + bd->inbufBitCount+=8; + }; + bd->inbufBitCount-=hufGroup->maxLen; + j = (bd->inbufBits>>bd->inbufBitCount)&((1<maxLen)-1); + +got_huff_bits: + + /* Figure how how many bits are in next symbol and unget extras */ + + i=hufGroup->minLen; + while(j>limit[i]) ++i; + bd->inbufBitCount += (hufGroup->maxLen - i); + + /* Huffman decode value to get nextSym (with bounds checking) */ + + if ((i > hufGroup->maxLen) + || (((unsigned)(j=(j>>(hufGroup->maxLen-i))-base[i])) + >= MAX_SYMBOLS)) + return RETVAL_DATA_ERROR; + nextSym = hufGroup->permute[j]; + + /* We have now decoded the symbol, which indicates either a new literal + byte, or a repeated run of the most recent literal byte. First, + check if nextSym indicates a repeated run, and if so loop collecting + how many times to repeat the last literal. */ + + if (((unsigned)nextSym) <= SYMBOL_RUNB) { /* RUNA or RUNB */ + + /* If this is the start of a new run, zero out counter */ + + if(!runPos) { + runPos = 1; + t = 0; + } + + /* Neat trick that saves 1 symbol: instead of or-ing 0 or 1 at + each bit position, add 1 or 2 instead. For example, + 1011 is 1<<0 + 1<<1 + 2<<2. 1010 is 2<<0 + 2<<1 + 1<<2. + You can make any bit pattern that way using 1 less symbol than + the basic or 0/1 method (except all bits 0, which would use no + symbols, but a run of length 0 doesn't mean anything in this + context). Thus space is saved. */ + + t += (runPos << nextSym); /* +runPos if RUNA; +2*runPos if RUNB */ + if(runPos < dbufSize) runPos <<= 1; + goto end_of_huffman_loop; + } + + /* When we hit the first non-run symbol after a run, we now know + how many times to repeat the last literal, so append that many + copies to our buffer of decoded symbols (dbuf) now. (The last + literal used is the one at the head of the mtfSymbol array.) */ + + if(runPos) { + runPos=0; + if(dbufCount+t>=dbufSize) return RETVAL_DATA_ERROR; + + uc = symToByte[mtfSymbol[0]]; + byteCount[uc] += t; + while(t--) dbuf[dbufCount++]=uc; + } + + /* Is this the terminating symbol? */ + + if(nextSym>symTotal) break; + + /* At this point, nextSym indicates a new literal character. Subtract + one to get the position in the MTF array at which this literal is + currently to be found. (Note that the result can't be -1 or 0, + because 0 and 1 are RUNA and RUNB. But another instance of the + first symbol in the mtf array, position 0, would have been handled + as part of a run above. Therefore 1 unused mtf position minus + 2 non-literal nextSym values equals -1.) */ + + if(dbufCount>=dbufSize) return RETVAL_DATA_ERROR; + i = nextSym - 1; + uc = mtfSymbol[i]; + + /* Adjust the MTF array. Since we typically expect to move only a + * small number of symbols, and are bound by 256 in any case, using + * memmove here would typically be bigger and slower due to function + * call overhead and other assorted setup costs. */ + + do { + mtfSymbol[i] = mtfSymbol[i-1]; + } while (--i); + mtfSymbol[0] = uc; + uc=symToByte[uc]; + + /* We have our literal byte. Save it into dbuf. */ + + byteCount[uc]++; + dbuf[dbufCount++] = (unsigned int)uc; + + /* Skip group initialization if we're not done with this group. Done + * this way to avoid compiler warning. */ + +end_of_huffman_loop: + if(symCount--) goto continue_this_group; + } + + /* At this point, we've read all the Huffman-coded symbols (and repeated + runs) for this block from the input stream, and decoded them into the + intermediate buffer. There are dbufCount many decoded bytes in dbuf[]. + Now undo the Burrows-Wheeler transform on dbuf. + See http://dogma.net/markn/articles/bwt/bwt.htm + */ + + /* Turn byteCount into cumulative occurrence counts of 0 to n-1. */ + + j=0; + for(i=0;i<256;i++) { + k=j+byteCount[i]; + byteCount[i] = j; + j=k; + } + + /* Figure out what order dbuf would be in if we sorted it. */ + + for (i=0;i=dbufCount) return RETVAL_DATA_ERROR; + bd->writePos=dbuf[origPtr]; + bd->writeCurrent=(unsigned char)(bd->writePos&0xff); + bd->writePos>>=8; + bd->writeRunCountdown=5; + } + bd->writeCount=dbufCount; + + return RETVAL_OK; +} + +/* Undo burrows-wheeler transform on intermediate buffer to produce output. + If start_bunzip was initialized with out_fd=-1, then up to len bytes of + data are written to outbuf. Return value is number of bytes written or + error (all errors are negative numbers). If out_fd!=-1, outbuf and len + are ignored, data is written to out_fd and return is RETVAL_OK or error. +*/ + +static int read_bunzip(bunzip_data *bd, char *outbuf, int len) +{ + const unsigned int *dbuf; + int pos,current,previous,gotcount; + + /* If last read was short due to end of file, return last block now */ + if(bd->writeCount<0) return bd->writeCount; + + gotcount = 0; + dbuf=bd->dbuf; + pos=bd->writePos; + current=bd->writeCurrent; + + /* We will always have pending decoded data to write into the output + buffer unless this is the very first call (in which case we haven't + Huffman-decoded a block into the intermediate buffer yet). */ + + if (bd->writeCopies) { + + /* Inside the loop, writeCopies means extra copies (beyond 1) */ + + --bd->writeCopies; + + /* Loop outputting bytes */ + + for(;;) { + + /* If the output buffer is full, snapshot state and return */ + + if(gotcount >= len) { + bd->writePos=pos; + bd->writeCurrent=current; + bd->writeCopies++; + return len; + } + + /* Write next byte into output buffer, updating CRC */ + + outbuf[gotcount++] = current; + bd->writeCRC=(((bd->writeCRC)<<8) + ^bd->crc32Table[((bd->writeCRC)>>24)^current]); + + /* Loop now if we're outputting multiple copies of this byte */ + + if (bd->writeCopies) { + --bd->writeCopies; + continue; + } +decode_next_byte: + if (!bd->writeCount--) break; + /* Follow sequence vector to undo Burrows-Wheeler transform */ + previous=current; + pos=dbuf[pos]; + current=pos&0xff; + pos>>=8; + + /* After 3 consecutive copies of the same byte, the 4th is a repeat + count. We count down from 4 instead + * of counting up because testing for non-zero is faster */ + + if(--bd->writeRunCountdown) { + if(current!=previous) bd->writeRunCountdown=4; + } else { + + /* We have a repeated run, this byte indicates the count */ + + bd->writeCopies=current; + current=previous; + bd->writeRunCountdown=5; + + /* Sometimes there are just 3 bytes (run length 0) */ + + if(!bd->writeCopies) goto decode_next_byte; + + /* Subtract the 1 copy we'd output anyway to get extras */ + + --bd->writeCopies; + } + } + + /* Decompression of this block completed successfully */ + + bd->writeCRC=~bd->writeCRC; + bd->totalCRC=((bd->totalCRC<<1) | (bd->totalCRC>>31)) ^ bd->writeCRC; + + /* If this block had a CRC error, force file level CRC error. */ + + if(bd->writeCRC!=bd->headerCRC) { + bd->totalCRC=bd->headerCRC+1; + return RETVAL_LAST_BLOCK; + } + } + + /* Refill the intermediate buffer by Huffman-decoding next block of input */ + /* (previous is just a convenient unused temp variable here) */ + + previous=get_next_block(bd); + if(previous) { + bd->writeCount=previous; + return (previous!=RETVAL_LAST_BLOCK) ? previous : gotcount; + } + bd->writeCRC=~0; + pos=bd->writePos; + current=bd->writeCurrent; + goto decode_next_byte; +} + +/* Allocate the structure, read file header. If in_fd==-1, inbuf must contain + a complete bunzip file (len bytes long). If in_fd!=-1, inbuf and len are + ignored, and data is read from file handle into temporary buffer. */ + +static int start_bunzip(bunzip_data **bdp, int in_fd, unsigned char *inbuf, + int len) +{ + bunzip_data *bd; + unsigned int i; + const unsigned int BZh0=(((unsigned int)'B')<<24)+(((unsigned int)'Z')<<16) + +(((unsigned int)'h')<<8)+(unsigned int)'0'; + + /* Figure out how much data to allocate */ + + i=sizeof(bunzip_data); + if(in_fd!=-1) i+=IOBUF_SIZE; + + /* Allocate bunzip_data. Most fields initialize to zero. */ + + bd=*bdp=xzalloc(i); + + /* Setup input buffer */ + + if(-1==(bd->in_fd=in_fd)) { + bd->inbuf=inbuf; + bd->inbufCount=len; + } else bd->inbuf=(unsigned char *)(bd+1); + + /* Init the CRC32 table (big endian) */ + + bd->crc32Table = crc32_filltable(1); + + /* Setup for I/O error handling via longjmp */ + + i=setjmp(bd->jmpbuf); + if(i) return i; + + /* Ensure that file starts with "BZh['1'-'9']." */ + + i = get_bits(bd,32); + if (((unsigned int)(i-BZh0-1)) >= 9) return RETVAL_NOT_BZIP_DATA; + + /* Fourth byte (ascii '1'-'9'), indicates block size in units of 100k of + uncompressed data. Allocate intermediate buffer for block. */ + + bd->dbufSize=100000*(i-BZh0); + + bd->dbuf=xmalloc(bd->dbufSize * sizeof(int)); + return RETVAL_OK; +} + +/* Example usage: decompress src_fd to dst_fd. (Stops at end of bzip data, + not end of file.) */ + +USE_DESKTOP(long long) int +uncompressStream(int src_fd, int dst_fd) +{ + USE_DESKTOP(long long total_written = 0;) + char *outbuf; + bunzip_data *bd; + int i; + + outbuf=xmalloc(IOBUF_SIZE); + i=start_bunzip(&bd,src_fd,0,0); + if(!i) { + for(;;) { + if((i=read_bunzip(bd,outbuf,IOBUF_SIZE)) <= 0) break; + if(i!=write(dst_fd,outbuf,i)) { + i=RETVAL_UNEXPECTED_OUTPUT_EOF; + break; + } + USE_DESKTOP(total_written += i;) + } + } + + /* Check CRC and release memory */ + + if(i==RETVAL_LAST_BLOCK) { + if (bd->headerCRC!=bd->totalCRC) { + bb_error_msg("data integrity error when decompressing"); + } else { + i=RETVAL_OK; + } + } else if (i==RETVAL_UNEXPECTED_OUTPUT_EOF) { + bb_error_msg("compressed file ends unexpectedly"); + } else { + bb_error_msg("decompression failed"); + } + free(bd->dbuf); + free(bd); + free(outbuf); + + return i ? i : USE_DESKTOP(total_written) + 0; +} + +#ifdef TESTING + +static char * const bunzip_errors[]={NULL,"Bad file checksum","Not bzip data", + "Unexpected input EOF","Unexpected output EOF","Data error", + "Out of memory","Obsolete (pre 0.9.5) bzip format not supported."}; + +/* Dumb little test thing, decompress stdin to stdout */ +int main(int argc, char *argv[]) +{ + int i=uncompressStream(0,1); + char c; + + if(i<0) fprintf(stderr,"%s\n", bunzip_errors[-i]); + else if(read(0,&c,1)) fprintf(stderr,"Trailing garbage ignored\n"); + return -i; +} +#endif diff --git a/archival/libunarchive/decompress_uncompress.c b/archival/libunarchive/decompress_uncompress.c new file mode 100644 index 000000000..ff98254ff --- /dev/null +++ b/archival/libunarchive/decompress_uncompress.c @@ -0,0 +1,292 @@ +/* vi: set sw=4 ts=4: */ +#include "libbb.h" + +/* uncompress for busybox -- (c) 2002 Robert Griebl + * + * based on the original compress42.c source + * (see disclaimer below) + */ + + +/* (N)compress42.c - File compression ala IEEE Computer, Mar 1992. + * + * Authors: + * Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + * Jim McKie (decvax!mcvax!jim) + * Steve Davies (decvax!vax135!petsd!peora!srd) + * Ken Turkowski (decvax!decwrl!turtlevax!ken) + * James A. Woods (decvax!ihnp4!ames!jaw) + * Joe Orost (decvax!vax135!petsd!joe) + * Dave Mack (csu@alembic.acs.com) + * Peter Jannesen, Network Communication Systems + * (peter@ncs.nl) + * + * marc@suse.de : a small security fix for a buffer overflow + * + * [... History snipped ...] + * + */ +#include +#include +#include + +/* Default input buffer size */ +#define IBUFSIZ 2048 + +/* Default output buffer size */ +#define OBUFSIZ 2048 + +/* Defines for third byte of header */ +#define MAGIC_1 (char_type)'\037' /* First byte of compressed file */ +#define MAGIC_2 (char_type)'\235' /* Second byte of compressed file */ +#define BIT_MASK 0x1f /* Mask for 'number of compresssion bits' */ + /* Masks 0x20 and 0x40 are free. */ + /* I think 0x20 should mean that there is */ + /* a fourth header byte (for expansion). */ +#define BLOCK_MODE 0x80 /* Block compresssion if table is full and */ + /* compression rate is dropping flush tables */ + /* the next two codes should not be changed lightly, as they must not */ + /* lie within the contiguous general code space. */ +#define FIRST 257 /* first free entry */ +#define CLEAR 256 /* table clear output code */ + +#define INIT_BITS 9 /* initial number of bits/code */ + + +/* machine variants which require cc -Dmachine: pdp11, z8000, DOS */ +#define FAST + +#define HBITS 17 /* 50% occupancy */ +#define HSIZE (1< BITS) { + bb_error_msg("compressed with %d bits, can only handle %d bits", maxbits, + BITS); + return -1; + } + + maxcode = MAXCODE(n_bits = INIT_BITS) - 1; + bitmask = (1 << n_bits) - 1; + oldcode = -1; + finchar = 0; + outpos = 0; + posbits = 0 << 3; + + free_ent = ((block_mode) ? FIRST : 256); + + /* As above, initialize the first 256 entries in the table. */ + clear_tab_prefixof(); + + for (code = 255; code >= 0; --code) { + tab_suffixof(code) = (unsigned char) code; + } + + do { + resetbuf:; + { + int i; + int e; + int o; + + e = insize - (o = (posbits >> 3)); + + for (i = 0; i < e; ++i) + inbuf[i] = inbuf[i + o]; + + insize = e; + posbits = 0; + } + + if (insize < (int) (IBUFSIZ + 64) - IBUFSIZ) { + rsize = safe_read(fd_in, inbuf + insize, IBUFSIZ); + insize += rsize; + } + + inbits = ((rsize > 0) ? (insize - insize % n_bits) << 3 : + (insize << 3) - (n_bits - 1)); + + while (inbits > posbits) { + if (free_ent > maxcode) { + posbits = + ((posbits - 1) + + ((n_bits << 3) - + (posbits - 1 + (n_bits << 3)) % (n_bits << 3))); + ++n_bits; + if (n_bits == maxbits) { + maxcode = maxmaxcode; + } else { + maxcode = MAXCODE(n_bits) - 1; + } + bitmask = (1 << n_bits) - 1; + goto resetbuf; + } + { + unsigned char *p = &inbuf[posbits >> 3]; + + code = ((((long) (p[0])) | ((long) (p[1]) << 8) | + ((long) (p[2]) << 16)) >> (posbits & 0x7)) & bitmask; + } + posbits += n_bits; + + + if (oldcode == -1) { + oldcode = code; + finchar = (int) oldcode; + outbuf[outpos++] = (unsigned char) finchar; + continue; + } + + if (code == CLEAR && block_mode) { + clear_tab_prefixof(); + free_ent = FIRST - 1; + posbits = + ((posbits - 1) + + ((n_bits << 3) - + (posbits - 1 + (n_bits << 3)) % (n_bits << 3))); + maxcode = MAXCODE(n_bits = INIT_BITS) - 1; + bitmask = (1 << n_bits) - 1; + goto resetbuf; + } + + incode = code; + stackp = de_stack; + + /* Special case for KwKwK string. */ + if (code >= free_ent) { + if (code > free_ent) { + unsigned char *p; + + posbits -= n_bits; + p = &inbuf[posbits >> 3]; + + bb_error_msg + ("insize:%d posbits:%d inbuf:%02X %02X %02X %02X %02X (%d)", + insize, posbits, p[-1], p[0], p[1], p[2], p[3], + (posbits & 07)); + bb_error_msg("uncompress: corrupt input"); + return -1; + } + + *--stackp = (unsigned char) finchar; + code = oldcode; + } + + /* Generate output characters in reverse order */ + while ((long int) code >= (long int) 256) { + *--stackp = tab_suffixof(code); + code = tab_prefixof(code); + } + + *--stackp = (unsigned char) (finchar = tab_suffixof(code)); + + /* And put them out in forward order */ + { + int i; + + if (outpos + (i = (de_stack - stackp)) >= OBUFSIZ) { + do { + if (i > OBUFSIZ - outpos) { + i = OBUFSIZ - outpos; + } + + if (i > 0) { + memcpy(outbuf + outpos, stackp, i); + outpos += i; + } + + if (outpos >= OBUFSIZ) { + write(fd_out, outbuf, outpos); + USE_DESKTOP(total_written += outpos;) + outpos = 0; + } + stackp += i; + } while ((i = (de_stack - stackp)) > 0); + } else { + memcpy(outbuf + outpos, stackp, i); + outpos += i; + } + } + + /* Generate the new entry. */ + if ((code = free_ent) < maxmaxcode) { + tab_prefixof(code) = (unsigned short) oldcode; + tab_suffixof(code) = (unsigned char) finchar; + free_ent = code + 1; + } + + /* Remember previous code. */ + oldcode = incode; + } + + } while (rsize > 0); + + if (outpos > 0) { + write(fd_out, outbuf, outpos); + USE_DESKTOP(total_written += outpos;) + } + + RELEASE_CONFIG_BUFFER(inbuf); + RELEASE_CONFIG_BUFFER(outbuf); + return USE_DESKTOP(total_written) + 0; +} diff --git a/archival/libunarchive/decompress_unlzma.c b/archival/libunarchive/decompress_unlzma.c new file mode 100644 index 000000000..a6902807b --- /dev/null +++ b/archival/libunarchive/decompress_unlzma.c @@ -0,0 +1,478 @@ +/* vi: set sw=4 ts=4: */ +/* + * Small lzma deflate implementation. + * Copyright (C) 2006 Aurelien Jacobs + * + * Based on LzmaDecode.c from the LZMA SDK 4.22 (http://www.7-zip.org/) + * Copyright (C) 1999-2005 Igor Pavlov + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +#ifdef CONFIG_FEATURE_LZMA_FAST +# define speed_inline ATTRIBUTE_ALWAYS_INLINE +#else +# define speed_inline +#endif + + +typedef struct { + int fd; + uint8_t *ptr; + uint8_t *buffer; + uint8_t *buffer_end; + int buffer_size; + uint32_t code; + uint32_t range; + uint32_t bound; +} rc_t; + + +#define RC_TOP_BITS 24 +#define RC_MOVE_BITS 5 +#define RC_MODEL_TOTAL_BITS 11 + + +/* Called twice: once at startup and once in rc_normalize() */ +static void rc_read(rc_t * rc) +{ + rc->buffer_size = read(rc->fd, rc->buffer, rc->buffer_size); + if (rc->buffer_size <= 0) + bb_error_msg_and_die("unexpected EOF"); + rc->ptr = rc->buffer; + rc->buffer_end = rc->buffer + rc->buffer_size; +} + +/* Called once */ +static void rc_init(rc_t * rc, int fd, int buffer_size) +{ + int i; + + rc->fd = fd; + rc->buffer = xmalloc(buffer_size); + rc->buffer_size = buffer_size; + rc->buffer_end = rc->buffer + rc->buffer_size; + rc->ptr = rc->buffer_end; + + rc->code = 0; + rc->range = 0xFFFFFFFF; + for (i = 0; i < 5; i++) { + if (rc->ptr >= rc->buffer_end) + rc_read(rc); + rc->code = (rc->code << 8) | *rc->ptr++; + } +} + +/* Called once. TODO: bb_maybe_free() */ +static ATTRIBUTE_ALWAYS_INLINE void rc_free(rc_t * rc) +{ + if (ENABLE_FEATURE_CLEAN_UP) + free(rc->buffer); +} + +/* Called twice, but one callsite is in speed_inline'd rc_is_bit_0_helper() */ +static void rc_do_normalize(rc_t * rc) +{ + if (rc->ptr >= rc->buffer_end) + rc_read(rc); + rc->range <<= 8; + rc->code = (rc->code << 8) | *rc->ptr++; +} +static ATTRIBUTE_ALWAYS_INLINE void rc_normalize(rc_t * rc) +{ + if (rc->range < (1 << RC_TOP_BITS)) { + rc_do_normalize(rc); + } +} + +/* Called 9 times */ +/* Why rc_is_bit_0_helper exists? + * Because we want to always expose (rc->code < rc->bound) to optimizer + */ +static speed_inline uint32_t rc_is_bit_0_helper(rc_t * rc, uint16_t * p) +{ + rc_normalize(rc); + rc->bound = *p * (rc->range >> RC_MODEL_TOTAL_BITS); + return rc->bound; +} +static ATTRIBUTE_ALWAYS_INLINE int rc_is_bit_0(rc_t * rc, uint16_t * p) +{ + uint32_t t = rc_is_bit_0_helper(rc, p); + return rc->code < t; +} + +/* Called ~10 times, but very small, thus inlined */ +static speed_inline void rc_update_bit_0(rc_t * rc, uint16_t * p) +{ + rc->range = rc->bound; + *p += ((1 << RC_MODEL_TOTAL_BITS) - *p) >> RC_MOVE_BITS; +} +static speed_inline void rc_update_bit_1(rc_t * rc, uint16_t * p) +{ + rc->range -= rc->bound; + rc->code -= rc->bound; + *p -= *p >> RC_MOVE_BITS; +} + +/* Called 4 times in unlzma loop */ +static int rc_get_bit(rc_t * rc, uint16_t * p, int *symbol) +{ + if (rc_is_bit_0(rc, p)) { + rc_update_bit_0(rc, p); + *symbol *= 2; + return 0; + } else { + rc_update_bit_1(rc, p); + *symbol = *symbol * 2 + 1; + return 1; + } +} + +/* Called once */ +static ATTRIBUTE_ALWAYS_INLINE int rc_direct_bit(rc_t * rc) +{ + rc_normalize(rc); + rc->range >>= 1; + if (rc->code >= rc->range) { + rc->code -= rc->range; + return 1; + } + return 0; +} + +/* Called twice */ +static speed_inline void +rc_bit_tree_decode(rc_t * rc, uint16_t * p, int num_levels, int *symbol) +{ + int i = num_levels; + + *symbol = 1; + while (i--) + rc_get_bit(rc, p + *symbol, symbol); + *symbol -= 1 << num_levels; +} + + +typedef struct { + uint8_t pos; + uint32_t dict_size; + uint64_t dst_size; +} __attribute__ ((packed)) lzma_header_t; + + +#define LZMA_BASE_SIZE 1846 +#define LZMA_LIT_SIZE 768 + +#define LZMA_NUM_POS_BITS_MAX 4 + +#define LZMA_LEN_NUM_LOW_BITS 3 +#define LZMA_LEN_NUM_MID_BITS 3 +#define LZMA_LEN_NUM_HIGH_BITS 8 + +#define LZMA_LEN_CHOICE 0 +#define LZMA_LEN_CHOICE_2 (LZMA_LEN_CHOICE + 1) +#define LZMA_LEN_LOW (LZMA_LEN_CHOICE_2 + 1) +#define LZMA_LEN_MID (LZMA_LEN_LOW \ + + (1 << (LZMA_NUM_POS_BITS_MAX + LZMA_LEN_NUM_LOW_BITS))) +#define LZMA_LEN_HIGH (LZMA_LEN_MID \ + +(1 << (LZMA_NUM_POS_BITS_MAX + LZMA_LEN_NUM_MID_BITS))) +#define LZMA_NUM_LEN_PROBS (LZMA_LEN_HIGH + (1 << LZMA_LEN_NUM_HIGH_BITS)) + +#define LZMA_NUM_STATES 12 +#define LZMA_NUM_LIT_STATES 7 + +#define LZMA_START_POS_MODEL_INDEX 4 +#define LZMA_END_POS_MODEL_INDEX 14 +#define LZMA_NUM_FULL_DISTANCES (1 << (LZMA_END_POS_MODEL_INDEX >> 1)) + +#define LZMA_NUM_POS_SLOT_BITS 6 +#define LZMA_NUM_LEN_TO_POS_STATES 4 + +#define LZMA_NUM_ALIGN_BITS 4 + +#define LZMA_MATCH_MIN_LEN 2 + +#define LZMA_IS_MATCH 0 +#define LZMA_IS_REP (LZMA_IS_MATCH + (LZMA_NUM_STATES <= (9 * 5 * 5)) + bb_error_msg_and_die("bad header"); + mi = header.pos / 9; + lc = header.pos % 9; + pb = mi / 5; + lp = mi % 5; + pos_state_mask = (1 << pb) - 1; + literal_pos_mask = (1 << lp) - 1; + + header.dict_size = SWAP_LE32(header.dict_size); + header.dst_size = SWAP_LE64(header.dst_size); + + if (header.dict_size == 0) + header.dict_size = 1; + + buffer = xmalloc(MIN(header.dst_size, header.dict_size)); + + num_probs = LZMA_BASE_SIZE + (LZMA_LIT_SIZE << (lc + lp)); + p = xmalloc(num_probs * sizeof(*p)); + num_probs = LZMA_LITERAL + (LZMA_LIT_SIZE << (lc + lp)); + for (i = 0; i < num_probs; i++) + p[i] = (1 << RC_MODEL_TOTAL_BITS) >> 1; + + rc_init(&rc, src_fd, 0x10000); + + while (global_pos + buffer_pos < header.dst_size) { + int pos_state = (buffer_pos + global_pos) & pos_state_mask; + + prob = + p + LZMA_IS_MATCH + (state << LZMA_NUM_POS_BITS_MAX) + pos_state; + if (rc_is_bit_0(&rc, prob)) { + mi = 1; + rc_update_bit_0(&rc, prob); + prob = (p + LZMA_LITERAL + (LZMA_LIT_SIZE + * ((((buffer_pos + global_pos) & literal_pos_mask) << lc) + + (previous_byte >> (8 - lc))))); + + if (state >= LZMA_NUM_LIT_STATES) { + int match_byte; + + pos = buffer_pos - rep0; + while (pos >= header.dict_size) + pos += header.dict_size; + match_byte = buffer[pos]; + do { + int bit; + + match_byte <<= 1; + bit = match_byte & 0x100; + prob_lit = prob + 0x100 + bit + mi; + if (rc_get_bit(&rc, prob_lit, &mi)) { + if (!bit) + break; + } else { + if (bit) + break; + } + } while (mi < 0x100); + } + while (mi < 0x100) { + prob_lit = prob + mi; + rc_get_bit(&rc, prob_lit, &mi); + } + previous_byte = (uint8_t) mi; + + buffer[buffer_pos++] = previous_byte; + if (buffer_pos == header.dict_size) { + buffer_pos = 0; + global_pos += header.dict_size; + // FIXME: error check + write(dst_fd, buffer, header.dict_size); + USE_DESKTOP(total_written += header.dict_size;) + } + if (state < 4) + state = 0; + else if (state < 10) + state -= 3; + else + state -= 6; + } else { + int offset; + uint16_t *prob_len; + + rc_update_bit_1(&rc, prob); + prob = p + LZMA_IS_REP + state; + if (rc_is_bit_0(&rc, prob)) { + rc_update_bit_0(&rc, prob); + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + state = state < LZMA_NUM_LIT_STATES ? 0 : 3; + prob = p + LZMA_LEN_CODER; + } else { + rc_update_bit_1(&rc, prob); + prob = p + LZMA_IS_REP_G0 + state; + if (rc_is_bit_0(&rc, prob)) { + rc_update_bit_0(&rc, prob); + prob = (p + LZMA_IS_REP_0_LONG + + (state << LZMA_NUM_POS_BITS_MAX) + pos_state); + if (rc_is_bit_0(&rc, prob)) { + rc_update_bit_0(&rc, prob); + + state = state < LZMA_NUM_LIT_STATES ? 9 : 11; + pos = buffer_pos - rep0; + while (pos >= header.dict_size) + pos += header.dict_size; + previous_byte = buffer[pos]; + buffer[buffer_pos++] = previous_byte; + if (buffer_pos == header.dict_size) { + buffer_pos = 0; + global_pos += header.dict_size; + // FIXME: error check + write(dst_fd, buffer, header.dict_size); + USE_DESKTOP(total_written += header.dict_size;) + } + continue; + } else { + rc_update_bit_1(&rc, prob); + } + } else { + uint32_t distance; + + rc_update_bit_1(&rc, prob); + prob = p + LZMA_IS_REP_G1 + state; + if (rc_is_bit_0(&rc, prob)) { + rc_update_bit_0(&rc, prob); + distance = rep1; + } else { + rc_update_bit_1(&rc, prob); + prob = p + LZMA_IS_REP_G2 + state; + if (rc_is_bit_0(&rc, prob)) { + rc_update_bit_0(&rc, prob); + distance = rep2; + } else { + rc_update_bit_1(&rc, prob); + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + state = state < LZMA_NUM_LIT_STATES ? 8 : 11; + prob = p + LZMA_REP_LEN_CODER; + } + + prob_len = prob + LZMA_LEN_CHOICE; + if (rc_is_bit_0(&rc, prob_len)) { + rc_update_bit_0(&rc, prob_len); + prob_len = (prob + LZMA_LEN_LOW + + (pos_state << LZMA_LEN_NUM_LOW_BITS)); + offset = 0; + num_bits = LZMA_LEN_NUM_LOW_BITS; + } else { + rc_update_bit_1(&rc, prob_len); + prob_len = prob + LZMA_LEN_CHOICE_2; + if (rc_is_bit_0(&rc, prob_len)) { + rc_update_bit_0(&rc, prob_len); + prob_len = (prob + LZMA_LEN_MID + + (pos_state << LZMA_LEN_NUM_MID_BITS)); + offset = 1 << LZMA_LEN_NUM_LOW_BITS; + num_bits = LZMA_LEN_NUM_MID_BITS; + } else { + rc_update_bit_1(&rc, prob_len); + prob_len = prob + LZMA_LEN_HIGH; + offset = ((1 << LZMA_LEN_NUM_LOW_BITS) + + (1 << LZMA_LEN_NUM_MID_BITS)); + num_bits = LZMA_LEN_NUM_HIGH_BITS; + } + } + rc_bit_tree_decode(&rc, prob_len, num_bits, &len); + len += offset; + + if (state < 4) { + int pos_slot; + + state += LZMA_NUM_LIT_STATES; + prob = + p + LZMA_POS_SLOT + + ((len < + LZMA_NUM_LEN_TO_POS_STATES ? len : + LZMA_NUM_LEN_TO_POS_STATES - 1) + << LZMA_NUM_POS_SLOT_BITS); + rc_bit_tree_decode(&rc, prob, LZMA_NUM_POS_SLOT_BITS, + &pos_slot); + if (pos_slot >= LZMA_START_POS_MODEL_INDEX) { + num_bits = (pos_slot >> 1) - 1; + rep0 = 2 | (pos_slot & 1); + if (pos_slot < LZMA_END_POS_MODEL_INDEX) { + rep0 <<= num_bits; + prob = p + LZMA_SPEC_POS + rep0 - pos_slot - 1; + } else { + num_bits -= LZMA_NUM_ALIGN_BITS; + while (num_bits--) + rep0 = (rep0 << 1) | rc_direct_bit(&rc); + prob = p + LZMA_ALIGN; + rep0 <<= LZMA_NUM_ALIGN_BITS; + num_bits = LZMA_NUM_ALIGN_BITS; + } + i = 1; + mi = 1; + while (num_bits--) { + if (rc_get_bit(&rc, prob + mi, &mi)) + rep0 |= i; + i <<= 1; + } + } else + rep0 = pos_slot; + if (++rep0 == 0) + break; + } + + len += LZMA_MATCH_MIN_LEN; + + do { + pos = buffer_pos - rep0; + while (pos >= header.dict_size) + pos += header.dict_size; + previous_byte = buffer[pos]; + buffer[buffer_pos++] = previous_byte; + if (buffer_pos == header.dict_size) { + buffer_pos = 0; + global_pos += header.dict_size; + // FIXME: error check + write(dst_fd, buffer, header.dict_size); + USE_DESKTOP(total_written += header.dict_size;) + } + len--; + } while (len != 0 && buffer_pos < header.dst_size); + } + } + + // FIXME: error check + write(dst_fd, buffer, buffer_pos); + USE_DESKTOP(total_written += buffer_pos;) + rc_free(&rc); + return USE_DESKTOP(total_written) + 0; +} diff --git a/archival/libunarchive/decompress_unzip.c b/archival/libunarchive/decompress_unzip.c new file mode 100644 index 000000000..621d84c2d --- /dev/null +++ b/archival/libunarchive/decompress_unzip.c @@ -0,0 +1,924 @@ +/* vi: set sw=4 ts=4: */ +/* + * gunzip implementation for busybox + * + * Based on GNU gzip v1.2.4 Copyright (C) 1992-1993 Jean-loup Gailly. + * + * Originally adjusted for busybox by Sven Rudolph + * based on gzip sources + * + * Adjusted further by Erik Andersen to support + * files as well as stdin/stdout, and to generally behave itself wrt + * command line handling. + * + * General cleanup to better adhere to the style guide and make use of standard + * busybox functions by Glenn McGrath + * + * read_gz interface + associated hacking by Laurence Anderson + * + * Fixed huft_build() so decoding end-of-block code does not grab more bits + * than necessary (this is required by unzip applet), added inflate_cleanup() + * to free leaked bytebuffer memory (used in unzip.c), and some minor style + * guide cleanups by Ed Clark + * + * gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface + * Copyright (C) 1992-1993 Jean-loup Gailly + * The unzip code was written and put in the public domain by Mark Adler. + * Portions of the lzw code are derived from the public domain 'compress' + * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies, + * Ken Turkowski, Dave Mack and Peter Jannesen. + * + * See the file algorithm.doc for the compression algorithms and file formats. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +typedef struct huft_s { + unsigned char e; /* number of extra bits or operation */ + unsigned char b; /* number of bits in this code or subcode */ + union { + unsigned short n; /* literal, length base, or distance base */ + struct huft_s *t; /* pointer to next level of table */ + } v; +} huft_t; + +static int gunzip_src_fd; +unsigned int gunzip_bytes_out; /* number of output bytes */ +static unsigned int gunzip_outbuf_count; /* bytes in output buffer */ + +/* gunzip_window size--must be a power of two, and + * at least 32K for zip's deflate method */ +enum { gunzip_wsize = 0x8000 }; +static unsigned char *gunzip_window; + +static uint32_t *gunzip_crc_table; +uint32_t gunzip_crc; + +/* If BMAX needs to be larger than 16, then h and x[] should be ulg. */ +#define BMAX 16 /* maximum bit length of any code (16 for explode) */ +#define N_MAX 288 /* maximum number of codes in any set */ + +/* bitbuffer */ +static unsigned int gunzip_bb; /* bit buffer */ +static unsigned char gunzip_bk; /* bits in bit buffer */ + +/* These control the size of the bytebuffer */ +static unsigned int bytebuffer_max = 0x8000; +static unsigned char *bytebuffer = NULL; +static unsigned int bytebuffer_offset = 0; +static unsigned int bytebuffer_size = 0; + +static const unsigned short mask_bits[] = { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + +/* Copy lengths for literal codes 257..285 */ +static const unsigned short cplens[] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +}; + +/* note: see note #13 above about the 258 in this list. */ +/* Extra bits for literal codes 257..285 */ +static const unsigned char cplext[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, + 5, 5, 5, 0, 99, 99 +}; /* 99==invalid */ + +/* Copy offsets for distance codes 0..29 */ +static const unsigned short cpdist[] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 +}; + +/* Extra bits for distance codes */ +static const unsigned char cpdext[] = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, + 11, 11, 12, 12, 13, 13 +}; + +/* Tables for deflate from PKZIP's appnote.txt. */ +/* Order of the bit length code lengths */ +static const unsigned char border[] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 +}; + +static unsigned int fill_bitbuffer(unsigned int bitbuffer, unsigned int *current, const unsigned int required) +{ + while (*current < required) { + if (bytebuffer_offset >= bytebuffer_size) { + /* Leave the first 4 bytes empty so we can always unwind the bitbuffer + * to the front of the bytebuffer, leave 4 bytes free at end of tail + * so we can easily top up buffer in check_trailer_gzip() */ + if (1 > (bytebuffer_size = safe_read(gunzip_src_fd, &bytebuffer[4], bytebuffer_max - 8))) + bb_error_msg_and_die("unexpected end of file"); + bytebuffer_size += 4; + bytebuffer_offset = 4; + } + bitbuffer |= ((unsigned int) bytebuffer[bytebuffer_offset]) << *current; + bytebuffer_offset++; + *current += 8; + } + return bitbuffer; +} + +/* + * Free the malloc'ed tables built by huft_build(), which makes a linked + * list of the tables it made, with the links in a dummy first entry of + * each table. + * t: table to free + */ +static int huft_free(huft_t * t) +{ + huft_t *p; + huft_t *q; + + /* Go through linked list, freeing from the malloced (t[-1]) address. */ + p = t; + while (p != (huft_t *) NULL) { + q = (--p)->v.t; + free((char *) p); + p = q; + } + return 0; +} + +/* Given a list of code lengths and a maximum table size, make a set of + * tables to decode that set of codes. Return zero on success, one if + * the given code set is incomplete (the tables are still built in this + * case), two if the input is invalid (all zero length codes or an + * oversubscribed set of lengths), and three if not enough memory. + * + * b: code lengths in bits (all assumed <= BMAX) + * n: number of codes (assumed <= N_MAX) + * s: number of simple-valued codes (0..s-1) + * d: list of base values for non-simple codes + * e: list of extra bits for non-simple codes + * t: result: starting table + * m: maximum lookup bits, returns actual + */ +static +int huft_build(unsigned int *b, const unsigned int n, + const unsigned int s, const unsigned short *d, + const unsigned char *e, huft_t ** t, unsigned int *m) +{ + unsigned a; /* counter for codes of length k */ + unsigned c[BMAX + 1]; /* bit length count table */ + unsigned eob_len; /* length of end-of-block code (value 256) */ + unsigned f; /* i repeats in table every f entries */ + int g; /* maximum code length */ + int htl; /* table level */ + unsigned i; /* counter, current code */ + unsigned j; /* counter */ + int k; /* number of bits in current code */ + unsigned *p; /* pointer into c[], b[], or v[] */ + huft_t *q; /* points to current table */ + huft_t r; /* table entry for structure assignment */ + huft_t *u[BMAX]; /* table stack */ + unsigned v[N_MAX]; /* values in order of bit length */ + int ws[BMAX+1]; /* bits decoded stack */ + int w; /* bits decoded */ + unsigned x[BMAX + 1]; /* bit offsets, then code stack */ + unsigned *xp; /* pointer into x */ + int y; /* number of dummy codes added */ + unsigned z; /* number of entries in current table */ + + /* Length of EOB code, if any */ + eob_len = n > 256 ? b[256] : BMAX; + + /* Generate counts for each bit length */ + memset((void *)c, 0, sizeof(c)); + p = b; + i = n; + do { + c[*p]++; /* assume all entries <= BMAX */ + p++; /* Can't combine with above line (Solaris bug) */ + } while (--i); + if (c[0] == n) { /* null input--all zero length codes */ + *t = (huft_t *) NULL; + *m = 0; + return 2; + } + + /* Find minimum and maximum length, bound *m by those */ + for (j = 1; (c[j] == 0) && (j <= BMAX); j++); + k = j; /* minimum code length */ + for (i = BMAX; (c[i] == 0) && i; i--); + g = i; /* maximum code length */ + *m = (*m < j) ? j : ((*m > i) ? i : *m); + + /* Adjust last length count to fill out codes, if needed */ + for (y = 1 << j; j < i; j++, y <<= 1) { + if ((y -= c[j]) < 0) { + return 2; /* bad input: more codes than bits */ + } + } + if ((y -= c[i]) < 0) { + return 2; + } + c[i] += y; + + /* Generate starting offsets into the value table for each length */ + x[1] = j = 0; + p = c + 1; + xp = x + 2; + while (--i) { /* note that i == g from above */ + *xp++ = (j += *p++); + } + + /* Make a table of values in order of bit lengths */ + p = b; + i = 0; + do { + if ((j = *p++) != 0) { + v[x[j]++] = i; + } + } while (++i < n); + + /* Generate the Huffman codes and for each, make the table entries */ + x[0] = i = 0; /* first Huffman code is zero */ + p = v; /* grab values in bit order */ + htl = -1; /* no tables yet--level -1 */ + w = ws[0] = 0; /* bits decoded */ + u[0] = (huft_t *) NULL; /* just to keep compilers happy */ + q = (huft_t *) NULL; /* ditto */ + z = 0; /* ditto */ + + /* go through the bit lengths (k already is bits in shortest code) */ + for (; k <= g; k++) { + a = c[k]; + while (a--) { + /* here i is the Huffman code of length k bits for value *p */ + /* make tables up to required level */ + while (k > ws[htl + 1]) { + w = ws[++htl]; + + /* compute minimum size table less than or equal to *m bits */ + z = (z = g - w) > *m ? *m : z; /* upper limit on table size */ + if ((f = 1 << (j = k - w)) > a + 1) { /* try a k-w bit table */ + /* too few codes for k-w bit table */ + f -= a + 1; /* deduct codes from patterns left */ + xp = c + k; + while (++j < z) { /* try smaller tables up to z bits */ + if ((f <<= 1) <= *++xp) { + break; /* enough codes to use up j bits */ + } + f -= *xp; /* else deduct codes from patterns */ + } + } + j = (w + j > eob_len && w < eob_len) ? eob_len - w : j; /* make EOB code end at table */ + z = 1 << j; /* table entries for j-bit table */ + ws[htl+1] = w + j; /* set bits decoded in stack */ + + /* allocate and link in new table */ + q = (huft_t *) xzalloc((z + 1) * sizeof(huft_t)); + *t = q + 1; /* link to list for huft_free() */ + t = &(q->v.t); + u[htl] = ++q; /* table starts after link */ + + /* connect to last table, if there is one */ + if (htl) { + x[htl] = i; /* save pattern for backing up */ + r.b = (unsigned char) (w - ws[htl - 1]); /* bits to dump before this table */ + r.e = (unsigned char) (16 + j); /* bits in this table */ + r.v.t = q; /* pointer to this table */ + j = (i & ((1 << w) - 1)) >> ws[htl - 1]; + u[htl - 1][j] = r; /* connect to last table */ + } + } + + /* set up table entry in r */ + r.b = (unsigned char) (k - w); + if (p >= v + n) { + r.e = 99; /* out of values--invalid code */ + } else if (*p < s) { + r.e = (unsigned char) (*p < 256 ? 16 : 15); /* 256 is EOB code */ + r.v.n = (unsigned short) (*p++); /* simple code is just the value */ + } else { + r.e = (unsigned char) e[*p - s]; /* non-simple--look up in lists */ + r.v.n = d[*p++ - s]; + } + + /* fill code-like entries with r */ + f = 1 << (k - w); + for (j = i >> w; j < z; j += f) { + q[j] = r; + } + + /* backwards increment the k-bit code i */ + for (j = 1 << (k - 1); i & j; j >>= 1) { + i ^= j; + } + i ^= j; + + /* backup over finished tables */ + while ((i & ((1 << w) - 1)) != x[htl]) { + w = ws[--htl]; + } + } + } + + /* return actual size of base table */ + *m = ws[1]; + + /* Return true (1) if we were given an incomplete table */ + return y != 0 && g != 1; +} + +/* + * inflate (decompress) the codes in a deflated (compressed) block. + * Return an error code or zero if it all goes ok. + * + * tl, td: literal/length and distance decoder tables + * bl, bd: number of bits decoded by tl[] and td[] + */ +static int inflate_codes(huft_t * my_tl, huft_t * my_td, const unsigned int my_bl, const unsigned int my_bd, int setup) +{ + static unsigned int e; /* table entry flag/number of extra bits */ + static unsigned int n, d; /* length and index for copy */ + static unsigned int w; /* current gunzip_window position */ + static huft_t *t; /* pointer to table entry */ + static unsigned int ml, md; /* masks for bl and bd bits */ + static unsigned int b; /* bit buffer */ + static unsigned int k; /* number of bits in bit buffer */ + static huft_t *tl, *td; + static unsigned int bl, bd; + static int resumeCopy = 0; + + if (setup) { // 1st time we are called, copy in variables + tl = my_tl; + td = my_td; + bl = my_bl; + bd = my_bd; + /* make local copies of globals */ + b = gunzip_bb; /* initialize bit buffer */ + k = gunzip_bk; + w = gunzip_outbuf_count; /* initialize gunzip_window position */ + + /* inflate the coded data */ + ml = mask_bits[bl]; /* precompute masks for speed */ + md = mask_bits[bd]; + return 0; // Don't actually do anything the first time + } + + if (resumeCopy) goto do_copy; + + while (1) { /* do until end of block */ + b = fill_bitbuffer(b, &k, bl); + if ((e = (t = tl + ((unsigned) b & ml))->e) > 16) + do { + if (e == 99) { + bb_error_msg_and_die("inflate_codes error 1"); + } + b >>= t->b; + k -= t->b; + e -= 16; + b = fill_bitbuffer(b, &k, e); + } while ((e = + (t = t->v.t + ((unsigned) b & mask_bits[e]))->e) > 16); + b >>= t->b; + k -= t->b; + if (e == 16) { /* then it's a literal */ + gunzip_window[w++] = (unsigned char) t->v.n; + if (w == gunzip_wsize) { + gunzip_outbuf_count = (w); + //flush_gunzip_window(); + w = 0; + return 1; // We have a block to read + } + } else { /* it's an EOB or a length */ + + /* exit if end of block */ + if (e == 15) { + break; + } + + /* get length of block to copy */ + b = fill_bitbuffer(b, &k, e); + n = t->v.n + ((unsigned) b & mask_bits[e]); + b >>= e; + k -= e; + + /* decode distance of block to copy */ + b = fill_bitbuffer(b, &k, bd); + if ((e = (t = td + ((unsigned) b & md))->e) > 16) + do { + if (e == 99) + bb_error_msg_and_die("inflate_codes error 2"); + b >>= t->b; + k -= t->b; + e -= 16; + b = fill_bitbuffer(b, &k, e); + } while ((e = + (t = + t->v.t + ((unsigned) b & mask_bits[e]))->e) > 16); + b >>= t->b; + k -= t->b; + b = fill_bitbuffer(b, &k, e); + d = w - t->v.n - ((unsigned) b & mask_bits[e]); + b >>= e; + k -= e; + + /* do the copy */ +do_copy: do { + n -= (e = + (e = + gunzip_wsize - ((d &= gunzip_wsize - 1) > w ? d : w)) > n ? n : e); + /* copy to new buffer to prevent possible overwrite */ + if (w - d >= e) { /* (this test assumes unsigned comparison) */ + memcpy(gunzip_window + w, gunzip_window + d, e); + w += e; + d += e; + } else { + /* do it slow to avoid memcpy() overlap */ + /* !NOMEMCPY */ + do { + gunzip_window[w++] = gunzip_window[d++]; + } while (--e); + } + if (w == gunzip_wsize) { + gunzip_outbuf_count = (w); + if (n) resumeCopy = 1; + else resumeCopy = 0; + //flush_gunzip_window(); + w = 0; + return 1; + } + } while (n); + resumeCopy = 0; + } + } + + /* restore the globals from the locals */ + gunzip_outbuf_count = w; /* restore global gunzip_window pointer */ + gunzip_bb = b; /* restore global bit buffer */ + gunzip_bk = k; + + /* normally just after call to inflate_codes, but save code by putting it here */ + /* free the decoding tables, return */ + huft_free(tl); + huft_free(td); + + /* done */ + return 0; +} + +static int inflate_stored(int my_n, int my_b_stored, int my_k_stored, int setup) +{ + static unsigned int n, b_stored, k_stored, w; + if (setup) { + n = my_n; + b_stored = my_b_stored; + k_stored = my_k_stored; + w = gunzip_outbuf_count; /* initialize gunzip_window position */ + return 0; // Don't do anything first time + } + + /* read and output the compressed data */ + while (n--) { + b_stored = fill_bitbuffer(b_stored, &k_stored, 8); + gunzip_window[w++] = (unsigned char) b_stored; + if (w == gunzip_wsize) { + gunzip_outbuf_count = (w); + //flush_gunzip_window(); + w = 0; + b_stored >>= 8; + k_stored -= 8; + return 1; // We have a block + } + b_stored >>= 8; + k_stored -= 8; + } + + /* restore the globals from the locals */ + gunzip_outbuf_count = w; /* restore global gunzip_window pointer */ + gunzip_bb = b_stored; /* restore global bit buffer */ + gunzip_bk = k_stored; + return 0; // Finished +} + +/* + * decompress an inflated block + * e: last block flag + * + * GLOBAL VARIABLES: bb, kk, + */ + // Return values: -1 = inflate_stored, -2 = inflate_codes +static int inflate_block(int *e) +{ + unsigned t; /* block type */ + unsigned int b; /* bit buffer */ + unsigned int k; /* number of bits in bit buffer */ + + /* make local bit buffer */ + + b = gunzip_bb; + k = gunzip_bk; + + /* read in last block bit */ + b = fill_bitbuffer(b, &k, 1); + *e = (int) b & 1; + b >>= 1; + k -= 1; + + /* read in block type */ + b = fill_bitbuffer(b, &k, 2); + t = (unsigned) b & 3; + b >>= 2; + k -= 2; + + /* restore the global bit buffer */ + gunzip_bb = b; + gunzip_bk = k; + + /* inflate that block type */ + switch (t) { + case 0: /* Inflate stored */ + { + unsigned int n; /* number of bytes in block */ + unsigned int b_stored; /* bit buffer */ + unsigned int k_stored; /* number of bits in bit buffer */ + + /* make local copies of globals */ + b_stored = gunzip_bb; /* initialize bit buffer */ + k_stored = gunzip_bk; + + /* go to byte boundary */ + n = k_stored & 7; + b_stored >>= n; + k_stored -= n; + + /* get the length and its complement */ + b_stored = fill_bitbuffer(b_stored, &k_stored, 16); + n = ((unsigned) b_stored & 0xffff); + b_stored >>= 16; + k_stored -= 16; + + b_stored = fill_bitbuffer(b_stored, &k_stored, 16); + if (n != (unsigned) ((~b_stored) & 0xffff)) { + return 1; /* error in compressed data */ + } + b_stored >>= 16; + k_stored -= 16; + + inflate_stored(n, b_stored, k_stored, 1); // Setup inflate_stored + return -1; + } + case 1: /* Inflate fixed + * decompress an inflated type 1 (fixed Huffman codes) block. We should + * either replace this with a custom decoder, or at least precompute the + * Huffman tables. + */ + { + int i; /* temporary variable */ + huft_t *tl; /* literal/length code table */ + huft_t *td; /* distance code table */ + unsigned int bl; /* lookup bits for tl */ + unsigned int bd; /* lookup bits for td */ + unsigned int l[288]; /* length list for huft_build */ + + /* set up literal table */ + for (i = 0; i < 144; i++) { + l[i] = 8; + } + for (; i < 256; i++) { + l[i] = 9; + } + for (; i < 280; i++) { + l[i] = 7; + } + for (; i < 288; i++) { /* make a complete, but wrong code set */ + l[i] = 8; + } + bl = 7; + if ((i = huft_build(l, 288, 257, cplens, cplext, &tl, &bl)) != 0) { + return i; + } + + /* set up distance table */ + for (i = 0; i < 30; i++) { /* make an incomplete code set */ + l[i] = 5; + } + bd = 5; + if ((i = huft_build(l, 30, 0, cpdist, cpdext, &td, &bd)) > 1) { + huft_free(tl); + return i; + } + + /* decompress until an end-of-block code */ + inflate_codes(tl, td, bl, bd, 1); // Setup inflate_codes + + /* huft_free code moved into inflate_codes */ + + return -2; + } + case 2: /* Inflate dynamic */ + { + const int dbits = 6; /* bits in base distance lookup table */ + const int lbits = 9; /* bits in base literal/length lookup table */ + + huft_t *tl; /* literal/length code table */ + huft_t *td; /* distance code table */ + unsigned int i; /* temporary variables */ + unsigned int j; + unsigned int l; /* last length */ + unsigned int m; /* mask for bit lengths table */ + unsigned int n; /* number of lengths to get */ + unsigned int bl; /* lookup bits for tl */ + unsigned int bd; /* lookup bits for td */ + unsigned int nb; /* number of bit length codes */ + unsigned int nl; /* number of literal/length codes */ + unsigned int nd; /* number of distance codes */ + + unsigned int ll[286 + 30]; /* literal/length and distance code lengths */ + unsigned int b_dynamic; /* bit buffer */ + unsigned int k_dynamic; /* number of bits in bit buffer */ + + /* make local bit buffer */ + b_dynamic = gunzip_bb; + k_dynamic = gunzip_bk; + + /* read in table lengths */ + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 5); + nl = 257 + ((unsigned int) b_dynamic & 0x1f); /* number of literal/length codes */ + + b_dynamic >>= 5; + k_dynamic -= 5; + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 5); + nd = 1 + ((unsigned int) b_dynamic & 0x1f); /* number of distance codes */ + + b_dynamic >>= 5; + k_dynamic -= 5; + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 4); + nb = 4 + ((unsigned int) b_dynamic & 0xf); /* number of bit length codes */ + + b_dynamic >>= 4; + k_dynamic -= 4; + if (nl > 286 || nd > 30) { + return 1; /* bad lengths */ + } + + /* read in bit-length-code lengths */ + for (j = 0; j < nb; j++) { + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 3); + ll[border[j]] = (unsigned int) b_dynamic & 7; + b_dynamic >>= 3; + k_dynamic -= 3; + } + for (; j < 19; j++) { + ll[border[j]] = 0; + } + + /* build decoding table for trees--single level, 7 bit lookup */ + bl = 7; + i = huft_build(ll, 19, 19, NULL, NULL, &tl, &bl); + if (i != 0) { + if (i == 1) { + huft_free(tl); + } + return i; /* incomplete code set */ + } + + /* read in literal and distance code lengths */ + n = nl + nd; + m = mask_bits[bl]; + i = l = 0; + while ((unsigned int) i < n) { + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, (unsigned int)bl); + j = (td = tl + ((unsigned int) b_dynamic & m))->b; + b_dynamic >>= j; + k_dynamic -= j; + j = td->v.n; + if (j < 16) { /* length of code in bits (0..15) */ + ll[i++] = l = j; /* save last length in l */ + } else if (j == 16) { /* repeat last length 3 to 6 times */ + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 2); + j = 3 + ((unsigned int) b_dynamic & 3); + b_dynamic >>= 2; + k_dynamic -= 2; + if ((unsigned int) i + j > n) { + return 1; + } + while (j--) { + ll[i++] = l; + } + } else if (j == 17) { /* 3 to 10 zero length codes */ + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 3); + j = 3 + ((unsigned int) b_dynamic & 7); + b_dynamic >>= 3; + k_dynamic -= 3; + if ((unsigned int) i + j > n) { + return 1; + } + while (j--) { + ll[i++] = 0; + } + l = 0; + } else { /* j == 18: 11 to 138 zero length codes */ + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 7); + j = 11 + ((unsigned int) b_dynamic & 0x7f); + b_dynamic >>= 7; + k_dynamic -= 7; + if ((unsigned int) i + j > n) { + return 1; + } + while (j--) { + ll[i++] = 0; + } + l = 0; + } + } + + /* free decoding table for trees */ + huft_free(tl); + + /* restore the global bit buffer */ + gunzip_bb = b_dynamic; + gunzip_bk = k_dynamic; + + /* build the decoding tables for literal/length and distance codes */ + bl = lbits; + + if ((i = huft_build(ll, nl, 257, cplens, cplext, &tl, &bl)) != 0) { + if (i == 1) { + bb_error_msg_and_die("incomplete literal tree"); + huft_free(tl); + } + return i; /* incomplete code set */ + } + + bd = dbits; + if ((i = huft_build(ll + nl, nd, 0, cpdist, cpdext, &td, &bd)) != 0) { + if (i == 1) { + bb_error_msg_and_die("incomplete distance tree"); + huft_free(td); + } + huft_free(tl); + return i; /* incomplete code set */ + } + + /* decompress until an end-of-block code */ + inflate_codes(tl, td, bl, bd, 1); // Setup inflate_codes + + /* huft_free code moved into inflate_codes */ + + return -2; + } + default: + /* bad block type */ + bb_error_msg_and_die("bad block type %d", t); + } +} + +static void calculate_gunzip_crc(void) +{ + int n; + for (n = 0; n < gunzip_outbuf_count; n++) { + gunzip_crc = gunzip_crc_table[((int) gunzip_crc ^ (gunzip_window[n])) & 0xff] ^ (gunzip_crc >> 8); + } + gunzip_bytes_out += gunzip_outbuf_count; +} + +static int inflate_get_next_window(void) +{ + static int method = -1; // Method == -1 for stored, -2 for codes + static int e = 0; + static int needAnotherBlock = 1; + + gunzip_outbuf_count = 0; + + while(1) { + int ret; + + if (needAnotherBlock) { + if(e) { + calculate_gunzip_crc(); + e = 0; + needAnotherBlock = 1; + return 0; + } // Last block + method = inflate_block(&e); + needAnotherBlock = 0; + } + + switch (method) { + case -1: ret = inflate_stored(0,0,0,0); + break; + case -2: ret = inflate_codes(0,0,0,0,0); + break; + default: bb_error_msg_and_die("inflate error %d", method); + } + + if (ret == 1) { + calculate_gunzip_crc(); + return 1; // More data left + } else needAnotherBlock = 1; // End of that block + } + /* Doesnt get here */ +} + +/* Initialise bytebuffer, be careful not to overfill the buffer */ +void inflate_init(unsigned int bufsize) +{ + /* Set the bytebuffer size, default is same as gunzip_wsize */ + bytebuffer_max = bufsize + 8; + bytebuffer_offset = 4; + bytebuffer_size = 0; +} + +void inflate_cleanup(void) +{ + free(bytebuffer); +} + +USE_DESKTOP(long long) int +inflate_unzip(int in, int out) +{ + USE_DESKTOP(long long total = 0;) + ssize_t nwrote; + typedef void (*sig_type) (int); + + /* Allocate all global buffers (for DYN_ALLOC option) */ + gunzip_window = xmalloc(gunzip_wsize); + gunzip_outbuf_count = 0; + gunzip_bytes_out = 0; + gunzip_src_fd = in; + + /* initialize gunzip_window, bit buffer */ + gunzip_bk = 0; + gunzip_bb = 0; + + /* Create the crc table */ + gunzip_crc_table = crc32_filltable(0); + gunzip_crc = ~0; + + /* Allocate space for buffer */ + bytebuffer = xmalloc(bytebuffer_max); + + while(1) { + int ret = inflate_get_next_window(); + nwrote = full_write(out, gunzip_window, gunzip_outbuf_count); + if (nwrote == -1) { + bb_perror_msg("write"); + return -1; + } + USE_DESKTOP(total += nwrote;) + if (ret == 0) break; + } + + /* Cleanup */ + free(gunzip_window); + free(gunzip_crc_table); + + /* Store unused bytes in a global buffer so calling applets can access it */ + if (gunzip_bk >= 8) { + /* Undo too much lookahead. The next read will be byte aligned + * so we can discard unused bits in the last meaningful byte. */ + bytebuffer_offset--; + bytebuffer[bytebuffer_offset] = gunzip_bb & 0xff; + gunzip_bb >>= 8; + gunzip_bk -= 8; + } + return USE_DESKTOP(total) + 0; +} + +USE_DESKTOP(long long) int +inflate_gunzip(int in, int out) +{ + uint32_t stored_crc = 0; + unsigned int count; + USE_DESKTOP(long long total = )inflate_unzip(in, out); + + USE_DESKTOP(if (total < 0) return total;) + + /* top up the input buffer with the rest of the trailer */ + count = bytebuffer_size - bytebuffer_offset; + if (count < 8) { + xread(in, &bytebuffer[bytebuffer_size], 8 - count); + bytebuffer_size += 8 - count; + } + for (count = 0; count != 4; count++) { + stored_crc |= (bytebuffer[bytebuffer_offset] << (count * 8)); + bytebuffer_offset++; + } + + /* Validate decompression - crc */ + if (stored_crc != (~gunzip_crc)) { + bb_error_msg("crc error"); + return -1; + } + + /* Validate decompression - size */ + if (gunzip_bytes_out != + (bytebuffer[bytebuffer_offset] | (bytebuffer[bytebuffer_offset+1] << 8) | + (bytebuffer[bytebuffer_offset+2] << 16) | (bytebuffer[bytebuffer_offset+3] << 24))) { + bb_error_msg("incorrect length"); + return -1; + } + + return USE_DESKTOP(total) + 0; +} diff --git a/archival/libunarchive/filter_accept_all.c b/archival/libunarchive/filter_accept_all.c new file mode 100644 index 000000000..5c991cac4 --- /dev/null +++ b/archival/libunarchive/filter_accept_all.c @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2002 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "unarchive.h" + +/* Accept any non-null name, its not really a filter at all */ +char filter_accept_all(archive_handle_t *archive_handle) +{ + if (archive_handle->file_header->name) + return EXIT_SUCCESS; + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/filter_accept_list.c b/archival/libunarchive/filter_accept_list.c new file mode 100644 index 000000000..cfc1b0c58 --- /dev/null +++ b/archival/libunarchive/filter_accept_list.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2002 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "unarchive.h" + +/* + * Accept names that are in the accept list, ignoring reject list. + */ +char filter_accept_list(archive_handle_t *archive_handle) +{ + if (find_list_entry(archive_handle->accept, archive_handle->file_header->name)) + return EXIT_SUCCESS; + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/filter_accept_list_reassign.c b/archival/libunarchive/filter_accept_list_reassign.c new file mode 100644 index 000000000..04b678007 --- /dev/null +++ b/archival/libunarchive/filter_accept_list_reassign.c @@ -0,0 +1,48 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2002 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include + +#include "libbb.h" +#include "unarchive.h" + +/* + * Reassign the subarchive metadata parser based on the filename extension + * e.g. if its a .tar.gz modify archive_handle->sub_archive to process a .tar.gz + * or if its a .tar.bz2 make archive_handle->sub_archive handle that + */ +char filter_accept_list_reassign(archive_handle_t *archive_handle) +{ + /* Check the file entry is in the accept list */ + if (find_list_entry(archive_handle->accept, archive_handle->file_header->name)) { + const char *name_ptr; + + /* Extract the last 2 extensions */ + name_ptr = strrchr(archive_handle->file_header->name, '.'); + + /* Modify the subarchive handler based on the extension */ +#ifdef CONFIG_FEATURE_DEB_TAR_GZ + if (strcmp(name_ptr, ".gz") == 0) { + archive_handle->action_data_subarchive = get_header_tar_gz; + return EXIT_SUCCESS; + } +#endif +#ifdef CONFIG_FEATURE_DEB_TAR_BZ2 + if (strcmp(name_ptr, ".bz2") == 0) { + archive_handle->action_data_subarchive = get_header_tar_bz2; + return EXIT_SUCCESS; + } +#endif + if (ENABLE_FEATURE_DEB_TAR_LZMA && !strcmp(name_ptr, ".lzma")) { + archive_handle->action_data_subarchive = get_header_tar_lzma; + return EXIT_SUCCESS; + } + } + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/filter_accept_reject_list.c b/archival/libunarchive/filter_accept_reject_list.c new file mode 100644 index 000000000..79da350b6 --- /dev/null +++ b/archival/libunarchive/filter_accept_reject_list.c @@ -0,0 +1,33 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2002 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "unarchive.h" + +/* + * Accept names that are in the accept list and not in the reject list + */ +char filter_accept_reject_list(archive_handle_t *archive_handle) +{ + const char *key = archive_handle->file_header->name; + const llist_t *reject_entry = find_list_entry2(archive_handle->reject, key); + const llist_t *accept_entry; + + /* If the key is in a reject list fail */ + if (reject_entry) { + return EXIT_FAILURE; + } + accept_entry = find_list_entry2(archive_handle->accept, key); + + /* Fail if an accept list was specified and the key wasnt in there */ + if ((accept_entry == NULL) && archive_handle->accept) { + return EXIT_FAILURE; + } + + /* Accepted */ + return EXIT_SUCCESS; +} diff --git a/archival/libunarchive/find_list_entry.c b/archival/libunarchive/find_list_entry.c new file mode 100644 index 000000000..d1afc72ce --- /dev/null +++ b/archival/libunarchive/find_list_entry.c @@ -0,0 +1,54 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2002 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "unarchive.h" + +/* Find a string in a shell pattern list */ +const llist_t *find_list_entry(const llist_t *list, const char *filename) +{ + while (list) { + if (fnmatch(list->data, filename, 0) == 0) { + return list; + } + list = list->link; + } + return NULL; +} + +/* Same, but compares only path components present in pattern + * (extra trailing path components in filename are assumed to match) + */ +const llist_t *find_list_entry2(const llist_t *list, const char *filename) +{ + char buf[PATH_MAX]; + int pattern_slash_cnt; + const char *c; + char *d; + + while (list) { + c = list->data; + pattern_slash_cnt = 0; + while (*c) + if (*c++ == '/') pattern_slash_cnt++; + c = filename; + d = buf; + /* paranoia is better that buffer overflows */ + while (*c && d != buf + sizeof(buf)-1) { + if (*c == '/' && --pattern_slash_cnt < 0) + break; + *d++ = *c++; + } + *d = '\0'; + if (fnmatch(list->data, buf, 0) == 0) { + return list; + } + list = list->link; + } + return NULL; +} diff --git a/archival/libunarchive/get_header_ar.c b/archival/libunarchive/get_header_ar.c new file mode 100644 index 000000000..7f8c81ca0 --- /dev/null +++ b/archival/libunarchive/get_header_ar.c @@ -0,0 +1,112 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright 2001 Glenn McGrath. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +char get_header_ar(archive_handle_t *archive_handle) +{ + file_header_t *typed = archive_handle->file_header; + union { + char raw[60]; + struct { + char name[16]; + char date[12]; + char uid[6]; + char gid[6]; + char mode[8]; + char size[10]; + char magic[2]; + } formatted; + } ar; +#ifdef CONFIG_FEATURE_AR_LONG_FILENAMES + static char *ar_long_names; + static unsigned int ar_long_name_size; +#endif + + /* dont use xread as we want to handle the error ourself */ + if (read(archive_handle->src_fd, ar.raw, 60) != 60) { + /* End Of File */ + return EXIT_FAILURE; + } + + /* ar header starts on an even byte (2 byte aligned) + * '\n' is used for padding + */ + if (ar.raw[0] == '\n') { + /* fix up the header, we started reading 1 byte too early */ + memmove(ar.raw, &ar.raw[1], 59); + ar.raw[59] = xread_char(archive_handle->src_fd); + archive_handle->offset++; + } + archive_handle->offset += 60; + + /* align the headers based on the header magic */ + if ((ar.formatted.magic[0] != '`') || (ar.formatted.magic[1] != '\n')) { + bb_error_msg_and_die("invalid ar header"); + } + + typed->mode = xstrtoul(ar.formatted.mode, 8); + typed->mtime = xatou(ar.formatted.date); + typed->uid = xatou(ar.formatted.uid); + typed->gid = xatou(ar.formatted.gid); + typed->size = xatoul(ar.formatted.size); + + /* long filenames have '/' as the first character */ + if (ar.formatted.name[0] == '/') { +#ifdef CONFIG_FEATURE_AR_LONG_FILENAMES + if (ar.formatted.name[1] == '/') { + /* If the second char is a '/' then this entries data section + * stores long filename for multiple entries, they are stored + * in static variable long_names for use in future entries */ + ar_long_name_size = typed->size; + ar_long_names = xmalloc(ar_long_name_size); + xread(archive_handle->src_fd, ar_long_names, ar_long_name_size); + archive_handle->offset += ar_long_name_size; + /* This ar entries data section only contained filenames for other records + * they are stored in the static ar_long_names for future reference */ + return get_header_ar(archive_handle); /* Return next header */ + } else if (ar.formatted.name[1] == ' ') { + /* This is the index of symbols in the file for compilers */ + data_skip(archive_handle); + archive_handle->offset += typed->size; + return get_header_ar(archive_handle); /* Return next header */ + } else { + /* The number after the '/' indicates the offset in the ar data section + (saved in variable long_name) that conatains the real filename */ + const unsigned int long_offset = atoi(&ar.formatted.name[1]); + if (long_offset >= ar_long_name_size) { + bb_error_msg_and_die("can't resolve long filename"); + } + typed->name = xstrdup(ar_long_names + long_offset); + } +#else + bb_error_msg_and_die("long filenames not supported"); +#endif + } else { + /* short filenames */ + typed->name = xstrndup(ar.formatted.name, 16); + } + + typed->name[strcspn(typed->name, " /")] = '\0'; + + if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) { + archive_handle->action_header(typed); + if (archive_handle->sub_archive) { + while (archive_handle->action_data_subarchive(archive_handle->sub_archive) == EXIT_SUCCESS); + } else { + archive_handle->action_data(archive_handle); + } + } else { + data_skip(archive_handle); + } + + archive_handle->offset += typed->size; + /* Set the file pointer to the correct spot, we may have been reading a compressed file */ + lseek(archive_handle->src_fd, archive_handle->offset, SEEK_SET); + + return EXIT_SUCCESS; +} diff --git a/archival/libunarchive/get_header_cpio.c b/archival/libunarchive/get_header_cpio.c new file mode 100644 index 000000000..e0272003c --- /dev/null +++ b/archival/libunarchive/get_header_cpio.c @@ -0,0 +1,160 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright 2002 Laurence Anderson + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +typedef struct hardlinks_s { + char *name; + int inode; + struct hardlinks_s *next; +} hardlinks_t; + +char get_header_cpio(archive_handle_t *archive_handle) +{ + static hardlinks_t *saved_hardlinks = NULL; + static unsigned short pending_hardlinks = 0; + static int inode; + file_header_t *file_header = archive_handle->file_header; + char cpio_header[110]; + int namesize; + char dummy[16]; + int major, minor, nlink; + + if (pending_hardlinks) { /* Deal with any pending hardlinks */ + hardlinks_t *tmp, *oldtmp; + + tmp = saved_hardlinks; + oldtmp = NULL; + + file_header->link_name = file_header->name; + file_header->size = 0; + + while (tmp) { + if (tmp->inode != inode) { + tmp = tmp->next; + continue; + } + + file_header->name = tmp->name; + + if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) { + archive_handle->action_data(archive_handle); + archive_handle->action_header(archive_handle->file_header); + } + + pending_hardlinks--; + + oldtmp = tmp; + tmp = tmp->next; + free(oldtmp->name); + free(oldtmp); + if (oldtmp == saved_hardlinks) + saved_hardlinks = tmp; + } + + file_header->name = file_header->link_name; + + if (pending_hardlinks > 1) { + bb_error_msg("error resolving hardlink: archive made by GNU cpio 2.0-2.2?"); + } + + /* No more pending hardlinks, read next file entry */ + pending_hardlinks = 0; + } + + /* There can be padding before archive header */ + data_align(archive_handle, 4); + + if (archive_xread_all_eof(archive_handle, (unsigned char*)cpio_header, 110) == 0) { + return EXIT_FAILURE; + } + archive_handle->offset += 110; + + if (strncmp(&cpio_header[0], "07070", 5) != 0 + || (cpio_header[5] != '1' && cpio_header[5] != '2') + ) { + bb_error_msg_and_die("unsupported cpio format, use newc or crc"); + } + + { + unsigned long tmpsize; + sscanf(cpio_header, "%6c%8x%8x%8x%8x%8x%8lx%8lx%16c%8x%8x%8x%8c", + dummy, &inode, (unsigned int*)&file_header->mode, + (unsigned int*)&file_header->uid, (unsigned int*)&file_header->gid, + &nlink, &file_header->mtime, &tmpsize, + dummy, &major, &minor, &namesize, dummy); + file_header->size = tmpsize; + } + + free(file_header->name); + file_header->name = xzalloc(namesize + 1); + /* Read in filename */ + xread(archive_handle->src_fd, file_header->name, namesize); + archive_handle->offset += namesize; + + /* Update offset amount and skip padding before file contents */ + data_align(archive_handle, 4); + + if (strcmp(file_header->name, "TRAILER!!!") == 0) { + /* Always round up */ + printf("%d blocks\n", (int) (archive_handle->offset % 512 ? + archive_handle->offset / 512 + 1 : + archive_handle->offset / 512 + )); + if (saved_hardlinks) { /* Bummer - we still have unresolved hardlinks */ + hardlinks_t *tmp = saved_hardlinks; + hardlinks_t *oldtmp = NULL; + while (tmp) { + bb_error_msg("%s not created: cannot resolve hardlink", tmp->name); + oldtmp = tmp; + tmp = tmp->next; + free(oldtmp->name); + free(oldtmp); + } + saved_hardlinks = NULL; + pending_hardlinks = 0; + } + return EXIT_FAILURE; + } + + if (S_ISLNK(file_header->mode)) { + file_header->link_name = xzalloc(file_header->size + 1); + xread(archive_handle->src_fd, file_header->link_name, file_header->size); + archive_handle->offset += file_header->size; + file_header->size = 0; /* Stop possible seeks in future */ + } else { + file_header->link_name = NULL; + } + if (nlink > 1 && !S_ISDIR(file_header->mode)) { + if (file_header->size == 0) { /* Put file on a linked list for later */ + hardlinks_t *new = xmalloc(sizeof(hardlinks_t)); + new->next = saved_hardlinks; + new->inode = inode; + /* name current allocated, freed later */ + new->name = file_header->name; + file_header->name = NULL; + saved_hardlinks = new; + return EXIT_SUCCESS; /* Skip this one */ + } + /* Found the file with data in */ + pending_hardlinks = nlink; + } + file_header->device = makedev(major, minor); + + if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) { + archive_handle->action_data(archive_handle); + archive_handle->action_header(archive_handle->file_header); + } else { + data_skip(archive_handle); + } + + archive_handle->offset += file_header->size; + + free(file_header->link_name); + + return EXIT_SUCCESS; +} diff --git a/archival/libunarchive/get_header_tar.c b/archival/libunarchive/get_header_tar.c new file mode 100644 index 000000000..66c3314a1 --- /dev/null +++ b/archival/libunarchive/get_header_tar.c @@ -0,0 +1,275 @@ +/* vi: set sw=4 ts=4: */ +/* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * FIXME: + * In privileged mode if uname and gname map to a uid and gid then use the + * mapped value instead of the uid/gid values in tar header + * + * References: + * GNU tar and star man pages, + * Opengroup's ustar interchange format, + * http://www.opengroup.org/onlinepubs/007904975/utilities/pax.html + */ + +#include "libbb.h" +#include "unarchive.h" + +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS +static char *longname; +static char *linkname; +#else +enum { + longname = 0, + linkname = 0, +}; +#endif + +/* NB: _DESTROYS_ str[len] character! */ +static unsigned long long getOctal(char *str, int len) +{ + unsigned long long v; + /* Actually, tar header allows leading spaces also. + * Oh well, we will be liberal and skip this... + * The only downside probably is that we allow "-123" too :) + if (*str < '0' || *str > '7') + bb_error_msg_and_die("corrupted octal value in tar header"); + */ + str[len] = '\0'; + v = strtoull(str, &str, 8); + if (*str) + bb_error_msg_and_die("corrupted octal value in tar header"); + return v; +} +#define GET_OCTAL(a) getOctal((a), sizeof(a)) + +void BUG_tar_header_size(void); +char get_header_tar(archive_handle_t *archive_handle) +{ + static int end; + + file_header_t *file_header = archive_handle->file_header; + struct { + /* ustar header, Posix 1003.1 */ + char name[100]; /* 0-99 */ + char mode[8]; /* 100-107 */ + char uid[8]; /* 108-115 */ + char gid[8]; /* 116-123 */ + char size[12]; /* 124-135 */ + char mtime[12]; /* 136-147 */ + char chksum[8]; /* 148-155 */ + char typeflag; /* 156-156 */ + char linkname[100]; /* 157-256 */ + char magic[6]; /* 257-262 */ + char version[2]; /* 263-264 */ + char uname[32]; /* 265-296 */ + char gname[32]; /* 297-328 */ + char devmajor[8]; /* 329-336 */ + char devminor[8]; /* 337-344 */ + char prefix[155]; /* 345-499 */ + char padding[12]; /* 500-512 */ + } tar; + char *cp; + int sum, i; + int parse_names; + + if (sizeof(tar) != 512) + BUG_tar_header_size(); + again: + + /* Align header */ + data_align(archive_handle, 512); + + again_after_align: + + xread(archive_handle->src_fd, &tar, 512); + archive_handle->offset += 512; + + /* If there is no filename its an empty header */ + if (tar.name[0] == 0) { + if (end) { + /* This is the second consecutive empty header! End of archive! + * Read until the end to empty the pipe from gz or bz2 + */ + while (full_read(archive_handle->src_fd, &tar, 512) == 512) + /* repeat */; + return EXIT_FAILURE; + } + end = 1; + return EXIT_SUCCESS; + } + end = 0; + + /* Check header has valid magic, "ustar" is for the proper tar + * 0's are for the old tar format + */ + if (strncmp(tar.magic, "ustar", 5) != 0) { +#if ENABLE_FEATURE_TAR_OLDGNU_COMPATIBILITY + if (memcmp(tar.magic, "\0\0\0\0", 5) != 0) +#endif + bb_error_msg_and_die("invalid tar magic"); + } + /* Do checksum on headers */ + sum = ' ' * sizeof(tar.chksum); + for (i = 0; i < 148 ; i++) { + sum += ((char*)&tar)[i]; + } + for (i = 156; i < 512 ; i++) { + sum += ((char*)&tar)[i]; + } + /* This field does not need special treatment (getOctal) */ + if (sum != xstrtoul(tar.chksum, 8)) { + bb_error_msg_and_die("invalid tar header checksum"); + } + + /* 0 is reserved for high perf file, treat as normal file */ + if (!tar.typeflag) tar.typeflag = '0'; + parse_names = (tar.typeflag >= '0' && tar.typeflag <= '7'); + + /* getOctal trashes subsequent field, therefore we call it + * on fields in reverse order */ + if (tar.devmajor[0]) { + unsigned minor = GET_OCTAL(tar.devminor); + unsigned major = GET_OCTAL(tar.devmajor); + file_header->device = makedev(major, minor); + } + file_header->link_name = NULL; + if (!linkname && parse_names && tar.linkname[0]) { + /* we trash magic[0] here, it's ok */ + tar.linkname[sizeof(tar.linkname)] = '\0'; + file_header->link_name = xstrdup(tar.linkname); + /* FIXME: what if we have non-link object with link_name? */ + /* Will link_name be free()ed? */ + } + file_header->mtime = GET_OCTAL(tar.mtime); + file_header->size = GET_OCTAL(tar.size); + file_header->gid = GET_OCTAL(tar.gid); + file_header->uid = GET_OCTAL(tar.uid); + /* Set bits 0-11 of the files mode */ + file_header->mode = 07777 & GET_OCTAL(tar.mode); + + file_header->name = NULL; + if (!longname && parse_names) { + /* we trash mode[0] here, it's ok */ + tar.name[sizeof(tar.name)] = '\0'; + if (tar.prefix[0]) { + /* and padding[0] */ + tar.prefix[sizeof(tar.prefix)] = '\0'; + file_header->name = concat_path_file(tar.prefix, tar.name); + } else + file_header->name = xstrdup(tar.name); + } + + /* Set bits 12-15 of the files mode */ + /* (typeflag was not trashed because chksum does not use getOctal) */ + switch (tar.typeflag) { + /* busybox identifies hard links as being regular files with 0 size and a link name */ + case '1': + file_header->mode |= S_IFREG; + break; + case '7': + /* case 0: */ + case '0': +#if ENABLE_FEATURE_TAR_OLDGNU_COMPATIBILITY + if (last_char_is(file_header->name, '/')) { + file_header->mode |= S_IFDIR; + } else +#endif + file_header->mode |= S_IFREG; + break; + case '2': + file_header->mode |= S_IFLNK; + break; + case '3': + file_header->mode |= S_IFCHR; + break; + case '4': + file_header->mode |= S_IFBLK; + break; + case '5': + file_header->mode |= S_IFDIR; + break; + case '6': + file_header->mode |= S_IFIFO; + break; +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS + case 'L': + /* free: paranoia: tar with several consecutive longnames */ + free(longname); + /* For paranoia reasons we allocate extra NUL char */ + longname = xzalloc(file_header->size + 1); + /* We read ASCIZ string, including NUL */ + xread(archive_handle->src_fd, longname, file_header->size); + archive_handle->offset += file_header->size; + /* return get_header_tar(archive_handle); */ + /* gcc 4.1.1 didn't optimize it into jump */ + /* so we will do it ourself, this also saves stack */ + goto again; + case 'K': + free(linkname); + linkname = xzalloc(file_header->size + 1); + xread(archive_handle->src_fd, linkname, file_header->size); + archive_handle->offset += file_header->size; + /* return get_header_tar(archive_handle); */ + goto again; + case 'D': /* GNU dump dir */ + case 'M': /* Continuation of multi volume archive */ + case 'N': /* Old GNU for names > 100 characters */ + case 'S': /* Sparse file */ + case 'V': /* Volume header */ +#endif + case 'g': /* pax global header */ + case 'x': { /* pax extended header */ + off_t sz; + bb_error_msg("warning: skipping header '%c'", tar.typeflag); + sz = (file_header->size + 511) & ~(off_t)511; + archive_handle->offset += sz; + sz >>= 9; /* sz /= 512 but w/o contortions for signed div */ + while (sz--) + xread(archive_handle->src_fd, &tar, 512); + /* return get_header_tar(archive_handle); */ + goto again_after_align; + } + default: + bb_error_msg_and_die("unknown typeflag: 0x%x", tar.typeflag); + } + +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS + if (longname) { + file_header->name = longname; + longname = NULL; + } + if (linkname) { + file_header->link_name = linkname; + linkname = NULL; + } +#endif + if (!strncmp(file_header->name, "/../"+1, 3) + || strstr(file_header->name, "/../") + ) { + bb_error_msg_and_die("name with '..' encountered: '%s'", + file_header->name); + } + + /* Strip trailing '/' in directories */ + /* Must be done after mode is set as '/' is used to check if its a directory */ + cp = last_char_is(file_header->name, '/'); + + if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) { + archive_handle->action_header(archive_handle->file_header); + /* Note that we kill the '/' only after action_header() */ + /* (like GNU tar 1.15.1: verbose mode outputs "dir/dir/") */ + if (cp) *cp = '\0'; + archive_handle->flags |= ARCHIVE_EXTRACT_QUIET; + archive_handle->action_data(archive_handle); + llist_add_to(&(archive_handle->passed), file_header->name); + } else { + data_skip(archive_handle); + free(file_header->name); + } + archive_handle->offset += file_header->size; + + free(file_header->link_name); + /* Do not free(file_header->name)! */ + + return EXIT_SUCCESS; +} diff --git a/archival/libunarchive/get_header_tar_bz2.c b/archival/libunarchive/get_header_tar_bz2.c new file mode 100644 index 000000000..e7a80fc41 --- /dev/null +++ b/archival/libunarchive/get_header_tar_bz2.c @@ -0,0 +1,27 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "libbb.h" +#include "unarchive.h" + +char get_header_tar_bz2(archive_handle_t *archive_handle) +{ + /* Can't lseek over pipes */ + archive_handle->seek = seek_by_read; + + archive_handle->src_fd = open_transformer(archive_handle->src_fd, uncompressStream); + archive_handle->offset = 0; + while (get_header_tar(archive_handle) == EXIT_SUCCESS) /**/; + + /* Can only do one file at a time */ + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/get_header_tar_gz.c b/archival/libunarchive/get_header_tar_gz.c new file mode 100644 index 000000000..41c02e16a --- /dev/null +++ b/archival/libunarchive/get_header_tar_gz.c @@ -0,0 +1,31 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include + +#include "libbb.h" +#include "unarchive.h" + +char get_header_tar_gz(archive_handle_t *archive_handle) +{ + unsigned char magic[2]; + + /* Can't lseek over pipes */ + archive_handle->seek = seek_by_read; + + xread(archive_handle->src_fd, &magic, 2); + if ((magic[0] != 0x1f) || (magic[1] != 0x8b)) { + bb_error_msg_and_die("invalid gzip magic"); + } + + check_header_gzip(archive_handle->src_fd); + + archive_handle->src_fd = open_transformer(archive_handle->src_fd, inflate_gunzip); + archive_handle->offset = 0; + while (get_header_tar(archive_handle) == EXIT_SUCCESS) /**/; + + /* Can only do one file at a time */ + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/get_header_tar_lzma.c b/archival/libunarchive/get_header_tar_lzma.c new file mode 100644 index 000000000..06b8daa0f --- /dev/null +++ b/archival/libunarchive/get_header_tar_lzma.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Small lzma deflate implementation. + * Copyright (C) 2006 Aurelien Jacobs + * + * Licensed under GPL v2, see file LICENSE in this tarball for details. + */ + +#include "unarchive.h" + +char get_header_tar_lzma(archive_handle_t * archive_handle) +{ + /* Can't lseek over pipes */ + archive_handle->seek = seek_by_read; + + archive_handle->src_fd = open_transformer(archive_handle->src_fd, unlzma); + archive_handle->offset = 0; + while (get_header_tar(archive_handle) == EXIT_SUCCESS) /**/; + + /* Can only do one file at a time */ + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/header_list.c b/archival/libunarchive/header_list.c new file mode 100644 index 000000000..fb461a68e --- /dev/null +++ b/archival/libunarchive/header_list.c @@ -0,0 +1,11 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include +#include "unarchive.h" + +void header_list(const file_header_t *file_header) +{ + puts(file_header->name); +} diff --git a/archival/libunarchive/header_skip.c b/archival/libunarchive/header_skip.c new file mode 100644 index 000000000..53242e011 --- /dev/null +++ b/archival/libunarchive/header_skip.c @@ -0,0 +1,10 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include +#include "unarchive.h" + +void header_skip(const file_header_t *file_header ATTRIBUTE_UNUSED) +{ +} diff --git a/archival/libunarchive/header_verbose_list.c b/archival/libunarchive/header_verbose_list.c new file mode 100644 index 000000000..7b97e524c --- /dev/null +++ b/archival/libunarchive/header_verbose_list.c @@ -0,0 +1,31 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +void header_verbose_list(const file_header_t *file_header) +{ + struct tm *mtime = localtime(&(file_header->mtime)); + + printf("%s %d/%d %9"OFF_FMT"u %4u-%02u-%02u %02u:%02u:%02u %s", + bb_mode_string(file_header->mode), + file_header->uid, + file_header->gid, + file_header->size, + 1900 + mtime->tm_year, + 1 + mtime->tm_mon, + mtime->tm_mday, + mtime->tm_hour, + mtime->tm_min, + mtime->tm_sec, + file_header->name); + + if (file_header->link_name) { + printf(" -> %s", file_header->link_name); + } + /* putchar isnt used anywhere else i dont think */ + puts(""); +} diff --git a/archival/libunarchive/init_handle.c b/archival/libunarchive/init_handle.c new file mode 100644 index 000000000..309d329ea --- /dev/null +++ b/archival/libunarchive/init_handle.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +archive_handle_t *init_handle(void) +{ + archive_handle_t *archive_handle; + + /* Initialize default values */ + archive_handle = xzalloc(sizeof(archive_handle_t)); + archive_handle->file_header = xzalloc(sizeof(file_header_t)); + archive_handle->action_header = header_skip; + archive_handle->action_data = data_skip; + archive_handle->filter = filter_accept_all; + archive_handle->seek = seek_by_jump; + + return archive_handle; +} diff --git a/archival/libunarchive/open_transformer.c b/archival/libunarchive/open_transformer.c new file mode 100644 index 000000000..456d3e986 --- /dev/null +++ b/archival/libunarchive/open_transformer.c @@ -0,0 +1,44 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include + +#include "libbb.h" + +#include "unarchive.h" + +/* transformer(), more than meets the eye */ +int open_transformer(int src_fd, + USE_DESKTOP(long long) int (*transformer)(int src_fd, int dst_fd)) +{ + int fd_pipe[2]; + int pid; + + if (pipe(fd_pipe) != 0) { + bb_perror_msg_and_die("can't create pipe"); + } + + pid = fork(); + if (pid == -1) { + bb_perror_msg_and_die("fork failed"); + } + + if (pid == 0) { + /* child process */ + close(fd_pipe[0]); /* We don't wan't to read from the parent */ + // FIXME: error check? + transformer(src_fd, fd_pipe[1]); + close(fd_pipe[1]); /* Send EOF */ + close(src_fd); + exit(0); + /* notreached */ + } + + /* parent process */ + close(fd_pipe[1]); /* Don't want to write to the child */ + + return fd_pipe[0]; +} diff --git a/archival/libunarchive/seek_by_jump.c b/archival/libunarchive/seek_by_jump.c new file mode 100644 index 000000000..d605f61dd --- /dev/null +++ b/archival/libunarchive/seek_by_jump.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include + +#include "libbb.h" +#include "unarchive.h" + +void seek_by_jump(const archive_handle_t *archive_handle, const unsigned int amount) +{ + if (lseek(archive_handle->src_fd, (off_t) amount, SEEK_CUR) == (off_t) -1) { +#ifdef CONFIG_FEATURE_UNARCHIVE_TAPE + if (errno == ESPIPE) { + seek_by_read(archive_handle, amount); + } else +#endif + bb_perror_msg_and_die("seek failure"); + } +} diff --git a/archival/libunarchive/seek_by_read.c b/archival/libunarchive/seek_by_read.c new file mode 100644 index 000000000..d56f94b21 --- /dev/null +++ b/archival/libunarchive/seek_by_read.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include + +#include "unarchive.h" +#include "libbb.h" + +/* If we are reading through a pipe(), or from stdin then we can't lseek, + * we must read and discard the data to skip over it. + */ +void seek_by_read(const archive_handle_t *archive_handle, const unsigned int jump_size) +{ + if (jump_size) { + bb_copyfd_size(archive_handle->src_fd, -1, jump_size); + } +} diff --git a/archival/libunarchive/unpack_ar_archive.c b/archival/libunarchive/unpack_ar_archive.c new file mode 100644 index 000000000..6a84ae811 --- /dev/null +++ b/archival/libunarchive/unpack_ar_archive.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include +#include +#include +#include "unarchive.h" +#include "libbb.h" + +void unpack_ar_archive(archive_handle_t *ar_archive) +{ + char magic[7]; + + xread(ar_archive->src_fd, magic, 7); + if (strncmp(magic, "!", 7) != 0) { + bb_error_msg_and_die("invalid ar magic"); + } + ar_archive->offset += 7; + + while (get_header_ar(ar_archive) == EXIT_SUCCESS); +} diff --git a/archival/rpm.c b/archival/rpm.c new file mode 100644 index 000000000..2ef7232f4 --- /dev/null +++ b/archival/rpm.c @@ -0,0 +1,319 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini rpm applet for busybox + * + * Copyright (C) 2001,2002 by Laurence Anderson + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "unarchive.h" + +#define RPM_HEADER_MAGIC "\216\255\350" +#define RPM_CHAR_TYPE 1 +#define RPM_INT8_TYPE 2 +#define RPM_INT16_TYPE 3 +#define RPM_INT32_TYPE 4 +/* #define RPM_INT64_TYPE 5 ---- These aren't supported (yet) */ +#define RPM_STRING_TYPE 6 +#define RPM_BIN_TYPE 7 +#define RPM_STRING_ARRAY_TYPE 8 +#define RPM_I18NSTRING_TYPE 9 + +#define RPMTAG_NAME 1000 +#define RPMTAG_VERSION 1001 +#define RPMTAG_RELEASE 1002 +#define RPMTAG_SUMMARY 1004 +#define RPMTAG_DESCRIPTION 1005 +#define RPMTAG_BUILDTIME 1006 +#define RPMTAG_BUILDHOST 1007 +#define RPMTAG_SIZE 1009 +#define RPMTAG_VENDOR 1011 +#define RPMTAG_LICENSE 1014 +#define RPMTAG_PACKAGER 1015 +#define RPMTAG_GROUP 1016 +#define RPMTAG_URL 1020 +#define RPMTAG_PREIN 1023 +#define RPMTAG_POSTIN 1024 +#define RPMTAG_FILEFLAGS 1037 +#define RPMTAG_FILEUSERNAME 1039 +#define RPMTAG_FILEGROUPNAME 1040 +#define RPMTAG_SOURCERPM 1044 +#define RPMTAG_PREINPROG 1085 +#define RPMTAG_POSTINPROG 1086 +#define RPMTAG_PREFIXS 1098 +#define RPMTAG_DIRINDEXES 1116 +#define RPMTAG_BASENAMES 1117 +#define RPMTAG_DIRNAMES 1118 +#define RPMFILE_CONFIG (1 << 0) +#define RPMFILE_DOC (1 << 1) + +enum rpm_functions_e { + rpm_query = 1, + rpm_install = 2, + rpm_query_info = 4, + rpm_query_package = 8, + rpm_query_list = 16, + rpm_query_list_doc = 32, + rpm_query_list_config = 64 +}; + +typedef struct { + uint32_t tag; /* 4 byte tag */ + uint32_t type; /* 4 byte type */ + uint32_t offset; /* 4 byte offset */ + uint32_t count; /* 4 byte count */ +} rpm_index; + +static void *map; +static rpm_index **mytags; +static int tagcount; + +static void extract_cpio_gz(int fd); +static rpm_index **rpm_gettags(int fd, int *num_tags); +static int bsearch_rpmtag(const void *key, const void *item); +static char *rpm_getstring(int tag, int itemindex); +static int rpm_getint(int tag, int itemindex); +static int rpm_getcount(int tag); +static void fileaction_dobackup(char *filename, int fileref); +static void fileaction_setowngrp(char *filename, int fileref); +static void loop_through_files(int filetag, void (*fileaction)(char *filename, int fileref)); + +int rpm_main(int argc, char **argv) +{ + int opt = 0, func = 0, rpm_fd, offset; + + while ((opt = getopt(argc, argv, "iqpldc")) != -1) { + switch (opt) { + case 'i': // First arg: Install mode, with q: Information + if (!func) func |= rpm_install; + else func |= rpm_query_info; + break; + case 'q': // First arg: Query mode + if (!func) func |= rpm_query; + else bb_show_usage(); + break; + case 'p': // Query a package + func |= rpm_query_package; + break; + case 'l': // List files in a package + func |= rpm_query_list; + break; + case 'd': // List doc files in a package (implies list) + func |= rpm_query_list; + func |= rpm_query_list_doc; + break; + case 'c': // List config files in a package (implies list) + func |= rpm_query_list; + func |= rpm_query_list_config; + break; + default: + bb_show_usage(); + } + } + + if (optind == argc) bb_show_usage(); + while (optind < argc) { + rpm_fd = xopen(argv[optind], O_RDONLY); + mytags = rpm_gettags(rpm_fd, (int *) &tagcount); + offset = lseek(rpm_fd, 0, SEEK_CUR); + if (!mytags) { printf("Error reading rpm header\n"); exit(-1); } + map = mmap(0, offset > getpagesize() ? (offset + offset % getpagesize()) : getpagesize(), PROT_READ, MAP_PRIVATE, rpm_fd, 0); // Mimimum is one page + if (func & rpm_install) { + loop_through_files(RPMTAG_BASENAMES, fileaction_dobackup); /* Backup any config files */ + extract_cpio_gz(rpm_fd); // Extact the archive + loop_through_files(RPMTAG_BASENAMES, fileaction_setowngrp); /* Set the correct file uid/gid's */ + } else if (func & rpm_query && func & rpm_query_package) { + if (!((func & rpm_query_info) || (func & rpm_query_list))) { // If just a straight query, just give package name + printf("%s-%s-%s\n", rpm_getstring(RPMTAG_NAME, 0), rpm_getstring(RPMTAG_VERSION, 0), rpm_getstring(RPMTAG_RELEASE, 0)); + } + if (func & rpm_query_info) { + /* Do the nice printout */ + time_t bdate_time; + struct tm *bdate; + char bdatestring[50]; + printf("Name : %-29sRelocations: %s\n", rpm_getstring(RPMTAG_NAME, 0), rpm_getstring(RPMTAG_PREFIXS, 0) ? rpm_getstring(RPMTAG_PREFIXS, 0) : "(not relocateable)"); + printf("Version : %-34sVendor: %s\n", rpm_getstring(RPMTAG_VERSION, 0), rpm_getstring(RPMTAG_VENDOR, 0) ? rpm_getstring(RPMTAG_VENDOR, 0) : "(none)"); + bdate_time = rpm_getint(RPMTAG_BUILDTIME, 0); + bdate = localtime((time_t *) &bdate_time); + strftime(bdatestring, 50, "%a %d %b %Y %T %Z", bdate); + printf("Release : %-30sBuild Date: %s\n", rpm_getstring(RPMTAG_RELEASE, 0), bdatestring); + printf("Install date: %-30sBuild Host: %s\n", "(not installed)", rpm_getstring(RPMTAG_BUILDHOST, 0)); + printf("Group : %-30sSource RPM: %s\n", rpm_getstring(RPMTAG_GROUP, 0), rpm_getstring(RPMTAG_SOURCERPM, 0)); + printf("Size : %-33dLicense: %s\n", rpm_getint(RPMTAG_SIZE, 0), rpm_getstring(RPMTAG_LICENSE, 0)); + printf("URL : %s\n", rpm_getstring(RPMTAG_URL, 0)); + printf("Summary : %s\n", rpm_getstring(RPMTAG_SUMMARY, 0)); + printf("Description :\n%s\n", rpm_getstring(RPMTAG_DESCRIPTION, 0)); + } + if (func & rpm_query_list) { + int count, it, flags; + count = rpm_getcount(RPMTAG_BASENAMES); + for (it = 0; it < count; it++) { + flags = rpm_getint(RPMTAG_FILEFLAGS, it); + switch ((func & rpm_query_list_doc) + (func & rpm_query_list_config)) + { + case rpm_query_list_doc: if (!(flags & RPMFILE_DOC)) continue; break; + case rpm_query_list_config: if (!(flags & RPMFILE_CONFIG)) continue; break; + case rpm_query_list_doc + rpm_query_list_config: if (!((flags & RPMFILE_CONFIG) || (flags & RPMFILE_DOC))) continue; break; + } + printf("%s%s\n", rpm_getstring(RPMTAG_DIRNAMES, rpm_getint(RPMTAG_DIRINDEXES, it)), rpm_getstring(RPMTAG_BASENAMES, it)); + } + } + } + optind++; + free (mytags); + } + return 0; +} + +static void extract_cpio_gz(int fd) { + archive_handle_t *archive_handle; + unsigned char magic[2]; + + /* Initialise */ + archive_handle = init_handle(); + archive_handle->seek = seek_by_read; + //archive_handle->action_header = header_list; + archive_handle->action_data = data_extract_all; + archive_handle->flags |= ARCHIVE_PRESERVE_DATE; + archive_handle->flags |= ARCHIVE_CREATE_LEADING_DIRS; + archive_handle->src_fd = fd; + archive_handle->offset = 0; + + xread(archive_handle->src_fd, &magic, 2); + if ((magic[0] != 0x1f) || (magic[1] != 0x8b)) { + bb_error_msg_and_die("invalid gzip magic"); + } + check_header_gzip(archive_handle->src_fd); + xchdir("/"); // Install RPM's to root + + archive_handle->src_fd = open_transformer(archive_handle->src_fd, inflate_gunzip); + archive_handle->offset = 0; + while (get_header_cpio(archive_handle) == EXIT_SUCCESS); +} + + +static rpm_index **rpm_gettags(int fd, int *num_tags) +{ + rpm_index **tags = xzalloc(200 * sizeof(struct rpmtag *)); /* We should never need mode than 200, and realloc later */ + int pass, tagindex = 0; + lseek(fd, 96, SEEK_CUR); /* Seek past the unused lead */ + + for (pass = 0; pass < 2; pass++) { /* 1st pass is the signature headers, 2nd is the main stuff */ + struct { + char magic[3]; /* 3 byte magic: 0x8e 0xad 0xe8 */ + uint8_t version; /* 1 byte version number */ + uint32_t reserved; /* 4 bytes reserved */ + uint32_t entries; /* Number of entries in header (4 bytes) */ + uint32_t size; /* Size of store (4 bytes) */ + } header; + rpm_index *tmpindex; + int storepos; + + read(fd, &header, sizeof(header)); + if (strncmp((char *) &header.magic, RPM_HEADER_MAGIC, 3) != 0) return NULL; /* Invalid magic */ + if (header.version != 1) return NULL; /* This program only supports v1 headers */ + header.size = ntohl(header.size); + header.entries = ntohl(header.entries); + storepos = lseek(fd,0,SEEK_CUR) + header.entries * 16; + + while (header.entries--) { + tmpindex = tags[tagindex++] = xmalloc(sizeof(rpm_index)); + read(fd, tmpindex, sizeof(rpm_index)); + tmpindex->tag = ntohl(tmpindex->tag); tmpindex->type = ntohl(tmpindex->type); tmpindex->count = ntohl(tmpindex->count); + tmpindex->offset = storepos + ntohl(tmpindex->offset); + if (pass==0) tmpindex->tag -= 743; + } + lseek(fd, header.size, SEEK_CUR); /* Seek past store */ + if (pass==0) lseek(fd, (8 - (lseek(fd,0,SEEK_CUR) % 8)) % 8, SEEK_CUR); /* Skip padding to 8 byte boundary after reading signature headers */ + } + tags = realloc(tags, tagindex * sizeof(struct rpmtag *)); /* realloc tags to save space */ + *num_tags = tagindex; + return tags; /* All done, leave the file at the start of the gzipped cpio archive */ +} + +static int bsearch_rpmtag(const void *key, const void *item) +{ + int *tag = (int *)key; + rpm_index **tmp = (rpm_index **) item; + return (*tag - tmp[0]->tag); +} + +static int rpm_getcount(int tag) +{ + rpm_index **found; + found = bsearch(&tag, mytags, tagcount, sizeof(struct rpmtag *), bsearch_rpmtag); + if (!found) return 0; + else return found[0]->count; +} + +static char *rpm_getstring(int tag, int itemindex) +{ + rpm_index **found; + found = bsearch(&tag, mytags, tagcount, sizeof(struct rpmtag *), bsearch_rpmtag); + if (!found || itemindex >= found[0]->count) return NULL; + if (found[0]->type == RPM_STRING_TYPE || found[0]->type == RPM_I18NSTRING_TYPE || found[0]->type == RPM_STRING_ARRAY_TYPE) { + int n; + char *tmpstr = (char *) (map + found[0]->offset); + for (n=0; n < itemindex; n++) tmpstr = tmpstr + strlen(tmpstr) + 1; + return tmpstr; + } else return NULL; +} + +static int rpm_getint(int tag, int itemindex) +{ + rpm_index **found; + int n, *tmpint; + /* gcc throws warnings here when sizeof(void*)!=sizeof(int) ... + * it's ok to ignore it because tag won't be used as a pointer */ + found = bsearch(&tag, mytags, tagcount, sizeof(struct rpmtag *), bsearch_rpmtag); + if (!found || itemindex >= found[0]->count) return -1; + tmpint = (int *) (map + found[0]->offset); + if (found[0]->type == RPM_INT32_TYPE) { + for (n=0; ntype == RPM_INT16_TYPE) { + for (n=0; ntype == RPM_INT8_TYPE) { + for (n=0; n + * + * Note, that as of BusyBox-0.43, tar has been completely rewritten from the + * ground up. It still has remnants of the old code lying about, but it is + * very different now (i.e., cleaner, less global variables, etc.) + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Based in part in the tar implementation in sash + * Copyright (c) 1999 by David I. Bell + * Permission is granted to use, distribute, or modify this source, + * provided that this copyright notice remains intact. + * Permission to distribute sash derived code under the GPL has been granted. + * + * Based in part on the tar implementation from busybox-0.28 + * Copyright (C) 1995 Bruce Perens + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "unarchive.h" +#include +#include + +#if ENABLE_FEATURE_TAR_CREATE + +/* Tar file constants */ + +#define TAR_BLOCK_SIZE 512 + +/* POSIX tar Header Block, from POSIX 1003.1-1990 */ +#define NAME_SIZE 100 +#define NAME_SIZE_STR "100" +struct TarHeader { /* byte offset */ + char name[NAME_SIZE]; /* 0-99 */ + char mode[8]; /* 100-107 */ + char uid[8]; /* 108-115 */ + char gid[8]; /* 116-123 */ + char size[12]; /* 124-135 */ + char mtime[12]; /* 136-147 */ + char chksum[8]; /* 148-155 */ + char typeflag; /* 156-156 */ + char linkname[NAME_SIZE]; /* 157-256 */ + char magic[6]; /* 257-262 */ + char version[2]; /* 263-264 */ + char uname[32]; /* 265-296 */ + char gname[32]; /* 297-328 */ + char devmajor[8]; /* 329-336 */ + char devminor[8]; /* 337-344 */ + char prefix[155]; /* 345-499 */ + char padding[12]; /* 500-512 (pad to exactly the TAR_BLOCK_SIZE) */ +}; +typedef struct TarHeader TarHeader; + +/* +** writeTarFile(), writeFileToTarball(), and writeTarHeader() are +** the only functions that deal with the HardLinkInfo structure. +** Even these functions use the xxxHardLinkInfo() functions. +*/ +typedef struct HardLinkInfo HardLinkInfo; +struct HardLinkInfo { + HardLinkInfo *next; /* Next entry in list */ + dev_t dev; /* Device number */ + ino_t ino; /* Inode number */ + short linkCount; /* (Hard) Link Count */ + char name[1]; /* Start of filename (must be last) */ +}; + +/* Some info to be carried along when creating a new tarball */ +struct TarBallInfo { + int tarFd; /* Open-for-write file descriptor + for the tarball */ + struct stat statBuf; /* Stat info for the tarball, letting + us know the inode and device that the + tarball lives, so we can avoid trying + to include the tarball into itself */ + int verboseFlag; /* Whether to print extra stuff or not */ + const llist_t *excludeList; /* List of files to not include */ + HardLinkInfo *hlInfoHead; /* Hard Link Tracking Information */ + HardLinkInfo *hlInfo; /* Hard Link Info for the current file */ +}; +typedef struct TarBallInfo TarBallInfo; + +/* A nice enum with all the possible tar file content types */ +enum TarFileType { + REGTYPE = '0', /* regular file */ + REGTYPE0 = '\0', /* regular file (ancient bug compat) */ + LNKTYPE = '1', /* hard link */ + SYMTYPE = '2', /* symbolic link */ + CHRTYPE = '3', /* character special */ + BLKTYPE = '4', /* block special */ + DIRTYPE = '5', /* directory */ + FIFOTYPE = '6', /* FIFO special */ + CONTTYPE = '7', /* reserved */ + GNULONGLINK = 'K', /* GNU long (>100 chars) link name */ + GNULONGNAME = 'L', /* GNU long (>100 chars) file name */ +}; +typedef enum TarFileType TarFileType; + +/* Might be faster (and bigger) if the dev/ino were stored in numeric order;) */ +static void addHardLinkInfo(HardLinkInfo ** hlInfoHeadPtr, + struct stat *statbuf, + const char *fileName) +{ + /* Note: hlInfoHeadPtr can never be NULL! */ + HardLinkInfo *hlInfo; + + hlInfo = xmalloc(sizeof(HardLinkInfo) + strlen(fileName)); + hlInfo->next = *hlInfoHeadPtr; + *hlInfoHeadPtr = hlInfo; + hlInfo->dev = statbuf->st_dev; + hlInfo->ino = statbuf->st_ino; + hlInfo->linkCount = statbuf->st_nlink; + strcpy(hlInfo->name, fileName); +} + +static void freeHardLinkInfo(HardLinkInfo ** hlInfoHeadPtr) +{ + HardLinkInfo *hlInfo; + HardLinkInfo *hlInfoNext; + + if (hlInfoHeadPtr) { + hlInfo = *hlInfoHeadPtr; + while (hlInfo) { + hlInfoNext = hlInfo->next; + free(hlInfo); + hlInfo = hlInfoNext; + } + *hlInfoHeadPtr = NULL; + } + return; +} + +/* Might be faster (and bigger) if the dev/ino were stored in numeric order;) */ +static HardLinkInfo *findHardLinkInfo(HardLinkInfo * hlInfo, struct stat *statbuf) +{ + while (hlInfo) { + if ((statbuf->st_ino == hlInfo->ino) && (statbuf->st_dev == hlInfo->dev)) + break; + hlInfo = hlInfo->next; + } + return hlInfo; +} + +/* Put an octal string into the specified buffer. + * The number is zero padded and possibly null terminated. + * Stores low-order bits only if whole value does not fit. */ +static void putOctal(char *cp, int len, off_t value) +{ + char tempBuffer[sizeof(off_t)*3+1]; + char *tempString = tempBuffer; + int width; + + width = sprintf(tempBuffer, "%0*"OFF_FMT"o", len, value); + tempString += (width - len); + + /* If string has leading zeroes, we can drop one */ + /* and field will have trailing '\0' */ + /* (increases chances of compat with other tars) */ + if (tempString[0] == '0') + tempString++; + + /* Copy the string to the field */ + memcpy(cp, tempString, len); +} +#define PUT_OCTAL(a, b) putOctal((a), sizeof(a), (b)) + +static void chksum_and_xwrite(int fd, struct TarHeader* hp) +{ + const unsigned char *cp; + int chksum, size; + + strcpy(hp->magic, "ustar "); + + /* Calculate and store the checksum (i.e., the sum of all of the bytes of + * the header). The checksum field must be filled with blanks for the + * calculation. The checksum field is formatted differently from the + * other fields: it has 6 digits, a null, then a space -- rather than + * digits, followed by a null like the other fields... */ + memset(hp->chksum, ' ', sizeof(hp->chksum)); + cp = (const unsigned char *) hp; + chksum = 0; + size = sizeof(*hp); + do { chksum += *cp++; } while (--size); + putOctal(hp->chksum, sizeof(hp->chksum)-1, chksum); + + /* Now write the header out to disk */ + xwrite(fd, hp, sizeof(*hp)); +} + +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS +static void writeLongname(int fd, int type, const char *name, int dir) +{ + static const struct { + char mode[8]; /* 100-107 */ + char uid[8]; /* 108-115 */ + char gid[8]; /* 116-123 */ + char size[12]; /* 124-135 */ + char mtime[12]; /* 136-147 */ + } prefilled = { + "0000000", + "0000000", + "0000000", + "00000000000", + "00000000000", + }; + struct TarHeader header; + int size; + + dir = !!dir; /* normalize: 0/1 */ + size = strlen(name) + 1 + dir; /* GNU tar uses strlen+1 */ + /* + dir: account for possible '/' */ + + memset(&header, 0, sizeof(header)); + strcpy(header.name, "././@LongLink"); + memcpy(header.mode, prefilled.mode, sizeof(prefilled)); + PUT_OCTAL(header.size, size); + header.typeflag = type; + chksum_and_xwrite(fd, &header); + + /* Write filename[/] and pad the block. */ + /* dir=0: writes 'name', pads */ + /* dir=1: writes 'name', writes '/', pads */ + dir *= 2; + xwrite(fd, name, size - dir); + xwrite(fd, "/", dir); + size = (-size) & (TAR_BLOCK_SIZE-1); + memset(&header, 0, size); + xwrite(fd, &header, size); +} +#endif + +/* Write out a tar header for the specified file/directory/whatever */ +void BUG_tar_header_size(void); +static int writeTarHeader(struct TarBallInfo *tbInfo, + const char *header_name, const char *fileName, struct stat *statbuf) +{ + struct TarHeader header; + + if (sizeof(header) != 512) + BUG_tar_header_size(); + + memset(&header, 0, sizeof(struct TarHeader)); + + strncpy(header.name, header_name, sizeof(header.name)); + + /* POSIX says to mask mode with 07777. */ + PUT_OCTAL(header.mode, statbuf->st_mode & 07777); + PUT_OCTAL(header.uid, statbuf->st_uid); + PUT_OCTAL(header.gid, statbuf->st_gid); + memset(header.size, '0', sizeof(header.size)-1); /* Regular file size is handled later */ + PUT_OCTAL(header.mtime, statbuf->st_mtime); + + /* Enter the user and group names */ + safe_strncpy(header.uname, get_cached_username(statbuf->st_uid), sizeof(header.uname)); + safe_strncpy(header.gname, get_cached_groupname(statbuf->st_gid), sizeof(header.gname)); + + if (tbInfo->hlInfo) { + /* This is a hard link */ + header.typeflag = LNKTYPE; + strncpy(header.linkname, tbInfo->hlInfo->name, + sizeof(header.linkname)); +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS + /* Write out long linkname if needed */ + if (header.linkname[sizeof(header.linkname)-1]) + writeLongname(tbInfo->tarFd, GNULONGLINK, + tbInfo->hlInfo->name, 0); +#endif + } else if (S_ISLNK(statbuf->st_mode)) { + char *lpath = xreadlink(fileName); + if (!lpath) /* Already printed err msg inside xreadlink() */ + return FALSE; + header.typeflag = SYMTYPE; + strncpy(header.linkname, lpath, sizeof(header.linkname)); +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS + /* Write out long linkname if needed */ + if (header.linkname[sizeof(header.linkname)-1]) + writeLongname(tbInfo->tarFd, GNULONGLINK, lpath, 0); +#else + /* If it is larger than 100 bytes, bail out */ + if (header.linkname[sizeof(header.linkname)-1]) { + free(lpath); + bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported"); + return FALSE; + } +#endif + free(lpath); + } else if (S_ISDIR(statbuf->st_mode)) { + header.typeflag = DIRTYPE; + /* Append '/' only if there is a space for it */ + if (!header.name[sizeof(header.name)-1]) + header.name[strlen(header.name)] = '/'; + } else if (S_ISCHR(statbuf->st_mode)) { + header.typeflag = CHRTYPE; + PUT_OCTAL(header.devmajor, major(statbuf->st_rdev)); + PUT_OCTAL(header.devminor, minor(statbuf->st_rdev)); + } else if (S_ISBLK(statbuf->st_mode)) { + header.typeflag = BLKTYPE; + PUT_OCTAL(header.devmajor, major(statbuf->st_rdev)); + PUT_OCTAL(header.devminor, minor(statbuf->st_rdev)); + } else if (S_ISFIFO(statbuf->st_mode)) { + header.typeflag = FIFOTYPE; + } else if (S_ISREG(statbuf->st_mode)) { + if (sizeof(statbuf->st_size) > 4 + && statbuf->st_size > (off_t)0777777777777LL + ) { + bb_error_msg_and_die("cannot store file '%s' " + "of size %"OFF_FMT"d, aborting", + fileName, statbuf->st_size); + } + header.typeflag = REGTYPE; + PUT_OCTAL(header.size, statbuf->st_size); + } else { + bb_error_msg("%s: unknown file type", fileName); + return FALSE; + } + +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS + /* Write out long name if needed */ + /* (we, like GNU tar, output long linkname *before* long name) */ + if (header.name[sizeof(header.name)-1]) + writeLongname(tbInfo->tarFd, GNULONGNAME, + header_name, S_ISDIR(statbuf->st_mode)); +#endif + + /* Now write the header out to disk */ + chksum_and_xwrite(tbInfo->tarFd, &header); + + /* Now do the verbose thing (or not) */ + if (tbInfo->verboseFlag) { + FILE *vbFd = stdout; + + if (tbInfo->tarFd == STDOUT_FILENO) /* If the archive goes to stdout, verbose to stderr */ + vbFd = stderr; + /* GNU "tar cvvf" prints "extended" listing a-la "ls -l" */ + /* We don't have such excesses here: for us "v" == "vv" */ + /* '/' is probably a GNUism */ + fprintf(vbFd, "%s%s\n", header_name, + S_ISDIR(statbuf->st_mode) ? "/" : ""); + } + + return TRUE; +} + +# if ENABLE_FEATURE_TAR_FROM +static int exclude_file(const llist_t *excluded_files, const char *file) +{ + while (excluded_files) { + if (excluded_files->data[0] == '/') { + if (fnmatch(excluded_files->data, file, + FNM_PATHNAME | FNM_LEADING_DIR) == 0) + return 1; + } else { + const char *p; + + for (p = file; p[0] != '\0'; p++) { + if ((p == file || p[-1] == '/') && p[0] != '/' && + fnmatch(excluded_files->data, p, + FNM_PATHNAME | FNM_LEADING_DIR) == 0) + return 1; + } + } + excluded_files = excluded_files->link; + } + + return 0; +} +# else +#define exclude_file(excluded_files, file) 0 +# endif + +static int writeFileToTarball(const char *fileName, struct stat *statbuf, + void *userData, int depth) +{ + struct TarBallInfo *tbInfo = (struct TarBallInfo *) userData; + const char *header_name; + int inputFileFd = -1; + + /* + * Check to see if we are dealing with a hard link. + * If so - + * Treat the first occurance of a given dev/inode as a file while + * treating any additional occurances as hard links. This is done + * by adding the file information to the HardLinkInfo linked list. + */ + tbInfo->hlInfo = NULL; + if (statbuf->st_nlink > 1) { + tbInfo->hlInfo = findHardLinkInfo(tbInfo->hlInfoHead, statbuf); + if (tbInfo->hlInfo == NULL) + addHardLinkInfo(&tbInfo->hlInfoHead, statbuf, fileName); + } + + /* It is against the rules to archive a socket */ + if (S_ISSOCK(statbuf->st_mode)) { + bb_error_msg("%s: socket ignored", fileName); + return TRUE; + } + + /* It is a bad idea to store the archive we are in the process of creating, + * so check the device and inode to be sure that this particular file isn't + * the new tarball */ + if (tbInfo->statBuf.st_dev == statbuf->st_dev && + tbInfo->statBuf.st_ino == statbuf->st_ino) { + bb_error_msg("%s: file is the archive; skipping", fileName); + return TRUE; + } + + header_name = fileName; + while (header_name[0] == '/') { + static int alreadyWarned = FALSE; + + if (alreadyWarned == FALSE) { + bb_error_msg("removing leading '/' from member names"); + alreadyWarned = TRUE; + } + header_name++; + } + +#if !ENABLE_FEATURE_TAR_GNU_EXTENSIONS + if (strlen(fileName) >= NAME_SIZE) { + bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported"); + return TRUE; + } +#endif + + if (header_name[0] == '\0') + return TRUE; + + if (exclude_file(tbInfo->excludeList, header_name)) + return SKIP; + + /* Is this a regular file? */ + if (tbInfo->hlInfo == NULL && S_ISREG(statbuf->st_mode)) { + /* open the file we want to archive, and make sure all is well */ + inputFileFd = open(fileName, O_RDONLY); + if (inputFileFd < 0) { + bb_perror_msg("%s: cannot open", fileName); + return FALSE; + } + } + + /* Add an entry to the tarball */ + if (writeTarHeader(tbInfo, header_name, fileName, statbuf) == FALSE) { + return FALSE; + } + + /* If it was a regular file, write out the body */ + if (inputFileFd >= 0) { + off_t readSize = 0; + + /* write the file to the archive */ + readSize = bb_copyfd_size(inputFileFd, tbInfo->tarFd, statbuf->st_size); + /* readSize < 0 means that error was already reported */ + if (readSize != statbuf->st_size && readSize >= 0) { + /* Deadly. We record size into header first, */ + /* and then write out file. If file shrinks in between, */ + /* tar will be corrupted. So bail out. */ + /* NB: GNU tar 1.16 warns and pads with zeroes */ + /* or even seeks back and updates header */ + bb_error_msg_and_die("short read from %s, aborting", fileName); + } + /* Check that file did not grow in between? */ + /* if (safe_read(inputFileFd,1) == 1) warn but continue? */ + close(inputFileFd); + + /* Pad the file up to the tar block size */ + /* (a few tricks here in the name of code size) */ + readSize = (-(int)readSize) & (TAR_BLOCK_SIZE-1); + memset(bb_common_bufsiz1, 0, readSize); + xwrite(tbInfo->tarFd, bb_common_bufsiz1, readSize); + } + + return TRUE; +} + +static int writeTarFile(const int tar_fd, const int verboseFlag, + const unsigned long dereferenceFlag, const llist_t *include, + const llist_t *exclude, const int gzip) +{ + pid_t gzipPid = 0; + int errorFlag = FALSE; + struct TarBallInfo tbInfo; + + tbInfo.hlInfoHead = NULL; + + fchmod(tar_fd, 0644); + tbInfo.tarFd = tar_fd; + tbInfo.verboseFlag = verboseFlag; + + /* Store the stat info for the tarball's file, so + * can avoid including the tarball into itself.... */ + if (fstat(tbInfo.tarFd, &tbInfo.statBuf) < 0) + bb_perror_msg_and_die("cannot stat tar file"); + + if ((ENABLE_FEATURE_TAR_GZIP || ENABLE_FEATURE_TAR_BZIP2) && gzip) { + int gzipDataPipe[2] = { -1, -1 }; + int gzipStatusPipe[2] = { -1, -1 }; + volatile int vfork_exec_errno = 0; + char *zip_exec = (gzip == 1) ? "gzip" : "bzip2"; + + + if (pipe(gzipDataPipe) < 0 || pipe(gzipStatusPipe) < 0) + bb_perror_msg_and_die("pipe"); + + signal(SIGPIPE, SIG_IGN); /* we only want EPIPE on errors */ + +# if __GNUC__ + /* Avoid vfork clobbering */ + (void) &include; + (void) &errorFlag; + (void) &zip_exec; +# endif + + gzipPid = vfork(); + + if (gzipPid == 0) { + dup2(gzipDataPipe[0], 0); + close(gzipDataPipe[1]); + + dup2(tbInfo.tarFd, 1); + + close(gzipStatusPipe[0]); + fcntl(gzipStatusPipe[1], F_SETFD, FD_CLOEXEC); /* close on exec shows success */ + + execlp(zip_exec, zip_exec, "-f", NULL); + vfork_exec_errno = errno; + + close(gzipStatusPipe[1]); + exit(-1); + } else if (gzipPid > 0) { + close(gzipDataPipe[0]); + close(gzipStatusPipe[1]); + + while (1) { + char buf; + + int n = full_read(gzipStatusPipe[0], &buf, 1); + + if (n == 0 && vfork_exec_errno != 0) { + errno = vfork_exec_errno; + bb_perror_msg_and_die("cannot exec %s", zip_exec); + } else if ((n < 0) && (errno == EAGAIN || errno == EINTR)) + continue; /* try it again */ + break; + } + close(gzipStatusPipe[0]); + + tbInfo.tarFd = gzipDataPipe[1]; + } else bb_perror_msg_and_die("vfork gzip"); + } + + tbInfo.excludeList = exclude; + + /* Read the directory/files and iterate over them one at a time */ + while (include) { + if (!recursive_action(include->data, TRUE, dereferenceFlag, + FALSE, writeFileToTarball, writeFileToTarball, &tbInfo, 0)) + { + errorFlag = TRUE; + } + include = include->link; + } + /* Write two empty blocks to the end of the archive */ + memset(bb_common_bufsiz1, 0, 2*TAR_BLOCK_SIZE); + xwrite(tbInfo.tarFd, bb_common_bufsiz1, 2*TAR_BLOCK_SIZE); + + /* To be pedantically correct, we would check if the tarball + * is smaller than 20 tar blocks, and pad it if it was smaller, + * but that isn't necessary for GNU tar interoperability, and + * so is considered a waste of space */ + + /* Close so the child process (if any) will exit */ + close(tbInfo.tarFd); + + /* Hang up the tools, close up shop, head home */ + if (ENABLE_FEATURE_CLEAN_UP) + freeHardLinkInfo(&tbInfo.hlInfoHead); + + if (errorFlag) + bb_error_msg("error exit delayed from previous errors"); + + if (gzipPid && waitpid(gzipPid, NULL, 0) == -1) + bb_error_msg("waitpid failed"); + + return !errorFlag; +} +#else +int writeTarFile(const int tar_fd, const int verboseFlag, + const unsigned long dereferenceFlag, const llist_t *include, + const llist_t *exclude, const int gzip); +#endif /* tar_create */ + +#if ENABLE_FEATURE_TAR_FROM +static llist_t *append_file_list_to_list(llist_t *list) +{ + FILE *src_stream; + llist_t *cur = list; + llist_t *tmp; + char *line; + llist_t *newlist = NULL; + + while (cur) { + src_stream = xfopen(cur->data, "r"); + tmp = cur; + cur = cur->link; + free(tmp); + while ((line = xmalloc_getline(src_stream)) != NULL) { + /* kill trailing '/' unless the string is just "/" */ + char *cp = last_char_is(line, '/'); + if (cp > line) + *cp = '\0'; + llist_add_to(&newlist, line); + } + fclose(src_stream); + } + return newlist; +} +#else +#define append_file_list_to_list(x) 0 +#endif + +#if ENABLE_FEATURE_TAR_COMPRESS +static char get_header_tar_Z(archive_handle_t *archive_handle) +{ + /* Can't lseek over pipes */ + archive_handle->seek = seek_by_read; + + /* do the decompression, and cleanup */ + if (xread_char(archive_handle->src_fd) != 0x1f + || xread_char(archive_handle->src_fd) != 0x9d + ) { + bb_error_msg_and_die("invalid magic"); + } + + archive_handle->src_fd = open_transformer(archive_handle->src_fd, uncompress); + archive_handle->offset = 0; + while (get_header_tar(archive_handle) == EXIT_SUCCESS) + /* nothing */; + + /* Can only do one file at a time */ + return EXIT_FAILURE; +} +#else +#define get_header_tar_Z 0 +#endif + +enum { + OPTBIT_KEEP_OLD = 7, + USE_FEATURE_TAR_CREATE( OPTBIT_CREATE ,) + USE_FEATURE_TAR_CREATE( OPTBIT_DEREFERENCE ,) + USE_FEATURE_TAR_BZIP2( OPTBIT_BZIP2 ,) + USE_FEATURE_TAR_LZMA( OPTBIT_LZMA ,) + USE_FEATURE_TAR_FROM( OPTBIT_INCLUDE_FROM,) + USE_FEATURE_TAR_FROM( OPTBIT_EXCLUDE_FROM,) + USE_FEATURE_TAR_GZIP( OPTBIT_GZIP ,) + USE_FEATURE_TAR_COMPRESS(OPTBIT_COMPRESS ,) + OPTBIT_NOPRESERVE_OWN, + OPTBIT_NOPRESERVE_PERM, + OPT_TEST = 1 << 0, // t + OPT_EXTRACT = 1 << 1, // x + OPT_BASEDIR = 1 << 2, // C + OPT_TARNAME = 1 << 3, // f + OPT_2STDOUT = 1 << 4, // O + OPT_P = 1 << 5, // p + OPT_VERBOSE = 1 << 6, // v + OPT_KEEP_OLD = 1 << 7, // k + OPT_CREATE = USE_FEATURE_TAR_CREATE( (1<flags = ARCHIVE_CREATE_LEADING_DIRS + | ARCHIVE_PRESERVE_DATE + | ARCHIVE_EXTRACT_UNCONDITIONAL; + + /* Prepend '-' to the first argument if required */ + opt_complementary = "--:" // first arg is options + "tt:vv:" // count -t,-v + "?:" // bail out with usage instead of error return + "X::T::" // cumulative lists + "\xff::" // cumulative lists for --exclude + USE_FEATURE_TAR_CREATE("c:") "t:x:" // at least one of these is reqd + USE_FEATURE_TAR_CREATE("c--tx:t--cx:x--ct") // mutually exclusive + SKIP_FEATURE_TAR_CREATE("t--x:x--t"); // mutually exclusive +#if ENABLE_FEATURE_TAR_LONG_OPTIONS + applet_long_options = tar_long_options; +#endif + opt = getopt32(argc, argv, + "txC:f:Opvk" + USE_FEATURE_TAR_CREATE( "ch" ) + USE_FEATURE_TAR_BZIP2( "j" ) + USE_FEATURE_TAR_LZMA( "a" ) + USE_FEATURE_TAR_FROM( "T:X:") + USE_FEATURE_TAR_GZIP( "z" ) + USE_FEATURE_TAR_COMPRESS("Z" ) + , + &base_dir, // -C dir + &tar_filename, // -f filename + USE_FEATURE_TAR_FROM(&(tar_handle->accept),) // T + USE_FEATURE_TAR_FROM(&(tar_handle->reject),) // X + USE_FEATURE_TAR_FROM(&excludes ,) // --exclude + &verboseFlag, // combined count for -t and -v + &verboseFlag // combined count for -t and -v + ); + + if (verboseFlag) tar_handle->action_header = header_verbose_list; + if (verboseFlag == 1) tar_handle->action_header = header_list; + + if (opt & OPT_EXTRACT) + tar_handle->action_data = data_extract_all; + + if (opt & OPT_2STDOUT) + tar_handle->action_data = data_extract_to_stdout; + + if (opt & OPT_KEEP_OLD) + tar_handle->flags &= ~ARCHIVE_EXTRACT_UNCONDITIONAL; + + if (opt & OPT_NOPRESERVE_OWN) + tar_handle->flags |= ARCHIVE_NOPRESERVE_OWN; + + if (opt & OPT_NOPRESERVE_PERM) + tar_handle->flags |= ARCHIVE_NOPRESERVE_PERM; + + if (opt & OPT_GZIP) + get_header_ptr = get_header_tar_gz; + + if (opt & OPT_BZIP2) + get_header_ptr = get_header_tar_bz2; + + if (opt & OPT_LZMA) + get_header_ptr = get_header_tar_lzma; + + if (opt & OPT_COMPRESS) + get_header_ptr = get_header_tar_Z; + + if (ENABLE_FEATURE_TAR_FROM) { + tar_handle->reject = append_file_list_to_list(tar_handle->reject); + /* Append excludes to reject */ + while (excludes) { + llist_t *temp = excludes->link; + excludes->link = tar_handle->reject; + tar_handle->reject = excludes; + excludes = temp; + } + tar_handle->accept = append_file_list_to_list(tar_handle->accept); + } + + /* Check if we are reading from stdin */ + if (argv[optind] && *argv[optind] == '-') { + /* Default is to read from stdin, so just skip to next arg */ + optind++; + } + + /* Setup an array of filenames to work with */ + /* TODO: This is the same as in ar, separate function ? */ + while (optind < argc) { + /* kill trailing '/' unless the string is just "/" */ + char *cp = last_char_is(argv[optind], '/'); + if (cp > argv[optind]) + *cp = '\0'; + llist_add_to(&tar_handle->accept, argv[optind]); + optind++; + } + tar_handle->accept = rev_llist(tar_handle->accept); + + if (tar_handle->accept || tar_handle->reject) + tar_handle->filter = filter_accept_reject_list; + + /* Open the tar file */ + { + FILE *tar_stream; + int flags; + + if (opt & OPT_CREATE) { + /* Make sure there is at least one file to tar up. */ + if (tar_handle->accept == NULL) + bb_error_msg_and_die("empty archive"); + + tar_stream = stdout; + /* Mimicking GNU tar 1.15.1: */ + flags = O_WRONLY|O_CREAT|O_TRUNC; + /* was doing unlink; open(O_WRONLY|O_CREAT|O_EXCL); why? */ + } else { + tar_stream = stdin; + flags = O_RDONLY; + } + + if (tar_filename[0] == '-' && !tar_filename[1]) { + tar_handle->src_fd = fileno(tar_stream); + tar_handle->seek = seek_by_read; + } else { + tar_handle->src_fd = xopen(tar_filename, flags); + } + } + + if (base_dir) + xchdir(base_dir); + + /* create an archive */ + if (opt & OPT_CREATE) { + int zipMode = 0; + if (ENABLE_FEATURE_TAR_GZIP && get_header_ptr == get_header_tar_gz) + zipMode = 1; + if (ENABLE_FEATURE_TAR_BZIP2 && get_header_ptr == get_header_tar_bz2) + zipMode = 2; + writeTarFile(tar_handle->src_fd, verboseFlag, opt & OPT_DEREFERENCE, + tar_handle->accept, + tar_handle->reject, zipMode); + /* NB: writeTarFile() closes tar_handle->src_fd */ + return EXIT_SUCCESS; + } + + while (get_header_ptr(tar_handle) == EXIT_SUCCESS) + /* nothing */; + + /* Check that every file that should have been extracted was */ + while (tar_handle->accept) { + if (!find_list_entry(tar_handle->reject, tar_handle->accept->data) + && !find_list_entry(tar_handle->passed, tar_handle->accept->data) + ) { + bb_error_msg_and_die("%s: not found in archive", + tar_handle->accept->data); + } + tar_handle->accept = tar_handle->accept->link; + } + if (ENABLE_FEATURE_CLEAN_UP /* && tar_handle->src_fd != STDIN_FILENO */) + close(tar_handle->src_fd); + + return EXIT_SUCCESS; +} diff --git a/archival/uncompress.c b/archival/uncompress.c new file mode 100644 index 000000000..3c78961d5 --- /dev/null +++ b/archival/uncompress.c @@ -0,0 +1,94 @@ +/* vi: set sw=4 ts=4: */ +/* + * Uncompress applet for busybox (c) 2002 Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "unarchive.h" + +#define GUNZIP_TO_STDOUT 1 +#define GUNZIP_FORCE 2 + +int uncompress_main(int argc, char **argv) +{ + int status = EXIT_SUCCESS; + unsigned long flags; + + flags = getopt32(argc, argv, "cf"); + + while (optind < argc) { + char *compressed_file = argv[optind++]; + char *delete_path = NULL; + char *uncompressed_file = NULL; + int src_fd; + int dst_fd; + + if (strcmp(compressed_file, "-") == 0) { + src_fd = STDIN_FILENO; + flags |= GUNZIP_TO_STDOUT; + } else { + src_fd = xopen(compressed_file, O_RDONLY); + } + + /* Check that the input is sane. */ + if (isatty(src_fd) && ((flags & GUNZIP_FORCE) == 0)) { + bb_error_msg_and_die + ("compressed data not read from terminal. Use -f to force it."); + } + + /* Set output filename and number */ + if (flags & GUNZIP_TO_STDOUT) { + dst_fd = STDOUT_FILENO; + } else { + struct stat stat_buf; + char *extension; + + uncompressed_file = xstrdup(compressed_file); + + extension = strrchr(uncompressed_file, '.'); + if (!extension || (strcmp(extension, ".Z") != 0)) { + bb_error_msg_and_die("invalid extension"); + } + *extension = '\0'; + + /* Open output file */ + xstat(compressed_file, &stat_buf); + dst_fd = xopen3(uncompressed_file, + O_WRONLY | O_CREAT | O_TRUNC, + stat_buf.st_mode); + + /* If unzip succeeds remove the old file */ + delete_path = compressed_file; + } + + /* do the decompression, and cleanup */ + if ((xread_char(src_fd) != 0x1f) || (xread_char(src_fd) != 0x9d)) { + bb_error_msg_and_die("invalid magic"); + } + + status = uncompress(src_fd, dst_fd); + + if ((status != EXIT_SUCCESS) && (uncompressed_file)) { + /* Unzip failed, remove the uncomressed file instead of compressed file */ + delete_path = uncompressed_file; + } + + if (dst_fd != STDOUT_FILENO) { + close(dst_fd); + } + if (src_fd != STDIN_FILENO) { + close(src_fd); + } + + /* delete_path will be NULL if in test mode or from stdin */ + if (delete_path && (unlink(delete_path) == -1)) { + bb_error_msg_and_die("cannot remove %s", delete_path); + } + + free(uncompressed_file); + } + + return status; +} diff --git a/archival/unlzma.c b/archival/unlzma.c new file mode 100644 index 000000000..46fbefdc0 --- /dev/null +++ b/archival/unlzma.c @@ -0,0 +1,64 @@ +/* vi: set sw=4 ts=4: */ +/* + * Small lzma deflate implementation. + * Copyright (C) 2006 Aurelien Jacobs + * + * Based on bunzip.c from busybox + * + * Licensed under GPL v2, see file LICENSE in this tarball for details. + */ + +/* Why our g[un]zip/bunzip2 are so ugly compared to this beauty? */ + +#include "busybox.h" +#include "unarchive.h" + +#define UNLZMA_OPT_STDOUT 1 + +int unlzma_main(int argc, char **argv) +{ + USE_DESKTOP(long long) int status; + char *filename; + unsigned opt; + int src_fd, dst_fd; + + opt = getopt32(argc, argv, "c"); + + /* Set input filename and number */ + filename = argv[optind]; + if (filename && (filename[0] != '-') && (filename[1] != '\0')) { + /* Open input file */ + src_fd = xopen(filename, O_RDONLY); + } else { + src_fd = STDIN_FILENO; + filename = 0; + } + + /* if called as lzmacat force the stdout flag */ + if ((opt & UNLZMA_OPT_STDOUT) || applet_name[4] == 'c') + filename = 0; + + if (filename) { + struct stat stat_buf; + /* bug: char *extension = filename + strlen(filename) - 5; */ + char *extension = strrchr(filename, '.'); + if (!extension || strcmp(extension, ".lzma") != 0) { + bb_error_msg_and_die("invalid extension"); + } + xstat(filename, &stat_buf); + *extension = '\0'; + dst_fd = xopen3(filename, O_WRONLY | O_CREAT | O_TRUNC, + stat_buf.st_mode); + } else + dst_fd = STDOUT_FILENO; + status = unlzma(src_fd, dst_fd); + if (filename) { + if (status >= 0) /* if success delete src, else delete dst */ + filename[strlen(filename)] = '.'; + if (unlink(filename) < 0) { + bb_error_msg_and_die("cannot remove %s", filename); + } + } + + return (status < 0); +} diff --git a/archival/unzip.c b/archival/unzip.c new file mode 100644 index 000000000..8b1c281c4 --- /dev/null +++ b/archival/unzip.c @@ -0,0 +1,386 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini unzip implementation for busybox + * + * Copyright (C) 2004 by Ed Clark + * + * Loosely based on original busybox unzip applet by Laurence Anderson. + * All options and features should work in this version. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* For reference see + * http://www.pkware.com/company/standards/appnote/ + * http://www.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip + */ + +/* TODO + * Endian issues + * Zip64 + other methods + * Improve handling of zip format, ie. + * - deferred CRC, comp. & uncomp. lengths (zip header flags bit 3) + * - unix file permissions, etc. + * - central directory + */ + +#include "busybox.h" +#include "unarchive.h" + +#define ZIP_FILEHEADER_MAGIC SWAP_LE32(0x04034b50) +#define ZIP_CDS_MAGIC SWAP_LE32(0x02014b50) +#define ZIP_CDS_END_MAGIC SWAP_LE32(0x06054b50) +#define ZIP_DD_MAGIC SWAP_LE32(0x08074b50) + +extern unsigned int gunzip_crc; +extern unsigned int gunzip_bytes_out; + +typedef union { + unsigned char raw[26]; + struct { + unsigned short version; /* 0-1 */ + unsigned short flags; /* 2-3 */ + unsigned short method; /* 4-5 */ + unsigned short modtime; /* 6-7 */ + unsigned short moddate; /* 8-9 */ + unsigned int crc32 ATTRIBUTE_PACKED; /* 10-13 */ + unsigned int cmpsize ATTRIBUTE_PACKED; /* 14-17 */ + unsigned int ucmpsize ATTRIBUTE_PACKED; /* 18-21 */ + unsigned short filename_len; /* 22-23 */ + unsigned short extra_len; /* 24-25 */ + } formatted ATTRIBUTE_PACKED; +} zip_header_t; + +static void unzip_skip(int fd, off_t skip) +{ + if (lseek(fd, skip, SEEK_CUR) == (off_t)-1) { + if ((errno != ESPIPE) || (bb_copyfd_size(fd, -1, skip) != skip)) { + bb_error_msg_and_die("seek failure"); + } + } +} + +static void unzip_create_leading_dirs(char *fn) +{ + /* Create all leading directories */ + char *name = xstrdup(fn); + if (bb_make_directory(dirname(name), 0777, FILEUTILS_RECUR)) { + bb_error_msg_and_die("exiting"); /* bb_make_directory is noisy */ + } + free(name); +} + +static int unzip_extract(zip_header_t *zip_header, int src_fd, int dst_fd) +{ + if (zip_header->formatted.method == 0) { + /* Method 0 - stored (not compressed) */ + off_t size = zip_header->formatted.ucmpsize; + if (size && (bb_copyfd_size(src_fd, dst_fd, size) != size)) { + bb_error_msg_and_die("cannot complete extraction"); + } + + } else { + /* Method 8 - inflate */ + inflate_init(zip_header->formatted.cmpsize); + inflate_unzip(src_fd, dst_fd); + inflate_cleanup(); + /* Validate decompression - crc */ + if (zip_header->formatted.crc32 != (gunzip_crc ^ 0xffffffffL)) { + bb_error_msg("invalid compressed data--%s error", "crc"); + return 1; + } + /* Validate decompression - size */ + if (zip_header->formatted.ucmpsize != gunzip_bytes_out) { + bb_error_msg("invalid compressed data--%s error", "length"); + return 1; + } + } + return 0; +} + +int unzip_main(int argc, char **argv) +{ + zip_header_t zip_header; + enum {v_silent, v_normal, v_list} verbosity = v_normal; + enum {o_prompt, o_never, o_always} overwrite = o_prompt; + unsigned int total_size = 0; + unsigned int total_entries = 0; + int src_fd = -1, dst_fd = -1; + char *src_fn = NULL, *dst_fn = NULL; + llist_t *zaccept = NULL; + llist_t *zreject = NULL; + char *base_dir = NULL; + int failed, i, opt, opt_range = 0, list_header_done = 0; + char key_buf[512]; + struct stat stat_buf; + + while((opt = getopt(argc, argv, "-d:lnopqx")) != -1) { + switch (opt_range) { + case 0: /* Options */ + switch (opt) { + case 'l': /* List */ + verbosity = v_list; + break; + + case 'n': /* Never overwrite existing files */ + overwrite = o_never; + break; + + case 'o': /* Always overwrite existing files */ + overwrite = o_always; + break; + + case 'p': /* Extract files to stdout and fall through to set verbosity */ + dst_fd = STDOUT_FILENO; + + case 'q': /* Be quiet */ + verbosity = (verbosity == v_normal) ? v_silent : verbosity; + break; + + case 1 : /* The zip file */ + src_fn = xstrndup(optarg, strlen(optarg)+4); + opt_range++; + break; + + default: + bb_show_usage(); + + } + break; + + case 1: /* Include files */ + if (opt == 1) { + llist_add_to(&zaccept, optarg); + + } else if (opt == 'd') { + base_dir = optarg; + opt_range += 2; + + } else if (opt == 'x') { + opt_range++; + + } else { + bb_show_usage(); + } + break; + + case 2 : /* Exclude files */ + if (opt == 1) { + llist_add_to(&zreject, optarg); + + } else if (opt == 'd') { /* Extract to base directory */ + base_dir = optarg; + opt_range++; + + } else { + bb_show_usage(); + } + break; + + default: + bb_show_usage(); + } + } + + if (src_fn == NULL) { + bb_show_usage(); + } + + /* Open input file */ + if (strcmp("-", src_fn) == 0) { + src_fd = STDIN_FILENO; + /* Cannot use prompt mode since zip data is arriving on STDIN */ + overwrite = (overwrite == o_prompt) ? o_never : overwrite; + + } else { + static const char *const extn[] = {"", ".zip", ".ZIP"}; + int orig_src_fn_len = strlen(src_fn); + for(i = 0; (i < 3) && (src_fd == -1); i++) { + strcpy(src_fn + orig_src_fn_len, extn[i]); + src_fd = open(src_fn, O_RDONLY); + } + if (src_fd == -1) { + src_fn[orig_src_fn_len] = 0; + bb_error_msg_and_die("cannot open %s, %s.zip, %s.ZIP", src_fn, src_fn, src_fn); + } + } + + /* Change dir if necessary */ + if (base_dir) + xchdir(base_dir); + + if (verbosity != v_silent) + printf("Archive: %s\n", src_fn); + + failed = 0; + + while (1) { + unsigned int magic; + + /* Check magic number */ + xread(src_fd, &magic, 4); + if (magic == ZIP_CDS_MAGIC) { + break; + } else if (magic != ZIP_FILEHEADER_MAGIC) { + bb_error_msg_and_die("invalid zip magic %08X", magic); + } + + /* Read the file header */ + xread(src_fd, zip_header.raw, 26); + zip_header.formatted.version = SWAP_LE32(zip_header.formatted.version); + zip_header.formatted.flags = SWAP_LE32(zip_header.formatted.flags); + zip_header.formatted.method = SWAP_LE32(zip_header.formatted.method); + zip_header.formatted.modtime = SWAP_LE32(zip_header.formatted.modtime); + zip_header.formatted.moddate = SWAP_LE32(zip_header.formatted.moddate); + zip_header.formatted.crc32 = SWAP_LE32(zip_header.formatted.crc32); + zip_header.formatted.cmpsize = SWAP_LE32(zip_header.formatted.cmpsize); + zip_header.formatted.ucmpsize = SWAP_LE32(zip_header.formatted.ucmpsize); + zip_header.formatted.filename_len = SWAP_LE32(zip_header.formatted.filename_len); + zip_header.formatted.extra_len = SWAP_LE32(zip_header.formatted.extra_len); + if ((zip_header.formatted.method != 0) && (zip_header.formatted.method != 8)) { + bb_error_msg_and_die("unsupported compression method %d", zip_header.formatted.method); + } + + /* Read filename */ + free(dst_fn); + dst_fn = xzalloc(zip_header.formatted.filename_len + 1); + xread(src_fd, dst_fn, zip_header.formatted.filename_len); + + /* Skip extra header bytes */ + unzip_skip(src_fd, zip_header.formatted.extra_len); + + if ((verbosity == v_list) && !list_header_done){ + printf(" Length Date Time Name\n" + " -------- ---- ---- ----\n"); + list_header_done = 1; + } + + /* Filter zip entries */ + if (find_list_entry(zreject, dst_fn) || + (zaccept && !find_list_entry(zaccept, dst_fn))) { /* Skip entry */ + i = 'n'; + + } else { /* Extract entry */ + total_size += zip_header.formatted.ucmpsize; + + if (verbosity == v_list) { /* List entry */ + unsigned int dostime = zip_header.formatted.modtime | (zip_header.formatted.moddate << 16); + printf("%9u %02u-%02u-%02u %02u:%02u %s\n", + zip_header.formatted.ucmpsize, + (dostime & 0x01e00000) >> 21, + (dostime & 0x001f0000) >> 16, + (((dostime & 0xfe000000) >> 25) + 1980) % 100, + (dostime & 0x0000f800) >> 11, + (dostime & 0x000007e0) >> 5, + dst_fn); + total_entries++; + i = 'n'; + + } else if (dst_fd == STDOUT_FILENO) { /* Extracting to STDOUT */ + i = -1; + + } else if (last_char_is(dst_fn, '/')) { /* Extract directory */ + if (stat(dst_fn, &stat_buf) == -1) { + if (errno != ENOENT) { + bb_perror_msg_and_die("cannot stat '%s'",dst_fn); + } + if (verbosity == v_normal) { + printf(" creating: %s\n", dst_fn); + } + unzip_create_leading_dirs(dst_fn); + if (bb_make_directory(dst_fn, 0777, 0)) { + bb_error_msg_and_die("exiting"); + } + } else { + if (!S_ISDIR(stat_buf.st_mode)) { + bb_error_msg_and_die("'%s' exists but is not directory", dst_fn); + } + } + i = 'n'; + + } else { /* Extract file */ + _check_file: + if (stat(dst_fn, &stat_buf) == -1) { /* File does not exist */ + if (errno != ENOENT) { + bb_perror_msg_and_die("cannot stat '%s'",dst_fn); + } + i = 'y'; + + } else { /* File already exists */ + if (overwrite == o_never) { + i = 'n'; + + } else if (S_ISREG(stat_buf.st_mode)) { /* File is regular file */ + if (overwrite == o_always) { + i = 'y'; + } else { + printf("replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", dst_fn); + if (!fgets(key_buf, 512, stdin)) { + bb_perror_msg_and_die("cannot read input"); + } + i = key_buf[0]; + } + + } else { /* File is not regular file */ + bb_error_msg_and_die("'%s' exists but is not regular file",dst_fn); + } + } + } + } + + switch (i) { + case 'A': + overwrite = o_always; + case 'y': /* Open file and fall into unzip */ + unzip_create_leading_dirs(dst_fn); + dst_fd = xopen(dst_fn, O_WRONLY | O_CREAT | O_TRUNC); + case -1: /* Unzip */ + if (verbosity == v_normal) { + printf(" inflating: %s\n", dst_fn); + } + if (unzip_extract(&zip_header, src_fd, dst_fd)) { + failed = 1; + } + if (dst_fd != STDOUT_FILENO) { + /* closing STDOUT is potentially bad for future business */ + close(dst_fd); + } + break; + + case 'N': + overwrite = o_never; + case 'n': + /* Skip entry data */ + unzip_skip(src_fd, zip_header.formatted.cmpsize); + break; + + case 'r': + /* Prompt for new name */ + printf("new name: "); + if (!fgets(key_buf, 512, stdin)) { + bb_perror_msg_and_die("cannot read input"); + } + free(dst_fn); + dst_fn = xstrdup(key_buf); + chomp(dst_fn); + goto _check_file; + + default: + printf("error: invalid response [%c]\n",(char)i); + goto _check_file; + } + + /* Data descriptor section */ + if (zip_header.formatted.flags & 4) { + /* skip over duplicate crc, compressed size and uncompressed size */ + unzip_skip(src_fd, 12); + } + } + + if (verbosity == v_list) { + printf(" -------- -------\n" + "%9d %d files\n", total_size, total_entries); + } + + return failed; +} diff --git a/console-tools/Config.in b/console-tools/Config.in new file mode 100644 index 000000000..4a5710de6 --- /dev/null +++ b/console-tools/Config.in @@ -0,0 +1,105 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Console Utilities" + +config CHVT + bool "chvt" + default n + help + This program is used to change to another terminal. + Example: chvt 4 (change to terminal /dev/tty4) + +config CLEAR + bool "clear" + default n + help + This program clears the terminal screen. + +config DEALLOCVT + bool "deallocvt" + default n + help + This program deallocates unused virtual consoles. + +config DUMPKMAP + bool "dumpkmap" + default n + help + This program dumps the kernel's keyboard translation table to + stdout, in binary format. You can then use loadkmap to load it. + +config LOADFONT + bool "loadfont" + default n + help + This program loads a console font from standard input. + +config LOADKMAP + bool "loadkmap" + default n + help + This program loads a keyboard translation table from + standard input. + +config OPENVT + bool "openvt" + default n + help + This program is used to start a command on an unused + virtual terminal. + +config RESET + bool "reset" + default n + help + This program is used to reset the terminal screen, if it + gets messed up. + +config RESIZE + bool "resize" + default n + help + This program is used to (re)set the width and height of your current + terminal. + +config FEATURE_RESIZE_PRINT + bool "print environment variables" + default n + depends on RESIZE + help + Prints the newly set size (number of columns and rows) of + the terminal. + E.g.: + COLUMNS=80;LINES=44;export COLUMNS LINES; + +config SETCONSOLE + bool "setconsole" + default n + help + This program redirects the system console to another device, + like the current tty while logged in via telnet. + +config FEATURE_SETCONSOLE_LONG_OPTIONS + bool "Enable long options" + default n + depends on SET_CONSOLE && GETOPT_LONG + help + Support long options for the setconsole applet. + +config SETKEYCODES + bool "setkeycodes" + default n + help + This program loads entries into the kernel's scancode-to-keycode + map, allowing unusual keyboards to generate usable keycodes. + +config SETLOGCONS + bool "setlogcons" + default n + help + This program redirects the output console of kernel messages. + +endmenu diff --git a/console-tools/Kbuild b/console-tools/Kbuild new file mode 100644 index 000000000..a55bc087c --- /dev/null +++ b/console-tools/Kbuild @@ -0,0 +1,19 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_CHVT) += chvt.o +lib-$(CONFIG_CLEAR) += clear.o +lib-$(CONFIG_DEALLOCVT) += deallocvt.o +lib-$(CONFIG_DUMPKMAP) += dumpkmap.o +lib-$(CONFIG_SETCONSOLE) += setconsole.o +lib-$(CONFIG_LOADFONT) += loadfont.o +lib-$(CONFIG_LOADKMAP) += loadkmap.o +lib-$(CONFIG_OPENVT) += openvt.o +lib-$(CONFIG_RESET) += reset.o +lib-$(CONFIG_RESIZE) += resize.o +lib-$(CONFIG_SETKEYCODES) += setkeycodes.o +lib-$(CONFIG_SETLOGCONS) += setlogcons.o diff --git a/console-tools/chvt.c b/console-tools/chvt.c new file mode 100644 index 000000000..bbc5a9c31 --- /dev/null +++ b/console-tools/chvt.c @@ -0,0 +1,33 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini chvt implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* From */ +enum { + VT_ACTIVATE = 0x5606, /* make vt active */ + VT_WAITACTIVE = 0x5607 /* wait for vt active */ +}; + +int chvt_main(int argc, char **argv) +{ + int fd, num; + + if (argc != 2) { + bb_show_usage(); + } + + fd = get_console_fd(); + num = xatoul_range(argv[1], 1, 63); + if ((-1 == ioctl(fd, VT_ACTIVATE, num)) + || (-1 == ioctl(fd, VT_WAITACTIVE, num))) { + bb_perror_msg_and_die("ioctl"); + } + return EXIT_SUCCESS; +} diff --git a/console-tools/clear.c b/console-tools/clear.c new file mode 100644 index 000000000..9686d5004 --- /dev/null +++ b/console-tools/clear.c @@ -0,0 +1,21 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini clear implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +/* no options, no getopt */ + +#include +#include +#include "busybox.h" + + +int clear_main(int argc, char **argv) +{ + return printf("\033[H\033[J") != 6; +} diff --git a/console-tools/deallocvt.c b/console-tools/deallocvt.c new file mode 100644 index 000000000..cd581b1c8 --- /dev/null +++ b/console-tools/deallocvt.c @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* + * Disallocate virtual terminal(s) + * + * Copyright (C) 2003 by Tito Ragusa + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* no options, no getopt */ + +#include "busybox.h" + +/* From */ +enum { VT_DISALLOCATE = 0x5608 }; /* free memory associated to vt */ + +int deallocvt_main(int argc, char *argv[]) +{ + /* num = 0 deallocate all unused consoles */ + int num = 0; + + switch (argc) { + case 2: + num = xatoul_range(argv[1], 1, 63); + /* Fallthrough */ + case 1: + break; + default: + bb_show_usage(); + } + + if (-1 == ioctl(get_console_fd(), VT_DISALLOCATE, num)) { + bb_perror_msg_and_die("VT_DISALLOCATE"); + } + return EXIT_SUCCESS; +} diff --git a/console-tools/dumpkmap.c b/console-tools/dumpkmap.c new file mode 100644 index 000000000..7c6633ae8 --- /dev/null +++ b/console-tools/dumpkmap.c @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini dumpkmap implementation for busybox + * + * Copyright (C) Arne Bernin + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "busybox.h" + +/* From */ +struct kbentry { + unsigned char kb_table; + unsigned char kb_index; + unsigned short kb_value; +}; +#define KDGKBENT 0x4B46 /* gets one entry in translation table */ + +/* From */ +#define NR_KEYS 128 +#define MAX_NR_KEYMAPS 256 + +int dumpkmap_main(int argc, char **argv) +{ + struct kbentry ke; + int i, j, fd; + char flags[MAX_NR_KEYMAPS], magic[] = "bkeymap"; + + if (argc >= 2 && *argv[1] == '-') + bb_show_usage(); + + fd = xopen(CURRENT_VC, O_RDWR); + + write(1, magic, 7); + + /* Here we want to set everything to 0 except for indexes: + * [0-2] [4-6] [8-10] [12] */ + memset(flags, 0x00, MAX_NR_KEYMAPS); + memset(flags, 0x01, 13); + flags[3] = flags[7] = flags[11] = 0; + + /* dump flags */ + write(1, flags, MAX_NR_KEYMAPS); + + for (i = 0; i < MAX_NR_KEYMAPS; i++) { + if (flags[i] == 1) { + for (j = 0; j < NR_KEYS; j++) { + ke.kb_index = j; + ke.kb_table = i; + if (ioctl(fd, KDGKBENT, &ke) < 0) { + bb_perror_msg("ioctl failed with %s, %s, %p", + (char *)&ke.kb_index, + (char *)&ke.kb_table, + &ke.kb_value); + } else { + write(1, (void*)&ke.kb_value, 2); + } + } + } + } + close(fd); + return EXIT_SUCCESS; +} diff --git a/console-tools/loadfont.c b/console-tools/loadfont.c new file mode 100644 index 000000000..5a05876c2 --- /dev/null +++ b/console-tools/loadfont.c @@ -0,0 +1,193 @@ +/* vi: set sw=4 ts=4: */ +/* + * loadfont.c - Eugene Crosser & Andries Brouwer + * + * Version 0.96bb + * + * Loads the console font, and possibly the corresponding screen map(s). + * (Adapted for busybox by Matej Vela.) + */ +#include "busybox.h" +#include + +enum { + PSF_MAGIC1 = 0x36, + PSF_MAGIC2 = 0x04, + + PSF_MODE512 = 0x01, + PSF_MODEHASTAB = 0x02, + PSF_MAXMODE = 0x03, + PSF_SEPARATOR = 0xFFFF +}; + +struct psf_header { + unsigned char magic1, magic2; /* Magic number */ + unsigned char mode; /* PSF font mode */ + unsigned char charsize; /* Character size */ +}; + +#define PSF_MAGIC_OK(x) ((x).magic1 == PSF_MAGIC1 && (x).magic2 == PSF_MAGIC2) + +static void loadnewfont(int fd); + +int loadfont_main(int argc, char **argv) +{ + int fd; + + if (argc != 1) + bb_show_usage(); + + fd = xopen(CURRENT_VC, O_RDWR); + loadnewfont(fd); + + return EXIT_SUCCESS; +} + +static void do_loadfont(int fd, unsigned char *inbuf, int unit, int fontsize) +{ + char buf[16384]; + int i; + + memset(buf, 0, sizeof(buf)); + + if (unit < 1 || unit > 32) + bb_error_msg_and_die("bad character size %d", unit); + + for (i = 0; i < fontsize; i++) + memcpy(buf + (32 * i), inbuf + (unit * i), unit); + +#if defined( PIO_FONTX ) && !defined( __sparc__ ) + { + struct consolefontdesc cfd; + + cfd.charcount = fontsize; + cfd.charheight = unit; + cfd.chardata = buf; + + if (ioctl(fd, PIO_FONTX, &cfd) == 0) + return; /* success */ + bb_perror_msg("PIO_FONTX ioctl error (trying PIO_FONT)"); + } +#endif + if (ioctl(fd, PIO_FONT, buf)) + bb_perror_msg_and_die("PIO_FONT ioctl error"); +} + +static void +do_loadtable(int fd, unsigned char *inbuf, int tailsz, int fontsize) +{ + struct unimapinit advice; + struct unimapdesc ud; + struct unipair *up; + int ct = 0, maxct; + int glyph; + u_short unicode; + + maxct = tailsz; /* more than enough */ + up = (struct unipair *) xmalloc(maxct * sizeof(struct unipair)); + + for (glyph = 0; glyph < fontsize; glyph++) { + while (tailsz >= 2) { + unicode = (((u_short) inbuf[1]) << 8) + inbuf[0]; + tailsz -= 2; + inbuf += 2; + if (unicode == PSF_SEPARATOR) + break; + up[ct].unicode = unicode; + up[ct].fontpos = glyph; + ct++; + } + } + + /* Note: after PIO_UNIMAPCLR and before PIO_UNIMAP + this printf did not work on many kernels */ + + advice.advised_hashsize = 0; + advice.advised_hashstep = 0; + advice.advised_hashlevel = 0; + if (ioctl(fd, PIO_UNIMAPCLR, &advice)) { +#ifdef ENOIOCTLCMD + if (errno == ENOIOCTLCMD) { + bb_error_msg("it seems this kernel is older than 1.1.92"); + bb_error_msg_and_die("no Unicode mapping table loaded"); + } else +#endif + bb_perror_msg_and_die("PIO_UNIMAPCLR"); + } + ud.entry_ct = ct; + ud.entries = up; + if (ioctl(fd, PIO_UNIMAP, &ud)) { + bb_perror_msg_and_die("PIO_UNIMAP"); + } +} + +static void loadnewfont(int fd) +{ + int unit; + unsigned char inbuf[32768]; /* primitive */ + unsigned int inputlth, offset; + + /* + * We used to look at the length of the input file + * with stat(); now that we accept compressed files, + * just read the entire file. + */ + inputlth = fread(inbuf, 1, sizeof(inbuf), stdin); + if (ferror(stdin)) + bb_perror_msg_and_die("error reading input font"); + /* use malloc/realloc in case of giant files; + maybe these do not occur: 16kB for the font, + and 16kB for the map leaves 32 unicode values + for each font position */ + if (!feof(stdin)) + bb_perror_msg_and_die("font too large"); + + /* test for psf first */ + { + struct psf_header psfhdr; + int fontsize; + int hastable; + unsigned int head0, head; + + if (inputlth < sizeof(struct psf_header)) + goto no_psf; + + psfhdr = *(struct psf_header *) &inbuf[0]; + + if (!PSF_MAGIC_OK(psfhdr)) + goto no_psf; + + if (psfhdr.mode > PSF_MAXMODE) + bb_error_msg_and_die("unsupported psf file mode"); + fontsize = ((psfhdr.mode & PSF_MODE512) ? 512 : 256); +#if !defined( PIO_FONTX ) || defined( __sparc__ ) + if (fontsize != 256) + bb_error_msg_and_die("only fontsize 256 supported"); +#endif + hastable = (psfhdr.mode & PSF_MODEHASTAB); + unit = psfhdr.charsize; + head0 = sizeof(struct psf_header); + + head = head0 + fontsize * unit; + if (head > inputlth || (!hastable && head != inputlth)) + bb_error_msg_and_die("input file: bad length"); + do_loadfont(fd, inbuf + head0, unit, fontsize); + if (hastable) + do_loadtable(fd, inbuf + head, inputlth - head, fontsize); + return; + } + no_psf: + + /* file with three code pages? */ + if (inputlth == 9780) { + offset = 40; + unit = 16; + } else { + /* bare font */ + if (inputlth & 0377) + bb_error_msg_and_die("bad input file size"); + offset = 0; + unit = inputlth / 256; + } + do_loadfont(fd, inbuf + offset, unit, 256); +} diff --git a/console-tools/loadkmap.c b/console-tools/loadkmap.c new file mode 100644 index 000000000..ce9b6817c --- /dev/null +++ b/console-tools/loadkmap.c @@ -0,0 +1,61 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini loadkmap implementation for busybox + * + * Copyright (C) 1998 Enrique Zanardi + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "busybox.h" + +#define BINARY_KEYMAP_MAGIC "bkeymap" + +/* From */ +struct kbentry { + unsigned char kb_table; + unsigned char kb_index; + unsigned short kb_value; +}; +/* sets one entry in translation table */ +#define KDSKBENT 0x4B47 + +/* From */ +#define NR_KEYS 128 +#define MAX_NR_KEYMAPS 256 + +int loadkmap_main(int argc, char **argv) +{ + struct kbentry ke; + int i, j, fd; + u_short ibuff[NR_KEYS]; + char flags[MAX_NR_KEYMAPS]; + char buff[7]; + + if (argc != 1) + bb_show_usage(); + + fd = xopen(CURRENT_VC, O_RDWR); + + xread(0, buff, 7); + if (strncmp(buff, BINARY_KEYMAP_MAGIC, 7)) + bb_error_msg_and_die("this is not a valid binary keymap"); + + xread(0, flags, MAX_NR_KEYMAPS); + + for (i = 0; i < MAX_NR_KEYMAPS; i++) { + if (flags[i] == 1) { + xread(0, ibuff, NR_KEYS * sizeof(u_short)); + for (j = 0; j < NR_KEYS; j++) { + ke.kb_index = j; + ke.kb_table = i; + ke.kb_value = ibuff[j]; + ioctl(fd, KDSKBENT, &ke); + } + } + } + + if (ENABLE_FEATURE_CLEAN_UP) close(fd); + return 0; +} diff --git a/console-tools/openvt.c b/console-tools/openvt.c new file mode 100644 index 000000000..f1cf5645b --- /dev/null +++ b/console-tools/openvt.c @@ -0,0 +1,45 @@ +/* vi: set sw=4 ts=4: */ +/* + * openvt.c - open a vt to run a command. + * + * busyboxed by Quy Tonthat + * hacked by Tito + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* getopt not needed */ + +#include "busybox.h" + +int openvt_main(int argc, char **argv) +{ + int fd; + char vtname[sizeof(VC_FORMAT) + 2]; + + + if (argc < 3) { + bb_show_usage(); + } + /* check for illegal vt number: < 1 or > 63 */ + sprintf(vtname, VC_FORMAT, (int)xatoul_range(argv[1], 1, 63)); + + if (fork() == 0) { + /* leave current vt */ + if (setsid() < 0) { + bb_perror_msg_and_die("setsid"); + } + close(0); /* so that new vt becomes stdin */ + + /* and grab new one */ + fd = xopen(vtname, O_RDWR); + + /* Reassign stdout and sterr */ + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + + execvp(argv[2], &argv[2]); + _exit(1); + } + return EXIT_SUCCESS; +} diff --git a/console-tools/reset.c b/console-tools/reset.c new file mode 100644 index 000000000..26aab667b --- /dev/null +++ b/console-tools/reset.c @@ -0,0 +1,32 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini reset implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * Written by Erik Andersen and Kent Robotti + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* no options, no getopt */ + +#include +#include +#include +#include "busybox.h" + +int reset_main(int argc, char **argv) +{ + if (isatty(1)) { + /* See 'man 4 console_codes' for details: + * "ESC c" -- Reset + * "ESC ( K" -- Select user mapping + * "ESC [ J" -- Erase display + * "ESC [ 0 m" -- Reset all display attributes + * "ESC [ ? 25 h" -- Make cursor visible. + */ + printf("\033c\033(K\033[J\033[0m\033[?25h"); + } + return EXIT_SUCCESS; +} + diff --git a/console-tools/resize.c b/console-tools/resize.c new file mode 100644 index 000000000..c405629aa --- /dev/null +++ b/console-tools/resize.c @@ -0,0 +1,38 @@ +/* vi: set sw=4 ts=4: */ +/* + * resize - set terminal width and height. + * + * Copyright 2006 Bernhard Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +/* no options, no getopt */ +#include "busybox.h" + +int resize_main(int argc, char **argv) +{ + struct termios old, new; + struct winsize w = {0,0,0,0}; + int ret; + + tcgetattr(STDOUT_FILENO, &old); /* fiddle echo */ + new = old; + new.c_cflag |= (CLOCAL | CREAD); + new.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + tcsetattr(STDOUT_FILENO, TCSANOW, &new); + /* save_cursor_pos 7 + * scroll_whole_screen [r + * put_cursor_waaaay_off [$x;$yH + * get_cursor_pos [6n + * restore_cursor_pos 8 + */ + printf("\0337\033[r\033[999;999H\033[6n"); + scanf("\033[%hu;%huR", &w.ws_row, &w.ws_col); + ret = ioctl(STDOUT_FILENO, TIOCSWINSZ, &w); + printf("\0338"); + tcsetattr(STDOUT_FILENO, TCSANOW, &old); + if (ENABLE_FEATURE_RESIZE_PRINT) + printf("COLUMNS=%d;LINES=%d;export COLUMNS LINES;\n", + w.ws_col, w.ws_row); + return ret; +} diff --git a/console-tools/setconsole.c b/console-tools/setconsole.c new file mode 100644 index 000000000..ef81f298b --- /dev/null +++ b/console-tools/setconsole.c @@ -0,0 +1,47 @@ +/* vi: set sw=4 ts=4: */ +/* + * setconsole.c - redirect system console output + * + * Copyright (C) 2004,2005 Enrik Berkhan + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +#if ENABLE_FEATURE_SETCONSOLE_LONG_OPTIONS +static const struct option setconsole_long_options[] = { + { "reset", 0, NULL, 'r' }, + { 0, 0, 0, 0 } +}; +#endif + +#define OPT_SETCONS_RESET 1 + +int setconsole_main(int argc, char **argv) +{ + unsigned long flags; + const char *device = CURRENT_TTY; + +#if ENABLE_FEATURE_SETCONSOLE_LONG_OPTIONS + applet_long_options = setconsole_long_options; +#endif + flags = getopt32(argc, argv, "r"); + + if (argc - optind > 1) + bb_show_usage(); + + if (argc - optind == 1) { + if (flags & OPT_SETCONS_RESET) + bb_show_usage(); + device = argv[optind]; + } else { + if (flags & OPT_SETCONS_RESET) + device = CONSOLE_DEV; + } + + if (-1 == ioctl(xopen(device, O_RDONLY), TIOCCONS)) { + bb_perror_msg_and_die("TIOCCONS"); + } + return EXIT_SUCCESS; +} diff --git a/console-tools/setkeycodes.c b/console-tools/setkeycodes.c new file mode 100644 index 000000000..d82525786 --- /dev/null +++ b/console-tools/setkeycodes.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * setkeycodes + * + * Copyright (C) 1994-1998 Andries E. Brouwer + * + * Adjusted for BusyBox by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "busybox.h" + + +/* From */ +struct kbkeycode { + unsigned int scancode, keycode; +}; +enum { + KDSETKEYCODE = 0x4B4D /* write kernel keycode table entry */ +}; + +extern int +setkeycodes_main(int argc, char** argv) +{ + int fd, sc; + struct kbkeycode a; + + if (argc % 2 != 1 || argc < 2) { + bb_show_usage(); + } + + fd = get_console_fd(); + + while (argc > 2) { + a.keycode = xatoul_range(argv[2], 0, 127); + a.scancode = sc = xstrtoul_range(argv[1], 16, 0, 255); + if (a.scancode > 127) { + a.scancode -= 0xe000; + a.scancode += 128; + } + if (ioctl(fd, KDSETKEYCODE, &a)) { + bb_perror_msg_and_die("failed to set SCANCODE %x to KEYCODE %d", sc, a.keycode); + } + argc -= 2; + argv += 2; + } + return EXIT_SUCCESS; +} diff --git a/console-tools/setlogcons.c b/console-tools/setlogcons.c new file mode 100644 index 000000000..ae15b9b28 --- /dev/null +++ b/console-tools/setlogcons.c @@ -0,0 +1,31 @@ +/* vi: set sw=4 ts=4: */ +/* + * setlogcons: Send kernel messages to the current console or to console N + * + * Copyright (C) 2006 by Jan Kiszka + * + * Based on setlogcons (kbd-1.12) by Andries E. Brouwer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +extern int setlogcons_main(int argc, char **argv) +{ + struct { + char fn; + char subarg; + } arg; + + arg.fn = 11; /* redirect kernel messages */ + arg.subarg = 0; /* to specified console (current as default) */ + + if (argc == 2) + arg.subarg = xatoul_range(argv[1], 0, 63); + + if (ioctl(xopen(VC_1, O_RDONLY), TIOCLINUX, &arg)) + bb_perror_msg_and_die("TIOCLINUX"); + + return 0; +} diff --git a/coreutils/Config.in b/coreutils/Config.in new file mode 100644 index 000000000..000f3a8af --- /dev/null +++ b/coreutils/Config.in @@ -0,0 +1,782 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Coreutils" + +config BASENAME + bool "basename" + default n + help + basename is used to strip the directory and suffix from filenames, + leaving just the filename itself. Enable this option if you wish + to enable the 'basename' utility. + +config CAL + bool "cal" + default n + help + cal is used to display a monthly calender. + +config CAT + bool "cat" + default n + help + cat is used to concatenate files and print them to the standard + output. Enable this option if you wish to enable the 'cat' utility. + +config CATV + bool "catv" + default n + help + Display nonprinting characters as escape sequences (like some + implementations' cat -v option). + +config CHGRP + bool "chgrp" + default n + help + chgrp is used to change the group ownership of files. + +config CHMOD + bool "chmod" + default n + help + chmod is used to change the access permission of files. + +config CHOWN + bool "chown" + default n + help + chown is used to change the user and/or group ownership + of files. + +config CHROOT + bool "chroot" + default n + help + chroot is used to change the root directory and run a command. + The default command is `/bin/sh'. + +config CKSUM + bool "cksum" + default n + help + cksum is used to calculate the CRC32 checksum of a file. + +config CMP + bool "cmp" + default n + help + cmp is used to compare two files and returns the result + to standard output. + +config COMM + bool "comm" + default n + help + comm is used to compare two files line by line and return + a three-column output. + +config CP + bool "cp" + default n + help + cp is used to copy files and directories. + +config CUT + bool "cut" + default n + help + cut is used to print selected parts of lines from + each file to stdout. + +config DATE + bool "date" + default n + help + date is used to set the system date or display the + current time in the given format. + +config FEATURE_DATE_ISOFMT + bool "Enable ISO date format output (-I)" + default y + depends on DATE + help + Enable option (-I) to output an ISO-8601 compliant + date/time string. + +config DD + bool "dd" + default n + help + dd copies a file (from standard input to standard output, + by default) using specific input and output blocksizes, + while optionally performing conversions on it. + +config FEATURE_DD_SIGNAL_HANDLING + bool "Enable DD signal handling for status reporting" + default y + depends on DD + help + sending a SIGUSR1 signal to a running `dd' process makes it + print to standard error the number of records read and written + so far, then to resume copying. + + $ dd if=/dev/zero of=/dev/null& pid=$! $ kill -USR1 $pid; sleep 1; kill $pid + 10899206+0 records in 10899206+0 records out + +config FEATURE_DD_IBS_OBS + bool "Enable ibs, obs and conv options" + default n + depends on DD + help + Enables support for writing a certain number of bytes in and out, + at a time, and performing conversions on the data stream. + +config DF + bool "df" + default n + help + df reports the amount of disk space used and available + on filesystems. + +config DIFF + bool "diff" + default n + help + diff compares two files or directories and outputs the + differences between them in a form that can be given to + the patch command. + +config FEATURE_DIFF_BINARY + bool "Enable checks for binary files" + default y + depends on DIFF + help + This option enables support for checking for binary files + before a comparison is carried out. + +config FEATURE_DIFF_DIR + bool "Enable directory support" + default y + depends on DIFF + help + This option enables support for directory and subdirectory + comparison. + +config FEATURE_DIFF_MINIMAL + bool "Enable -d option to find smaller sets of changes" + default n + depends on DIFF + help + Enabling this option allows the use of -d to make diff + try hard to find the smallest possible set of changes. + +config DIRNAME + bool "dirname" + default n + help + dirname is used to strip a non-directory suffix from + a file name. + +config DOS2UNIX + bool "dos2unix/unix2dos" + default n + help + dos2unix is used to convert a text file from DOS format to + UNIX format, and vice versa. + +config UNIX2DOS + bool + default y + depends on DOS2UNIX + help + unix2dos is used to convert a text file from UNIX format to + DOS format, and vice versa. + +config DU + bool "du (default blocksize of 512 bytes)" + default n + help + du is used to report the amount of disk space used + for specified files. + +config FEATURE_DU_DEFAULT_BLOCKSIZE_1K + bool "Use a default blocksize of 1024 bytes (1K)" + default y + depends on DU + help + Use a blocksize of (1K) instead of the default 512b. + +config ECHO + bool "echo (basic SuSv3 version taking no options)" + default n + help + echo is used to print a specified string to stdout. + +# this entry also appears in shell/Config.in, next to the echo builtin +config FEATURE_FANCY_ECHO + bool "Enable echo options (-n and -e)" + default y + depends on ECHO + help + This adds options (-n and -e) to echo. + +config ENV + bool "env" + default n + help + env is used to set an environment variable and run + a command; without options it displays the current + environment. + +config FEATURE_ENV_LONG_OPTIONS + bool "Enable long options" + default n + depends on ENV && GETOPT_LONG + help + Support long options for the env applet. + +config EXPR + bool "expr" + default n + help + expr is used to calculate numbers and print the result + to standard output. + +config EXPR_MATH_SUPPORT_64 + bool "Extend Posix numbers support to 64 bit" + default n + depends on EXPR + help + Enable 64-bit math support in the expr applet. This will make + the applet slightly larger, but will allow computation with very + large numbers. + +config FALSE + bool "false" + default n + help + false returns an exit code of FALSE (1). + +config FOLD + bool "fold" + default n + help + Wrap text to fit a specific width. + +config HEAD + bool "head" + default n + help + head is used to print the first specified number of lines + from files. + +config FEATURE_FANCY_HEAD + bool "Enable head options (-c, -q, and -v)" + default n + depends on HEAD + help + This enables the head options (-c, -q, and -v). + +config HOSTID + bool "hostid" + default n + help + hostid prints the numeric identifier (in hexadecimal) for + the current host. + +config ID + bool "id" + default n + help + id displays the current user and group ID names. + +config INSTALL + bool "install" + default n + help + Copy files and set attributes. + +config FEATURE_INSTALL_LONG_OPTIONS + bool "Enable long options" + default n + depends on INSTALL && GETOPT_LONG + help + Support long options for the install applet. + +config LENGTH + bool "length" + default n + help + length is used to print out the length of a specified string. + +config LN + bool "ln" + default n + help + ln is used to create hard or soft links between files. + +config LOGNAME + bool "logname" + default n + help + logname is used to print the current user's login name. + +config LS + bool "ls" + default n + help + ls is used to list the contents of directories. + +config FEATURE_LS_FILETYPES + bool "Enable filetyping options (-p and -F)" + default y + depends on LS + help + Enable the ls options (-p and -F). + +config FEATURE_LS_FOLLOWLINKS + bool "Enable symlinks dereferencing (-L)" + default y + depends on LS + help + Enable the ls option (-L). + +config FEATURE_LS_RECURSIVE + bool "Enable recursion (-R)" + default y + depends on LS + help + Enable the ls option (-R). + +config FEATURE_LS_SORTFILES + bool "Sort the file names" + default y + depends on LS + help + Allow ls to sort file names alphabetically. + +config FEATURE_LS_TIMESTAMPS + bool "Show file timestamps" + default y + depends on LS + help + Allow ls to display timestamps for files. + +config FEATURE_LS_USERNAME + bool "Show username/groupnames" + default y + depends on LS + help + Allow ls to display username/groupname for files. + +config FEATURE_LS_COLOR + bool "Allow use of color to identify file types" + default y + depends on LS && GETOPT_LONG + help + This enables the --color option to ls. + +config FEATURE_LS_COLOR_IS_DEFAULT + bool "Produce colored ls output by default" + default n + depends on FEATURE_LS_COLOR + help + Saying yes here will turn coloring on by default, + even if no "--color" option is given to the ls command. + This is not recommended, since the colors are not + configurable, and the output may not be legible on + many output screens. + +config MD5SUM + bool "md5sum" + default n + help + md5sum is used to print or check MD5 checksums. + +config MKDIR + bool "mkdir" + default n + help + mkdir is used to create directories with the specified names. + +config FEATURE_MKDIR_LONG_OPTIONS + bool "Enable long options" + default n + depends on MKDIR && GETOPT_LONG + help + Support long options for the mkdir applet. + +config MKFIFO + bool "mkfifo" + default n + help + mkfifo is used to create FIFOs (named pipes). + The `mknod' program can also create FIFOs. + +config MKNOD + bool "mknod" + default n + help + mknod is used to create FIFOs or block/character special + files with the specified names. + +config MV + bool "mv" + default n + help + mv is used to move or rename files or directories. + +config FEATURE_MV_LONG_OPTIONS + bool "Enable long options" + default n + depends on MV && GETOPT_LONG + help + Support long options for the mv applet. + +config NICE + bool "nice" + default n + help + nice runs a program with modified scheduling priority. + +config NOHUP + bool "nohup" + default n + help + run a command immune to hangups, with output to a non-tty. + +config OD + bool "od" + default n + help + od is used to dump binary files in octal and other formats. + +config PRINTENV + bool "printenv" + default n + help + printenv is used to print all or part of environment. + +config PRINTF + bool "printf" + default n + help + printf is used to format and print specified strings. + It's similar to `echo' except it has more options. + +config PWD + bool "pwd" + default n + help + pwd is used to print the current directory. + +config REALPATH + bool "realpath" + default n + help + Return the canonicalized absolute pathname. + This isn't provided by GNU shellutils, but where else does it belong. + +config RM + bool "rm" + default n + help + rm is used to remove files or directories. + +config RMDIR + bool "rmdir" + default n + help + rmdir is used to remove empty directories. + +config SEQ + bool "seq" + default n + help + print a sequence of numbers + +config SHA1SUM + bool "sha1sum" + default n + help + Compute and check SHA1 message digest + +config SLEEP + bool "sleep (single integer arg with no suffix)" + default n + help + sleep is used to pause for a specified number of seconds, + +config FEATURE_FANCY_SLEEP + bool "Enable multiple integer args and optional time suffixes" + default n + depends on SLEEP + help + Allow sleep to pause for specified minutes, hours, and days. + +config SORT + bool "sort" + default n + help + sort is used to sort lines of text in specified files. + +config FEATURE_SORT_BIG + bool "full SuSv3 compliant sort (Support -ktcsbdfiozgM)" + default y + depends on SORT + help + Without this, sort only supports -r, -u, and an integer version + of -n. Selecting this adds sort keys, floating point support, and + more. This adds a little over 3k to a nonstatic build on x86. + + The SuSv3 sort standard is available at: + http://www.opengroup.org/onlinepubs/007904975/utilities/sort.html + +config STAT + bool "stat" + default n + help + display file or filesystem status. + +config FEATURE_STAT_FORMAT + bool "Enable custom formats (-c)" + default n + depends on STAT + help + Without this, stat will not support the '-c format' option where + users can pass a custom format string for output. This adds about + 7k to a nonstatic build on amd64. + +config STTY + bool "stty" + default n + help + stty is used to change and print terminal line settings. + +config SUM + bool "sum" + default n + help + checksum and count the blocks in a file + +config SYNC + bool "sync" + default n + help + sync is used to flush filesystem buffers. + +config TAIL + bool "tail" + default n + help + tail is used to print the last specified number of lines + from files. + +config FEATURE_FANCY_TAIL + bool "Enable extra tail options (-q, -s, and -v)" + default y + depends on TAIL + help + The options (-q, -s, and -v) are provided by GNU tail, but + are not specific in the SUSv3 standard. + +config TEE + bool "tee" + default n + help + tee is used to read from standard input and write + to standard output and files. + +config FEATURE_TEE_USE_BLOCK_IO + bool "Enable block i/o (larger/faster) instead of byte i/o." + default n + depends on TEE + help + Enable this option for a faster tee, at expense of size. + +config TEST + bool "test" + default n + help + test is used to check file types and compare values, + returning an appropriate exit code. The bash shell + has test built in, ash can build it in optionally. + +config FEATURE_TEST_64 + bool "Extend test to 64 bit" + default n + depends on TEST + help + Enable 64-bit support in test. + +config TOUCH + bool "touch" + default n + help + touch is used to create or change the access and/or + modification timestamp of specified files. + +config TR + bool "tr" + default n + help + tr is used to squeeze, and/or delete characters from standard + input, writing to standard output. + +config FEATURE_TR_CLASSES + bool "Enable character classes (such as [:upper:])" + default n + depends on TR + help + Enable character classes, enabling commands such as: + tr [:upper:] [:lower:] to convert input into lowercase. + +config FEATURE_TR_EQUIV + bool "Enable equivalence classes" + default n + depends on TR + help + Enable equivalence classes, which essentially add the enclosed + character to the current set. For instance, tr [=a=] xyz would + replace all instances of 'a' with 'xyz'. This option is mainly + useful for cases when no other way of expressing a character + is possible. + +config TRUE + bool "true" + default n + help + true returns an exit code of TRUE (0). + +config TTY + bool "tty" + default n + help + tty is used to print the name of the current terminal to + standard output. + +config UNAME + bool "uname" + default n + help + uname is used to print system information. + +config UNIQ + bool "uniq" + default n + help + uniq is used to remove duplicate lines from a sorted file. + +config USLEEP + bool "usleep" + default n + help + usleep is used to pause for a specified number of microseconds. + +config UUDECODE + bool "uudecode" + default n + help + uudecode is used to decode a uuencoded file. + +config UUENCODE + bool "uuencode" + default n + help + uuencode is used to uuencode a file. + +config WATCH + bool "watch" + default n + select DATE + help + watch is used to execute a program periodically, showing + output to the screen. + +config WC + bool "wc" + default n + help + wc is used to print the number of bytes, words, and lines, + in specified files. + +config FEATURE_WC_LARGE + bool "Support very large files in wc" + default n + depends on WC + help + Use "unsigned long long" in wc for count variables + +config WHO + bool "who" + default n + select FEATURE_UTMP + help + who is used to show who is logged on. + +config WHOAMI + bool "whoami" + default n + help + whoami is used to print the username of the current + user id (same as id -un). + +config YES + bool "yes" + default n + help + yes is used to repeatedly output a specific string, or + the default string `y'. + +comment "Common options for cp and mv" + depends on CP || MV + +config FEATURE_PRESERVE_HARDLINKS + bool "Preserve hard links" + default n + depends on CP || MV + help + Allow cp and mv to preserve hard links. + +comment "Common options for ls, more and telnet" + depends on LS || MORE || TELNET + +config FEATURE_AUTOWIDTH + bool "Calculate terminal & column widths" + default y + depends on LS || MORE || TELNET + help + This option allows utilities such as 'ls', 'more' and 'telnet' + to determine the width of the screen, which can allow them to + display additional text or avoid wrapping text onto the next line. + If you leave this disabled, your utilities will be especially + primitive and will be unable to determine the current screen width. + +comment "Common options for df, du, ls" + depends on DF || DU || LS + +config FEATURE_HUMAN_READABLE + bool "Support for human readable output (example 13k, 23M, 235G)" + default n + depends on DF || DU || LS + help + Allow df, du, and ls to have human readable output. + +comment "Common options for md5sum, sha1sum" + depends on MD5SUM || SHA1SUM + +config FEATURE_MD5_SHA1_SUM_CHECK + bool "Enable -c, -s and -w options" + default n + depends on MD5SUM || SHA1SUM + help + Enabling the -c options allows files to be checked + against pre-calculated hash values. + + -s and -w are useful options when verifying checksums. + +endmenu diff --git a/coreutils/Kbuild b/coreutils/Kbuild new file mode 100644 index 000000000..cfb508d81 --- /dev/null +++ b/coreutils/Kbuild @@ -0,0 +1,81 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +libs-y += libcoreutils/ + +lib-y:= +lib-$(CONFIG_BASENAME) += basename.o +lib-$(CONFIG_CAL) += cal.o +lib-$(CONFIG_CAT) += cat.o +lib-$(CONFIG_CATV) += catv.o +lib-$(CONFIG_CHGRP) += chgrp.o chown.o +lib-$(CONFIG_CHMOD) += chmod.o +lib-$(CONFIG_CHOWN) += chown.o +lib-$(CONFIG_CHROOT) += chroot.o +lib-$(CONFIG_CKSUM) += cksum.o +lib-$(CONFIG_CMP) += cmp.o +lib-$(CONFIG_COMM) += comm.o +lib-$(CONFIG_CP) += cp.o +lib-$(CONFIG_CUT) += cut.o +lib-$(CONFIG_DATE) += date.o +lib-$(CONFIG_DD) += dd.o +lib-$(CONFIG_DF) += df.o +lib-$(CONFIG_DIFF) += diff.o +lib-$(CONFIG_DIRNAME) += dirname.o +lib-$(CONFIG_DOS2UNIX) += dos2unix.o +lib-$(CONFIG_DU) += du.o +lib-$(CONFIG_ECHO) += echo.o +lib-$(CONFIG_ENV) += env.o +lib-$(CONFIG_EXPR) += expr.o +lib-$(CONFIG_FALSE) += false.o +lib-$(CONFIG_FOLD) += fold.o +lib-$(CONFIG_HEAD) += head.o +lib-$(CONFIG_HOSTID) += hostid.o +lib-$(CONFIG_ID) += id.o +lib-$(CONFIG_INSTALL) += install.o +lib-$(CONFIG_LENGTH) += length.o +lib-$(CONFIG_LN) += ln.o +lib-$(CONFIG_LOGNAME) += logname.o +lib-$(CONFIG_LS) += ls.o +lib-$(CONFIG_MD5SUM) += md5_sha1_sum.o +lib-$(CONFIG_MKDIR) += mkdir.o +lib-$(CONFIG_MKFIFO) += mkfifo.o +lib-$(CONFIG_MKNOD) += mknod.o +lib-$(CONFIG_MV) += mv.o +lib-$(CONFIG_NICE) += nice.o +lib-$(CONFIG_NOHUP) += nohup.o +lib-$(CONFIG_OD) += od.o +lib-$(CONFIG_PRINTENV) += printenv.o +lib-$(CONFIG_PRINTF) += printf.o +lib-$(CONFIG_PWD) += pwd.o +lib-$(CONFIG_REALPATH) += realpath.o +lib-$(CONFIG_RM) += rm.o +lib-$(CONFIG_RMDIR) += rmdir.o +lib-$(CONFIG_SEQ) += seq.o +lib-$(CONFIG_SHA1SUM) += md5_sha1_sum.o +lib-$(CONFIG_SLEEP) += sleep.o +lib-$(CONFIG_SORT) += sort.o +lib-$(CONFIG_STAT) += stat.o +lib-$(CONFIG_STTY) += stty.o +lib-$(CONFIG_SUM) += sum.o +lib-$(CONFIG_SYNC) += sync.o +lib-$(CONFIG_TAIL) += tail.o +lib-$(CONFIG_TEE) += tee.o +lib-$(CONFIG_TEST) += test.o +lib-$(CONFIG_TOUCH) += touch.o +lib-$(CONFIG_TR) += tr.o +lib-$(CONFIG_TRUE) += true.o +lib-$(CONFIG_TTY) += tty.o +lib-$(CONFIG_UNAME) += uname.o +lib-$(CONFIG_UNIQ) += uniq.o +lib-$(CONFIG_USLEEP) += usleep.o +lib-$(CONFIG_UUDECODE) += uudecode.o +lib-$(CONFIG_UUENCODE) += uuencode.o +lib-$(CONFIG_WATCH) += watch.o +lib-$(CONFIG_WC) += wc.o +lib-$(CONFIG_WHO) += who.o +lib-$(CONFIG_WHOAMI) += whoami.o +lib-$(CONFIG_YES) += yes.o diff --git a/coreutils/basename.c b/coreutils/basename.c new file mode 100644 index 000000000..30f76dc12 --- /dev/null +++ b/coreutils/basename.c @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini basename implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/basename.html */ + + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Changes: + * 1) Now checks for too many args. Need at least one and at most two. + * 2) Don't check for options, as per SUSv3. + * 3) Save some space by using strcmp(). Calling strncmp() here was silly. + */ + +#include +#include +#include +#include "busybox.h" + +int basename_main(int argc, char **argv) +{ + size_t m, n; + char *s; + + if (((unsigned int)(argc-2)) >= 2) { + bb_show_usage(); + } + + s = bb_get_last_path_component(*++argv); + + if (*++argv) { + n = strlen(*argv); + m = strlen(s); + if ((m > n) && ((strcmp)(s+m-n, *argv) == 0)) { + s[m-n] = '\0'; + } + } + + puts(s); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/cal.c b/coreutils/cal.c new file mode 100644 index 000000000..681a88d90 --- /dev/null +++ b/coreutils/cal.c @@ -0,0 +1,356 @@ +/* vi: set sw=4 ts=4: */ +/* + * Calendar implementation for busybox + * + * See original copyright at the end of this file + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant with -j and -y extensions (from util-linux). */ +/* BB_AUDIT BUG: The output of 'cal -j 1752' is incorrect. The upstream + * BB_AUDIT BUG: version in util-linux seems to be broken as well. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/cal.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Major size reduction... over 50% (>1.5k) on i386. + */ + +#include "busybox.h" + +#define THURSDAY 4 /* for reformation */ +#define SATURDAY 6 /* 1 Jan 1 was a Saturday */ + +#define FIRST_MISSING_DAY 639787 /* 3 Sep 1752 */ +#define NUMBER_MISSING_DAYS 11 /* 11 day correction */ + +#define MAXDAYS 42 /* max slots in a month array */ +#define SPACE -1 /* used in day array */ + +static const char days_in_month[] = { + 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +static const char sep1752[] = { + 1, 2, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30 +}; + +static int julian; + +/* leap year -- account for Gregorian reformation in 1752 */ +#define leap_year(yr) \ + ((yr) <= 1752 ? !((yr) % 4) : \ + (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400)) + +static int is_leap_year(int year) +{ + return leap_year(year); +} +#undef leap_year +#define leap_year(yr) is_leap_year(yr) + +/* number of centuries since 1700, not inclusive */ +#define centuries_since_1700(yr) \ + ((yr) > 1700 ? (yr) / 100 - 17 : 0) + +/* number of centuries since 1700 whose modulo of 400 is 0 */ +#define quad_centuries_since_1700(yr) \ + ((yr) > 1600 ? ((yr) - 1600) / 400 : 0) + +/* number of leap years between year 1 and this year, not inclusive */ +#define leap_years_since_year_1(yr) \ + ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr)) + +static void center (char *, int, int); +static void day_array (int, int, int *); +static void trim_trailing_spaces_and_print (char *); + +static void blank_string(char *buf, size_t buflen); +static char *build_row(char *p, int *dp); + +#define DAY_LEN 3 /* 3 spaces per day */ +#define J_DAY_LEN (DAY_LEN + 1) +#define WEEK_LEN 20 /* 7 * 3 - one space at the end */ +#define J_WEEK_LEN (WEEK_LEN + 7) +#define HEAD_SEP 2 /* spaces between day headings */ + +int cal_main(int argc, char **argv) +{ + struct tm *local_time; + struct tm zero_tm; + time_t now; + int month, year, flags, i; + char *month_names[12]; + char day_headings[28]; /* 28 for julian, 21 for nonjulian */ + char buf[40]; + +// Done in busybox.c, ok to remove? +//#ifdef CONFIG_LOCALE_SUPPORT +// setlocale(LC_TIME, ""); +//#endif + + flags = getopt32(argc, argv, "jy"); + + julian = flags & 1; + + argv += optind; + + month = 0; + + if ((argc -= optind) > 2) { + bb_show_usage(); + } + + if (!argc) { + time(&now); + local_time = localtime(&now); + year = local_time->tm_year + 1900; + if (!(flags & 2)) { + month = local_time->tm_mon + 1; + } + } else { + if (argc == 2) { + month = xatoul_range(*argv++, 1, 12); + } + year = xatoul_range(*argv, 1, 9999); + } + + blank_string(day_headings, sizeof(day_headings) - 7 + 7*julian); + + i = 0; + do { + zero_tm.tm_mon = i; + strftime(buf, sizeof(buf), "%B", &zero_tm); + month_names[i] = xstrdup(buf); + + if (i < 7) { + zero_tm.tm_wday = i; + strftime(buf, sizeof(buf), "%a", &zero_tm); + strncpy(day_headings + i * (3+julian) + julian, buf, 2); + } + } while (++i < 12); + + if (month) { + int row, len, days[MAXDAYS]; + int *dp = days; + char lineout[30]; + + day_array(month, year, dp); + len = sprintf(lineout, "%s %d", month_names[month - 1], year); + printf("%*s%s\n%s\n", + ((7*julian + WEEK_LEN) - len) / 2, "", + lineout, day_headings); + for (row = 0; row < 6; row++) { + build_row(lineout, dp)[0] = '\0'; + dp += 7; + trim_trailing_spaces_and_print(lineout); + } + } else { + int row, which_cal, week_len, days[12][MAXDAYS]; + int *dp; + char lineout[80]; + + sprintf(lineout, "%d", year); + center(lineout, + (WEEK_LEN * 3 + HEAD_SEP * 2) + + julian * (J_WEEK_LEN * 2 + HEAD_SEP + - (WEEK_LEN * 3 + HEAD_SEP * 2)), + 0); + puts("\n"); /* two \n's */ + for (i = 0; i < 12; i++) { + day_array(i + 1, year, days[i]); + } + blank_string(lineout, sizeof(lineout)); + week_len = WEEK_LEN + julian * (J_WEEK_LEN - WEEK_LEN); + for (month = 0; month < 12; month += 3-julian) { + center(month_names[month], week_len, HEAD_SEP); + if (!julian) { + center(month_names[month + 1], week_len, HEAD_SEP); + } + center(month_names[month + 2 - julian], week_len, 0); + printf("\n%s%*s%s", day_headings, HEAD_SEP, "", day_headings); + if (!julian) { + printf("%*s%s", HEAD_SEP, "", day_headings); + } + putchar('\n'); + for (row = 0; row < (6*7); row += 7) { + for (which_cal = 0; which_cal < 3-julian; which_cal++) { + dp = days[month + which_cal] + row; + build_row(lineout + which_cal * (week_len + 2), dp); + } + /* blank_string took care of nul termination. */ + trim_trailing_spaces_and_print(lineout); + } + } + } + + fflush_stdout_and_exit(0); +} + +/* + * day_array -- + * Fill in an array of 42 integers with a calendar. Assume for a moment + * that you took the (maximum) 6 rows in a calendar and stretched them + * out end to end. You would have 42 numbers or spaces. This routine + * builds that array for any month from Jan. 1 through Dec. 9999. + */ +static void day_array(int month, int year, int *days) +{ + long temp; + int i; + int j_offset; + int day, dw, dm; + + memset(days, SPACE, MAXDAYS * sizeof(int)); + + if ((month == 9) && (year == 1752)) { + size_t oday = 0; + + j_offset = julian * 244; + do { + days[oday+2] = sep1752[oday] + j_offset; + } while (++oday < sizeof(sep1752)); + + return; + } + + /* day_in_year + * return the 1 based day number within the year + */ + day = 1; + if ((month > 2) && leap_year(year)) { + ++day; + } + + i = month; + while (i) { + day += days_in_month[--i]; + } + + /* day_in_week + * return the 0 based day number for any date from 1 Jan. 1 to + * 31 Dec. 9999. Assumes the Gregorian reformation eliminates + * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all + * missing days. + */ + dw = THURSDAY; + temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1) + + day; + if (temp < FIRST_MISSING_DAY) { + dw = ((temp - 1 + SATURDAY) % 7); + } else if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS)) { + dw = (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7); + } + + if (!julian) { + day = 1; + } + + dm = days_in_month[month]; + if ((month == 2) && leap_year(year)) { + ++dm; + } + + while (dm) { + days[dw++] = day++; + --dm; + } +} + +static void trim_trailing_spaces_and_print(char *s) +{ + char *p = s; + + while (*p) { + ++p; + } + while (p > s) { + --p; + if (!(isspace)(*p)) { /* We want the function... not the inline. */ + p[1] = '\0'; + break; + } + } + + puts(s); +} + +static void center(char *str, int len, int separate) +{ + int n = strlen(str); + len -= n; + printf("%*s%*s", (len/2) + n, str, (len/2) + (len % 2) + separate, ""); +} + +static void blank_string(char *buf, size_t buflen) +{ + memset(buf, ' ', buflen); + buf[buflen-1] = '\0'; +} + +static char *build_row(char *p, int *dp) +{ + int col, val, day; + + memset(p, ' ', (julian + DAY_LEN) * 7); + + col = 0; + do { + if ((day = *dp++) != SPACE) { + if (julian) { + ++p; + if (day >= 100) { + *p = '0'; + p[-1] = (day / 100) + '0'; + day %= 100; + } + } + if ((val = day / 10) > 0) { + *p = val + '0'; + } + *++p = day % 10 + '0'; + p += 2; + } else { + p += DAY_LEN + julian; + } + } while (++col < 7); + + return p; +} + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kim Letkeman. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + diff --git a/coreutils/cat.c b/coreutils/cat.c new file mode 100644 index 000000000..a95980552 --- /dev/null +++ b/coreutils/cat.c @@ -0,0 +1,41 @@ +/* vi: set sw=4 ts=4: */ +/* + * cat implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2, see file License in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/cat.html */ + +#include "busybox.h" +#include + +int cat_main(int argc, char **argv) +{ + FILE *f; + int retval = EXIT_SUCCESS; + + getopt32(argc, argv, "u"); + + argv += optind; + if (!*argv) { + *--argv = "-"; + } + + do { + f = fopen_or_warn_stdin(*argv); + if (f) { + off_t r = bb_copyfd_eof(fileno(f), STDOUT_FILENO); + fclose_if_not_stdin(f); + if (r >= 0) { + continue; + } + } + retval = EXIT_FAILURE; + } while (*++argv); + + return retval; +} diff --git a/coreutils/catv.c b/coreutils/catv.c new file mode 100644 index 000000000..66f30693a --- /dev/null +++ b/coreutils/catv.c @@ -0,0 +1,69 @@ +/* vi: set sw=4 ts=4: */ +/* + * cat -v implementation for busybox + * + * Copyright (C) 2006 Rob Landley + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* See "Cat -v considered harmful" at + * http://cm.bell-labs.com/cm/cs/doc/84/kp.ps.gz */ + +#include "busybox.h" + +int catv_main(int argc, char **argv) +{ + int retval = EXIT_SUCCESS, fd; + unsigned flags; + + flags = getopt32(argc, argv, "etv"); +#define CATV_OPT_e (1<<0) +#define CATV_OPT_t (1<<1) +#define CATV_OPT_v (1<<2) + flags ^= CATV_OPT_v; + + argv += optind; + do { + /* Read from stdin if there's nothing else to do. */ + fd = 0; + if (*argv && 0 > (fd = xopen(*argv, O_RDONLY))) + retval = EXIT_FAILURE; + else for (;;) { + int i, res; + + res = read(fd, bb_common_bufsiz1, sizeof(bb_common_bufsiz1)); + if (res < 0) + retval = EXIT_FAILURE; + if (res < 1) + break; + for (i = 0; i < res; i++) { + char c = bb_common_bufsiz1[i]; + + if (c > 126 && (flags & CATV_OPT_v)) { + if (c == 127) { + printf("^?"); + continue; + } else { + printf("M-"); + c -= 128; + } + } + if (c < 32) { + if (c == 10) { + if (flags & CATV_OPT_e) + putchar('$'); + } else if (flags & (c==9 ? CATV_OPT_t : CATV_OPT_v)) { + printf("^%c", c+'@'); + continue; + } + } + putchar(c); + } + } + if (ENABLE_FEATURE_CLEAN_UP && fd) + close(fd); + } while (*++argv); + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/chgrp.c b/coreutils/chgrp.c new file mode 100644 index 000000000..da5b12964 --- /dev/null +++ b/coreutils/chgrp.c @@ -0,0 +1,27 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini chgrp implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 defects - unsupported options -H, -L, and -P. */ +/* BB_AUDIT GNU defects - unsupported long options. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/chgrp.html */ + +#include "busybox.h" + +int chgrp_main(int argc, char **argv) +{ + /* "chgrp [opts] abc file(s)" == "chown [opts] :abc file(s)" */ + char **p = argv; + while (*++p) { + if (p[0][0] != '-') { + p[0] = xasprintf(":%s", p[0]); + break; + } + } + return chown_main(argc, argv); +} diff --git a/coreutils/chmod.c b/coreutils/chmod.c new file mode 100644 index 000000000..990e9b4ce --- /dev/null +++ b/coreutils/chmod.c @@ -0,0 +1,157 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini chmod implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Reworked by (C) 2002 Vladimir Oleynik + * to correctly parse '-rwxgoa' + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* BB_AUDIT GNU defects - unsupported long options. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/chmod.html */ + +#include "busybox.h" + +#define OPT_RECURSE (option_mask32 & 1) +#define OPT_VERBOSE (USE_DESKTOP(option_mask32 & 2) SKIP_DESKTOP(0)) +#define OPT_CHANGED (USE_DESKTOP(option_mask32 & 4) SKIP_DESKTOP(0)) +#define OPT_QUIET (USE_DESKTOP(option_mask32 & 8) SKIP_DESKTOP(0)) +#define OPT_STR "R" USE_DESKTOP("vcf") + +/* coreutils: + * chmod never changes the permissions of symbolic links; the chmod + * system call cannot change their permissions. This is not a problem + * since the permissions of symbolic links are never used. + * However, for each symbolic link listed on the command line, chmod changes + * the permissions of the pointed-to file. In contrast, chmod ignores + * symbolic links encountered during recursive directory traversals. + */ + +static int fileAction(const char *fileName, struct stat *statbuf, void* junk, int depth) +{ + mode_t newmode; + + /* match coreutils behavior */ + if (depth == 0) { + /* statbuf holds lstat result, but we need stat (follow link) */ + if (stat(fileName, statbuf)) + goto err; + } else { /* depth > 0: skip links */ + if (S_ISLNK(statbuf->st_mode)) + return TRUE; + } + newmode = statbuf->st_mode; + + if (!bb_parse_mode((char *)junk, &newmode)) + bb_error_msg_and_die("invalid mode: %s", (char *)junk); + + if (chmod(fileName, newmode) == 0) { + if (OPT_VERBOSE + || (OPT_CHANGED && statbuf->st_mode != newmode) + ) { + printf("mode of '%s' changed to %04o (%s)\n", fileName, + newmode & 07777, bb_mode_string(newmode)+1); + } + return TRUE; + } + err: + if (!OPT_QUIET) + bb_perror_msg("%s", fileName); + return FALSE; +} + +int chmod_main(int argc, char **argv) +{ + int retval = EXIT_SUCCESS; + char *arg, **argp; + char *smode; + + /* Convert first encountered -r into ar, -w into aw etc + * so that getopt would not eat it */ + argp = argv; + while ((arg = *++argp)) { + /* Mode spec must be the first arg (sans -R etc) */ + /* (protect against mishandling e.g. "chmod 644 -r") */ + if (arg[0] != '-') { + arg = NULL; + break; + } + /* An option. Not a -- or valid option? */ + if (arg[1] && !strchr("-"OPT_STR, arg[1])) { + arg[0] = 'a'; + break; + } + } + + /* Parse options */ + opt_complementary = "-2"; + getopt32(argc, argv, ("-"OPT_STR) + 1); /* Reuse string */ + argv += optind; + + /* Restore option-like mode if needed */ + if (arg) arg[0] = '-'; + + /* Ok, ready to do the deed now */ + smode = *argv++; + do { + if (!recursive_action(*argv, + OPT_RECURSE, // recurse + FALSE, // follow links: coreutils doesn't + FALSE, // depth first + fileAction, // file action + fileAction, // dir action + smode, // user data + 0) // depth + ) { + retval = EXIT_FAILURE; + } + } while (*++argv); + + return retval; +} + +/* +Security: chmod is too important and too subtle. +This is a test script (busybox chmod versus coreutils). +Run it in empty dir. Probably requires bash. + +#!/bin/sh +function create() { + rm -rf $1; mkdir $1 + ( + cd $1 || exit 1 + mkdir dir + >up + >file + >dir/file + ln -s dir linkdir + ln -s file linkfile + ln -s ../up dir/up + ) +} +function tst() { + (cd test1; $t1 $1) + (cd test2; $t2 $1) + (cd test1; ls -lR) >out1 + (cd test2; ls -lR) >out2 + echo "chmod $1" >out.diff + if ! diff -u out1 out2 >>out.diff; then exit 1; fi + mv out.diff out1.diff +} +t1="/tmp/busybox chmod" +t2="/usr/bin/chmod" +create test1; create test2 +tst "a+w file" +tst "a-w dir" +tst "a+w linkfile" +tst "a-w linkdir" +tst "-R a+w file" +tst "-R a-w dir" +tst "-R a+w linkfile" +tst "-R a-w linkdir" +tst "a-r,a+x linkfile" +*/ diff --git a/coreutils/chown.c b/coreutils/chown.c new file mode 100644 index 000000000..fddce7cf1 --- /dev/null +++ b/coreutils/chown.c @@ -0,0 +1,98 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini chown implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 defects - unsupported options -H, -L, and -P. */ +/* BB_AUDIT GNU defects - unsupported long options. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/chown.html */ + +#include "busybox.h" + +static uid_t uid = -1; +static gid_t gid = -1; + +static int (*chown_func)(const char *, uid_t, gid_t) = chown; + +#define OPT_RECURSE (option_mask32 & 1) +#define OPT_NODEREF (option_mask32 & 2) +#define OPT_VERBOSE (USE_DESKTOP(option_mask32 & 4) SKIP_DESKTOP(0)) +#define OPT_CHANGED (USE_DESKTOP(option_mask32 & 8) SKIP_DESKTOP(0)) +#define OPT_QUIET (USE_DESKTOP(option_mask32 & 0x10) SKIP_DESKTOP(0)) +#define OPT_STR ("Rh" USE_DESKTOP("vcf")) + +/* TODO: + * -H if a command line argument is a symbolic link to a directory, traverse it + * -L traverse every symbolic link to a directory encountered + * -P do not traverse any symbolic links (default) + */ + +static int fileAction(const char *fileName, struct stat *statbuf, + void ATTRIBUTE_UNUSED *junk, int depth) +{ + // TODO: -H/-L/-P + // if (depth ... && S_ISLNK(statbuf->st_mode)) .... + + if (!chown_func(fileName, + (uid == (uid_t)-1) ? statbuf->st_uid : uid, + (gid == (gid_t)-1) ? statbuf->st_gid : gid)) { + if (OPT_VERBOSE + || (OPT_CHANGED && (statbuf->st_uid != uid || statbuf->st_gid != gid)) + ) { + printf("changed ownership of '%s' to %u:%u\n", fileName, uid, gid); + } + return TRUE; + } + if (!OPT_QUIET) + bb_perror_msg("%s", fileName); /* A filename can have % in it... */ + return FALSE; +} + +int chown_main(int argc, char **argv) +{ + int retval = EXIT_SUCCESS; + char *groupName; + + opt_complementary = "-2"; + getopt32(argc, argv, OPT_STR); + + if (OPT_NODEREF) chown_func = lchown; + + argv += optind; + + /* First, check if there is a group name here */ + groupName = strchr(*argv, '.'); + if (!groupName) { + groupName = strchr(*argv, ':'); + } + + /* Check for the username and groupname */ + if (groupName) { + *groupName++ = '\0'; + gid = get_ug_id(groupName, bb_xgetgrnam); + } + if (--groupName != *argv) + uid = get_ug_id(*argv, bb_xgetpwnam); + ++argv; + + /* Ok, ready to do the deed now */ + do { + if (!recursive_action(*argv, + OPT_RECURSE, // recurse + FALSE, // follow links: TODO: -H/-L/-P + FALSE, // depth first + fileAction, // file action + fileAction, // dir action + NULL, // user data + 0) // depth + ) { + retval = EXIT_FAILURE; + } + } while (*++argv); + + return retval; +} diff --git a/coreutils/chroot.c b/coreutils/chroot.c new file mode 100644 index 000000000..62cfdc244 --- /dev/null +++ b/coreutils/chroot.c @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini chroot implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +#include "busybox.h" + +int chroot_main(int argc, char **argv) +{ + if (argc < 2) { + bb_show_usage(); + } + + ++argv; + if (chroot(*argv)) { + bb_perror_msg_and_die("cannot change root directory to %s", *argv); + } + xchdir("/"); + + ++argv; + if (argc == 2) { + argv -= 2; + if (!(*argv = getenv("SHELL"))) { + *argv = (char *) DEFAULT_SHELL; + } + argv[1] = (char *) "-i"; + } + + execvp(*argv, argv); + bb_perror_msg_and_die("cannot execute %s", *argv); +} diff --git a/coreutils/cksum.c b/coreutils/cksum.c new file mode 100644 index 000000000..52213328c --- /dev/null +++ b/coreutils/cksum.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * cksum - calculate the CRC32 checksum of a file + * + * Copyright (C) 2006 by Rob Sullivan, with ideas from code by Walter Harms + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. */ + +#include "busybox.h" + +int cksum_main(int argc, char **argv) +{ + + uint32_t *crc32_table = crc32_filltable(1); + + FILE *fp; + uint32_t crc; + long length, filesize; + int bytes_read; + char *cp; + + int inp_stdin = (argc == optind) ? 1 : 0; + + do { + fp = fopen_or_warn_stdin((inp_stdin) ? bb_msg_standard_input : *++argv); + + crc = 0; + length = 0; + + while ((bytes_read = fread(bb_common_bufsiz1, 1, BUFSIZ, fp)) > 0) { + cp = bb_common_bufsiz1; + length += bytes_read; + while (bytes_read--) + crc = (crc << 8) ^ crc32_table[((crc >> 24) ^ (*cp++)) & 0xffL]; + } + + filesize = length; + + for (; length; length >>= 8) + crc = (crc << 8) ^ crc32_table[((crc >> 24) ^ length) & 0xffL]; + crc ^= 0xffffffffL; + + if (inp_stdin) { + printf("%" PRIu32 " %li\n", crc, filesize); + break; + } + + printf("%" PRIu32 " %li %s\n", crc, filesize, *argv); + fclose(fp); + } while (*(argv + 1)); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/cmp.c b/coreutils/cmp.c new file mode 100644 index 000000000..71007eac1 --- /dev/null +++ b/coreutils/cmp.c @@ -0,0 +1,127 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini cmp implementation for busybox + * + * Copyright (C) 2000,2001 by Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 (virtually) compliant -- uses nicer GNU format for -l. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/cmp.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Original version majorly reworked for SUSv3 compliance, bug fixes, and + * size optimizations. Changes include: + * 1) Now correctly distinguishes between errors and actual file differences. + * 2) Proper handling of '-' args. + * 3) Actual error checking of i/o. + * 4) Accept SUSv3 -l option. Note that we use the slightly nicer gnu format + * in the '-l' case. + */ + +#include "busybox.h" + +static FILE *cmp_xfopen_input(const char * const filename) +{ + FILE *fp; + + fp = fopen_or_warn_stdin(filename); + if (fp) + return fp; + exit(xfunc_error_retval); /* We already output an error message. */ +} + +static const char fmt_eof[] = "cmp: EOF on %s\n"; +static const char fmt_differ[] = "%s %s differ: char %d, line %d\n"; +// This fmt_l_opt uses gnu-isms. SUSv3 would be "%.0s%.0s%d %o %o\n" +static const char fmt_l_opt[] = "%.0s%.0s%d %3o %3o\n"; + +static const char opt_chars[] = "sl"; +#define CMP_OPT_s (1<<0) +#define CMP_OPT_l (1<<1) + +int cmp_main(int argc, char **argv) +{ + FILE *fp1, *fp2, *outfile = stdout; + const char *filename1, *filename2 = "-"; + const char *fmt; + int c1, c2, char_pos = 0, line_pos = 1; + unsigned opt; + int retval = 0; + + xfunc_error_retval = 2; /* 1 is returned if files are different. */ + + opt = getopt32(argc, argv, opt_chars); + + if (((opt & (CMP_OPT_s|CMP_OPT_l)) == (CMP_OPT_s|CMP_OPT_l)) + || (((unsigned int)(--argc - optind)) > 1)) + bb_show_usage(); + + fp1 = cmp_xfopen_input(filename1 = *(argv += optind)); + + if (*++argv) { + filename2 = *argv; + } + fp2 = cmp_xfopen_input(filename2); + + if (fp1 == fp2) { /* Paranioa check... stdin == stdin? */ + /* Note that we don't bother reading stdin. Neither does gnu wc. + * But perhaps we should, so that other apps down the chain don't + * get the input. Consider 'echo hello | (cmp - - && cat -)'. + */ + return 0; + } + + if (opt & CMP_OPT_l) + fmt = fmt_l_opt; + else + fmt = fmt_differ; + + do { + c1 = getc(fp1); + c2 = getc(fp2); + ++char_pos; + if (c1 != c2) { /* Remember: a read error may have occurred. */ + retval = 1; /* But assume the files are different for now. */ + if (c2 == EOF) { + /* We know that fp1 isn't at EOF or in an error state. But to + * save space below, things are setup to expect an EOF in fp1 + * if an EOF occurred. So, swap things around. + */ + fp1 = fp2; + filename1 = filename2; + c1 = c2; + } + if (c1 == EOF) { + die_if_ferror(fp1, filename1); + fmt = fmt_eof; /* Well, no error, so it must really be EOF. */ + outfile = stderr; + /* There may have been output to stdout (option -l), so + * make sure we fflush before writing to stderr. */ + xfflush_stdout(); + } + if (!(opt & CMP_OPT_s)) { + if (opt & CMP_OPT_l) { + line_pos = c1; /* line_pos is unused in the -l case. */ + } + fprintf(outfile, fmt, filename1, filename2, char_pos, line_pos, c2); + if (opt) { /* This must be -l since not -s. */ + /* If we encountered an EOF, + * the while check will catch it. */ + continue; + } + } + break; + } + if (c1 == '\n') { + ++line_pos; + } + } while (c1 != EOF); + + die_if_ferror(fp1, filename1); + die_if_ferror(fp2, filename2); + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/comm.c b/coreutils/comm.c new file mode 100644 index 000000000..91f017753 --- /dev/null +++ b/coreutils/comm.c @@ -0,0 +1,126 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini comm implementation for busybox + * + * Copyright (C) 2005 by Robert Sullivan + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +#define COMM_OPT_1 0x01 +#define COMM_OPT_2 0x02 +#define COMM_OPT_3 0x04 + +/* These three variables control behaviour if non-zero */ + +static int only_file_1; +static int only_file_2; +static int both; + +/* writeline outputs the input given, appropriately aligned according to class */ +static void writeline(char *line, int class) +{ + if (class == 0) { + if (!only_file_1) + return; + } else if (class == 1) { + if (!only_file_2) + return; + if (only_file_1) + putchar('\t'); + } + else /*if (class == 2)*/ { + if (!both) + return; + if (only_file_1) + putchar('\t'); + if (only_file_2) + putchar('\t'); + } + fputs(line, stdout); +} + +/* This is the real core of the program - lines are compared here */ +static void cmp_files(char **infiles) +{ +#define LINE_LEN 100 +#define BB_EOF_0 0x1 +#define BB_EOF_1 0x2 + char thisline[2][LINE_LEN]; + FILE *streams[2]; + int i; + + for (i = 0; i < 2; ++i) { + streams[i] = ((infiles[i][0] == '=' && infiles[i][1]) ? stdin : xfopen(infiles[i], "r")); + fgets(thisline[i], LINE_LEN, streams[i]); + } + + while (*thisline[0] || *thisline[1]) { + int order = 0; + + i = 0; + if (feof(streams[0])) i |= BB_EOF_0; + if (feof(streams[1])) i |= BB_EOF_1; + + if (!*thisline[0]) + order = 1; + else if (!*thisline[1]) + order = -1; + else { + int tl0_len, tl1_len; + tl0_len = strlen(thisline[0]); + tl1_len = strlen(thisline[1]); + order = memcmp(thisline[0], thisline[1], tl0_len < tl1_len ? tl0_len : tl1_len); + if (!order) + order = tl0_len < tl1_len ? -1 : tl0_len != tl1_len; + } + + if (order == 0 && !i) + writeline(thisline[1], 2); + else if (order > 0 && !(i & BB_EOF_1)) + writeline(thisline[1], 1); + else if (order < 0 && !(i & BB_EOF_0)) + writeline(thisline[0], 0); + + if (i & BB_EOF_0 & BB_EOF_1) { + break; + + } else if (i) { + i = (i & BB_EOF_0 ? 1 : 0); + while (!feof(streams[i])) { + if ((order < 0 && i) || (order > 0 && !i)) + writeline(thisline[i], i); + fgets(thisline[i], LINE_LEN, streams[i]); + } + break; + + } else { + if (order >= 0) + fgets(thisline[1], LINE_LEN, streams[1]); + if (order <= 0) + fgets(thisline[0], LINE_LEN, streams[0]); + } + } + + fclose(streams[0]); + fclose(streams[1]); +} + +int comm_main(int argc, char **argv) +{ + unsigned long flags; + + flags = getopt32(argc, argv, "123"); + + if (optind + 2 != argc) + bb_show_usage(); + + only_file_1 = !(flags & COMM_OPT_1); + only_file_2 = !(flags & COMM_OPT_2); + both = !(flags & COMM_OPT_3); + + cmp_files(argv + optind); + exit(EXIT_SUCCESS); +} diff --git a/coreutils/cp.c b/coreutils/cp.c new file mode 100644 index 000000000..47ad85ecf --- /dev/null +++ b/coreutils/cp.c @@ -0,0 +1,92 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini cp implementation for busybox + * + * Copyright (C) 2000 by Matt Kraai + * + * Licensed under GPL v2 or later, see file LICENSE in this tarball for details. + */ + +/* http://www.opengroup.org/onlinepubs/007904975/utilities/cp.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Size reduction. + */ + +#include "busybox.h" +#include "libcoreutils/coreutils.h" + +int cp_main(int argc, char **argv) +{ + struct stat source_stat; + struct stat dest_stat; + const char *last; + const char *dest; + int s_flags; + int d_flags; + int flags; + int status = 0; + enum { + OPT_a = 1 << (sizeof(FILEUTILS_CP_OPTSTR)-1), + OPT_r = 1 << (sizeof(FILEUTILS_CP_OPTSTR)), + OPT_P = 1 << (sizeof(FILEUTILS_CP_OPTSTR)+1), + OPT_H = 1 << (sizeof(FILEUTILS_CP_OPTSTR)+2), + OPT_L = 1 << (sizeof(FILEUTILS_CP_OPTSTR)+3), + }; + + // Soft- and hardlinking don't mix + // -P and -d are the same (-P is POSIX, -d is GNU) + // -r and -R are the same + // -a = -pdR + opt_complementary = "?:l--s:s--l:Pd:rR:apdR"; + flags = getopt32(argc, argv, FILEUTILS_CP_OPTSTR "arPHL"); + /* Default behavior of cp is to dereference, so we don't have to do + * anything special when we are given -L. + * The behavior of -H is *almost* like -L, but not quite, so let's + * just ignore it too for fun. + if (flags & OPT_L) ... + if (flags & OPT_H) ... // deref command-line params only + */ + + flags ^= FILEUTILS_DEREFERENCE; /* The sense of this flag was reversed. */ + + if (optind + 2 > argc) { + bb_show_usage(); + } + + last = argv[argc - 1]; + argv += optind; + + /* If there are only two arguments and... */ + if (optind + 2 == argc) { + s_flags = cp_mv_stat2(*argv, &source_stat, + (flags & FILEUTILS_DEREFERENCE) ? stat : lstat); + if (s_flags < 0) + return EXIT_FAILURE; + d_flags = cp_mv_stat(last, &dest_stat); + if (d_flags < 0) + return EXIT_FAILURE; + + /* ...if neither is a directory or... */ + if ( !((s_flags | d_flags) & 2) || + /* ...recursing, the 1st is a directory, and the 2nd doesn't exist... */ + ((flags & FILEUTILS_RECUR) && (s_flags & 2) && !d_flags) + ) { + /* ...do a simple copy. */ + dest = xstrdup(last); + goto DO_COPY; /* Note: optind+2==argc implies argv[1]==last below. */ + } + } + + do { + dest = concat_path_file(last, bb_get_last_path_component(*argv)); + DO_COPY: + if (copy_file(*argv, dest, flags) < 0) { + status = 1; + } + free((void*)dest); + } while (*++argv != last); + + return status; +} diff --git a/coreutils/cut.c b/coreutils/cut.c new file mode 100644 index 000000000..a538e3d20 --- /dev/null +++ b/coreutils/cut.c @@ -0,0 +1,285 @@ +/* vi: set sw=4 ts=4: */ +/* + * cut.c - minimalist version of cut + * + * Copyright (C) 1999,2000,2001 by Lineo, inc. + * Written by Mark Whitley + * debloated by Bernhard Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* option vars */ +static const char optstring[] = "b:c:f:d:sn"; +#define CUT_OPT_BYTE_FLGS (1<<0) +#define CUT_OPT_CHAR_FLGS (1<<1) +#define CUT_OPT_FIELDS_FLGS (1<<2) +#define CUT_OPT_DELIM_FLGS (1<<3) +#define CUT_OPT_SUPPRESS_FLGS (1<<4) + +static char delim = '\t'; /* delimiter, default is tab */ + +struct cut_list { + int startpos; + int endpos; +}; + +enum { + BOL = 0, + EOL = INT_MAX, + NON_RANGE = -1 +}; + +/* growable array holding a series of lists */ +static struct cut_list *cut_lists; +static unsigned int nlists; /* number of elements in above list */ + + +static int cmpfunc(const void *a, const void *b) +{ + return (((struct cut_list *) a)->startpos - + ((struct cut_list *) b)->startpos); + +} + +static void cut_file(FILE * file) +{ + char *line = NULL; + unsigned int linenum = 0; /* keep these zero-based to be consistent */ + + /* go through every line in the file */ + while ((line = xmalloc_getline(file)) != NULL) { + + /* set up a list so we can keep track of what's been printed */ + char * printed = xzalloc(strlen(line) * sizeof(char)); + char * orig_line = line; + unsigned int cl_pos = 0; + int spos; + + /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */ + if (option_mask32 & (CUT_OPT_CHAR_FLGS | CUT_OPT_BYTE_FLGS)) { + /* print the chars specified in each cut list */ + for (; cl_pos < nlists; cl_pos++) { + spos = cut_lists[cl_pos].startpos; + while (spos < strlen(line)) { + if (!printed[spos]) { + printed[spos] = 'X'; + putchar(line[spos]); + } + spos++; + if (spos > cut_lists[cl_pos].endpos + || cut_lists[cl_pos].endpos == NON_RANGE) + break; + } + } + } else if (delim == '\n') { /* cut by lines */ + spos = cut_lists[cl_pos].startpos; + + /* get out if we have no more lists to process or if the lines + * are lower than what we're interested in */ + if (linenum < spos || cl_pos >= nlists) + goto next_line; + + /* if the line we're looking for is lower than the one we were + * passed, it means we displayed it already, so move on */ + while (spos < linenum) { + spos++; + /* go to the next list if we're at the end of this one */ + if (spos > cut_lists[cl_pos].endpos + || cut_lists[cl_pos].endpos == NON_RANGE) { + cl_pos++; + /* get out if there's no more lists to process */ + if (cl_pos >= nlists) + goto next_line; + spos = cut_lists[cl_pos].startpos; + /* get out if the current line is lower than the one + * we just became interested in */ + if (linenum < spos) + goto next_line; + } + } + + /* If we made it here, it means we've found the line we're + * looking for, so print it */ + puts(line); + goto next_line; + } else { /* cut by fields */ + int ndelim = -1; /* zero-based / one-based problem */ + int nfields_printed = 0; + char *field = NULL; + const char delimiter[2] = { delim, 0 }; + + /* does this line contain any delimiters? */ + if (strchr(line, delim) == NULL) { + if (!(option_mask32 & CUT_OPT_SUPPRESS_FLGS)) + puts(line); + goto next_line; + } + + /* process each list on this line, for as long as we've got + * a line to process */ + for (; cl_pos < nlists && line; cl_pos++) { + spos = cut_lists[cl_pos].startpos; + do { + /* find the field we're looking for */ + while (line && ndelim < spos) { + field = strsep(&line, delimiter); + ndelim++; + } + + /* we found it, and it hasn't been printed yet */ + if (field && ndelim == spos && !printed[ndelim]) { + /* if this isn't our first time through, we need to + * print the delimiter after the last field that was + * printed */ + if (nfields_printed > 0) + putchar(delim); + fputs(field, stdout); + printed[ndelim] = 'X'; + nfields_printed++; /* shouldn't overflow.. */ + } + + spos++; + + /* keep going as long as we have a line to work with, + * this is a list, and we're not at the end of that + * list */ + } while (spos <= cut_lists[cl_pos].endpos && line + && cut_lists[cl_pos].endpos != NON_RANGE); + } + } + /* if we printed anything at all, we need to finish it with a + * newline cuz we were handed a chomped line */ + putchar('\n'); + next_line: + linenum++; + free(printed); + free(orig_line); + } +} + +static const char _op_on_field[] = " only when operating on fields"; + +int cut_main(int argc, char **argv) +{ + char *sopt, *ltok; + + opt_complementary = "b--bcf:c--bcf:f--bcf"; + getopt32(argc, argv, optstring, &sopt, &sopt, &sopt, <ok); + if (!(option_mask32 & (CUT_OPT_BYTE_FLGS | CUT_OPT_CHAR_FLGS | CUT_OPT_FIELDS_FLGS))) + bb_error_msg_and_die("expected a list of bytes, characters, or fields"); + if (option_mask32 & BB_GETOPT_ERROR) + bb_error_msg_and_die("only one type of list may be specified"); + + if (option_mask32 & CUT_OPT_DELIM_FLGS) { + if (strlen(ltok) > 1) { + bb_error_msg_and_die("the delimiter must be a single character"); + } + delim = ltok[0]; + } + + /* non-field (char or byte) cutting has some special handling */ + if (!(option_mask32 & CUT_OPT_FIELDS_FLGS)) { + if (option_mask32 & CUT_OPT_SUPPRESS_FLGS) { + bb_error_msg_and_die + ("suppressing non-delimited lines makes sense%s", + _op_on_field); + } + if (delim != '\t') { + bb_error_msg_and_die + ("a delimiter may be specified%s", _op_on_field); + } + } + + /* + * parse list and put values into startpos and endpos. + * valid list formats: N, N-, N-M, -M + * more than one list can be separated by commas + */ + { + char *ntok; + int s = 0, e = 0; + + /* take apart the lists, one by one (they are separated with commas */ + while ((ltok = strsep(&sopt, ",")) != NULL) { + + /* it's actually legal to pass an empty list */ + if (strlen(ltok) == 0) + continue; + + /* get the start pos */ + ntok = strsep(<ok, "-"); + if (ntok == NULL) { + bb_error_msg + ("internal error: ntok is null for start pos!?\n"); + } else if (strlen(ntok) == 0) { + s = BOL; + } else { + s = xatoi_u(ntok); + /* account for the fact that arrays are zero based, while + * the user expects the first char on the line to be char #1 */ + if (s != 0) + s--; + } + + /* get the end pos */ + ntok = strsep(<ok, "-"); + if (ntok == NULL) { + e = NON_RANGE; + } else if (strlen(ntok) == 0) { + e = EOL; + } else { + e = xatoi_u(ntok); + /* if the user specified and end position of 0, that means "til the + * end of the line */ + if (e == 0) + e = EOL; + e--; /* again, arrays are zero based, lines are 1 based */ + if (e == s) + e = NON_RANGE; + } + + /* if there's something left to tokenize, the user passed + * an invalid list */ + if (ltok) + bb_error_msg_and_die("invalid byte or field list"); + + /* add the new list */ + cut_lists = xrealloc(cut_lists, sizeof(struct cut_list) * (++nlists)); + cut_lists[nlists-1].startpos = s; + cut_lists[nlists-1].endpos = e; + } + + /* make sure we got some cut positions out of all that */ + if (nlists == 0) + bb_error_msg_and_die("missing list of positions"); + + /* now that the lists are parsed, we need to sort them to make life + * easier on us when it comes time to print the chars / fields / lines + */ + qsort(cut_lists, nlists, sizeof(struct cut_list), cmpfunc); + } + + /* argv[(optind)..(argc-1)] should be names of file to process. If no + * files were specified or '-' was specified, take input from stdin. + * Otherwise, we process all the files specified. */ + if (argv[optind] == NULL + || (argv[optind][0] == '-' && argv[optind][1] == '\0')) { + cut_file(stdin); + } else { + FILE *file; + + for (; optind < argc; optind++) { + file = fopen_or_warn(argv[optind], "r"); + if (file) { + cut_file(file); + fclose(file); + } + } + } + if (ENABLE_FEATURE_CLEAN_UP) + free(cut_lists); + return EXIT_SUCCESS; +} diff --git a/coreutils/date.c b/coreutils/date.c new file mode 100644 index 000000000..37ccfd5ba --- /dev/null +++ b/coreutils/date.c @@ -0,0 +1,239 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini date implementation for busybox + * + * by Matthew Grant + * + * iso-format handling added by Robert Griebl + * bugfixes and cleanup by Bernhard Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. +*/ + +#include "busybox.h" + +/* This 'date' command supports only 2 time setting formats, + all the GNU strftime stuff (its in libc, lets use it), + setting time using UTC and displaying it, as well as + an RFC 2822 compliant date output for shell scripting + mail commands */ + +/* Input parsing code is always bulky - used heavy duty libc stuff as + much as possible, missed out a lot of bounds checking */ + +/* Default input handling to save surprising some people */ + + +#define DATE_OPT_RFC2822 0x01 +#define DATE_OPT_SET 0x02 +#define DATE_OPT_UTC 0x04 +#define DATE_OPT_DATE 0x08 +#define DATE_OPT_REFERENCE 0x10 +#define DATE_OPT_TIMESPEC 0x20 +#define DATE_OPT_HINT 0x40 + +static void xputenv(char *s) +{ + if (putenv(s) != 0) + bb_error_msg_and_die(bb_msg_memory_exhausted); +} + +static void maybe_set_utc(int opt) +{ + if (opt & DATE_OPT_UTC) + xputenv("TZ=UTC0"); +} + +int date_main(int argc, char **argv) +{ + time_t tm; + struct tm tm_time; + unsigned opt; + int ifmt = -1; + char *date_str = NULL; + char *date_fmt = NULL; + char *filename = NULL; + char *isofmt_arg; + char *hintfmt_arg; + + opt_complementary = "?:d--s:s--d" + USE_FEATURE_DATE_ISOFMT(":R--I:I--R"); + opt = getopt32(argc, argv, "Rs:ud:r:" + USE_FEATURE_DATE_ISOFMT("I::D:"), + &date_str, &date_str, &filename + USE_FEATURE_DATE_ISOFMT(, &isofmt_arg, &hintfmt_arg)); + maybe_set_utc(opt); + + if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_TIMESPEC)) { + if (!isofmt_arg) { + ifmt = 0; /* default is date */ + } else { + const char * const isoformats[] = + {"date", "hours", "minutes", "seconds"}; + + for (ifmt = 0; ifmt < 4; ifmt++) + if (!strcmp(isofmt_arg, isoformats[ifmt])) { + break; + } + if (ifmt == 4) /* parse error */ + bb_show_usage(); + } + } + + /* XXX, date_fmt == NULL from this always */ + if ((date_fmt == NULL) && (optind < argc) && (argv[optind][0] == '+')) { + date_fmt = &argv[optind][1]; /* Skip over the '+' */ + } else if (date_str == NULL) { + opt |= DATE_OPT_SET; + date_str = argv[optind]; + } + + /* Now we have parsed all the information except the date format + which depends on whether the clock is being set or read */ + + if (filename) { + struct stat statbuf; + xstat(filename, &statbuf); + tm = statbuf.st_mtime; + } else + time(&tm); + memcpy(&tm_time, localtime(&tm), sizeof(tm_time)); + /* Zero out fields - take her back to midnight! */ + if (date_str != NULL) { + tm_time.tm_sec = 0; + tm_time.tm_min = 0; + tm_time.tm_hour = 0; + + /* Process any date input to UNIX time since 1 Jan 1970 */ + if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_HINT)) { + strptime(date_str, hintfmt_arg, &tm_time); + } else if (strchr(date_str, ':') != NULL) { + /* Parse input and assign appropriately to tm_time */ + + if (sscanf(date_str, "%d:%d:%d", &tm_time.tm_hour, &tm_time.tm_min, + &tm_time.tm_sec) == 3) { + /* no adjustments needed */ + } else if (sscanf(date_str, "%d:%d", &tm_time.tm_hour, + &tm_time.tm_min) == 2) { + /* no adjustments needed */ + } else if (sscanf(date_str, "%d.%d-%d:%d:%d", &tm_time.tm_mon, + &tm_time.tm_mday, &tm_time.tm_hour, + &tm_time.tm_min, &tm_time.tm_sec) == 5) { + /* Adjust dates from 1-12 to 0-11 */ + tm_time.tm_mon -= 1; + } else if (sscanf(date_str, "%d.%d-%d:%d", &tm_time.tm_mon, + &tm_time.tm_mday, + &tm_time.tm_hour, &tm_time.tm_min) == 4) { + /* Adjust dates from 1-12 to 0-11 */ + tm_time.tm_mon -= 1; + } else if (sscanf(date_str, "%d.%d.%d-%d:%d:%d", &tm_time.tm_year, + &tm_time.tm_mon, &tm_time.tm_mday, + &tm_time.tm_hour, &tm_time.tm_min, + &tm_time.tm_sec) == 6) { + tm_time.tm_year -= 1900; /* Adjust years */ + tm_time.tm_mon -= 1; /* Adjust dates from 1-12 to 0-11 */ + } else if (sscanf(date_str, "%d.%d.%d-%d:%d", &tm_time.tm_year, + &tm_time.tm_mon, &tm_time.tm_mday, + &tm_time.tm_hour, &tm_time.tm_min) == 5) { + tm_time.tm_year -= 1900; /* Adjust years */ + tm_time.tm_mon -= 1; /* Adjust dates from 1-12 to 0-11 */ + } else { + bb_error_msg_and_die(bb_msg_invalid_date, date_str); + } + } else { + int nr; + char *cp; + + nr = sscanf(date_str, "%2d%2d%2d%2d%d", &tm_time.tm_mon, + &tm_time.tm_mday, &tm_time.tm_hour, &tm_time.tm_min, + &tm_time.tm_year); + + if (nr < 4 || nr > 5) { + bb_error_msg_and_die(bb_msg_invalid_date, date_str); + } + + cp = strchr(date_str, '.'); + if (cp) { + nr = sscanf(cp + 1, "%2d", &tm_time.tm_sec); + if (nr != 1) { + bb_error_msg_and_die(bb_msg_invalid_date, date_str); + } + } + + /* correct for century - minor Y2K problem here? */ + if (tm_time.tm_year >= 1900) { + tm_time.tm_year -= 1900; + } + /* adjust date */ + tm_time.tm_mon -= 1; + } + + /* Correct any day of week and day of year etc. fields */ + tm_time.tm_isdst = -1; /* Be sure to recheck dst. */ + tm = mktime(&tm_time); + if (tm < 0) { + bb_error_msg_and_die(bb_msg_invalid_date, date_str); + } + maybe_set_utc(opt); + + /* if setting time, set it */ + if ((opt & DATE_OPT_SET) && stime(&tm) < 0) { + bb_perror_msg("cannot set date"); + } + } + + /* Display output */ + + /* Deal with format string */ + + if (date_fmt == NULL) { + int i; + date_fmt = xzalloc(32); + if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) { + strcpy(date_fmt, "%Y-%m-%d"); + if (ifmt > 0) { + i = 8; + date_fmt[i++] = 'T'; + date_fmt[i++] = '%'; + date_fmt[i++] = 'H'; + if (ifmt > 1) { + date_fmt[i++] = ':'; + date_fmt[i++] = '%'; + date_fmt[i++] = 'M'; + } + if (ifmt > 2) { + date_fmt[i++] = ':'; + date_fmt[i++] = '%'; + date_fmt[i++] = 'S'; + } +format_utc: + date_fmt[i++] = '%'; + date_fmt[i] = (opt & DATE_OPT_UTC) ? 'Z' : 'z'; + } + } else if (opt & DATE_OPT_RFC2822) { + /* Undo busybox.c for date -R */ + setlocale(LC_TIME, "C"); + strcpy(date_fmt, "%a, %d %b %Y %H:%M:%S "); + i = 22; + goto format_utc; + } else /* default case */ + date_fmt = "%a %b %e %H:%M:%S %Z %Y"; + } + + if (*date_fmt == '\0') { + /* With no format string, just print a blank line */ + *bb_common_bufsiz1 = 0; + } else { + /* Handle special conversions */ + + if (strncmp(date_fmt, "%f", 2) == 0) { + date_fmt = "%Y.%m.%d-%H:%M:%S"; + } + + /* Generate output string */ + strftime(bb_common_bufsiz1, 200, date_fmt, &tm_time); + } + puts(bb_common_bufsiz1); + + return EXIT_SUCCESS; +} diff --git a/coreutils/dd.c b/coreutils/dd.c new file mode 100644 index 000000000..01f37abeb --- /dev/null +++ b/coreutils/dd.c @@ -0,0 +1,248 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini dd implementation for busybox + * + * + * Copyright (C) 2000,2001 Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include /* For FEATURE_DD_SIGNAL_HANDLING */ + +static const struct suffix_mult dd_suffixes[] = { + { "c", 1 }, + { "w", 2 }, + { "b", 512 }, + { "kD", 1000 }, + { "k", 1024 }, + { "K", 1024 }, // compat with coreutils dd + { "MD", 1000000 }, + { "M", 1048576 }, + { "GD", 1000000000 }, + { "G", 1073741824 }, + { NULL, 0 } +}; + +static off_t out_full, out_part, in_full, in_part; + +static void dd_output_status(int ATTRIBUTE_UNUSED cur_signal) +{ + fprintf(stderr, "%"OFF_FMT"d+%"OFF_FMT"d records in\n" + "%"OFF_FMT"d+%"OFF_FMT"d records out\n", + in_full, in_part, + out_full, out_part); +} + +static ssize_t full_write_or_warn(int fd, const void *buf, size_t len, + const char* filename) +{ + ssize_t n = full_write(fd, buf, len); + if (n < 0) + bb_perror_msg("writing '%s'", filename); + return n; +} + +#if ENABLE_LFS +#define XATOU_SFX xatoull_sfx +#else +#define XATOU_SFX xatoul_sfx +#endif + +int dd_main(int argc, char **argv) +{ + enum { + sync_flag = 1 << 0, + noerror = 1 << 1, + trunc_flag = 1 << 2, + twobufs_flag = 1 << 3, + }; + int flags = trunc_flag; + size_t oc = 0, ibs = 512, obs = 512; + ssize_t n, w; + off_t seek = 0, skip = 0, count = OFF_T_MAX; + int oflag, ifd, ofd; + const char *infile = NULL, *outfile = NULL; + char *ibuf, *obuf; + + if (ENABLE_FEATURE_DD_SIGNAL_HANDLING) { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = dd_output_status; + sa.sa_flags = SA_RESTART; + sigemptyset(&sa.sa_mask); + sigaction(SIGUSR1, &sa, 0); + } + + for (n = 1; n < argc; n++) { + char *arg = argv[n]; + /* Must fit into positive ssize_t */ + if (ENABLE_FEATURE_DD_IBS_OBS && !strncmp("ibs=", arg, 4)) + ibs = xatoul_range_sfx(arg+4, 0, ((size_t)-1L)/2, dd_suffixes); + else if (ENABLE_FEATURE_DD_IBS_OBS && !strncmp("obs=", arg, 4)) + obs = xatoul_range_sfx(arg+4, 0, ((size_t)-1L)/2, dd_suffixes); + else if (!strncmp("bs=", arg, 3)) + ibs = obs = xatoul_range_sfx(arg+3, 0, ((size_t)-1L)/2, dd_suffixes); + /* These can be large: */ + else if (!strncmp("count=", arg, 6)) + count = XATOU_SFX(arg+6, dd_suffixes); + else if (!strncmp("seek=", arg, 5)) + seek = XATOU_SFX(arg+5, dd_suffixes); + else if (!strncmp("skip=", arg, 5)) + skip = XATOU_SFX(arg+5, dd_suffixes); + + else if (!strncmp("if=", arg, 3)) + infile = arg+3; + else if (!strncmp("of=", arg, 3)) + outfile = arg+3; + else if (ENABLE_FEATURE_DD_IBS_OBS && !strncmp("conv=", arg, 5)) { + arg += 5; + while (1) { + if (!strncmp("notrunc", arg, 7)) { + flags &= ~trunc_flag; + arg += 7; + } else if (!strncmp("sync", arg, 4)) { + flags |= sync_flag; + arg += 4; + } else if (!strncmp("noerror", arg, 7)) { + flags |= noerror; + arg += 7; + } else { + bb_error_msg_and_die(bb_msg_invalid_arg, arg, "conv"); + } + if (arg[0] == '\0') break; + if (*arg++ != ',') bb_show_usage(); + } + } else + bb_show_usage(); + } + + ibuf = obuf = xmalloc(ibs); + if (ibs != obs) { + flags |= twobufs_flag; + obuf = xmalloc(obs); + } + + if (infile != NULL) + ifd = xopen(infile, O_RDONLY); + else { + ifd = STDIN_FILENO; + infile = bb_msg_standard_input; + } + + if (outfile != NULL) { + oflag = O_WRONLY | O_CREAT; + + if (!seek && (flags & trunc_flag)) + oflag |= O_TRUNC; + + ofd = xopen(outfile, oflag); + + if (seek && (flags & trunc_flag)) { + if (ftruncate(ofd, seek * obs) < 0) { + struct stat st; + + if (fstat(ofd, &st) < 0 || S_ISREG(st.st_mode) || + S_ISDIR(st.st_mode)) + goto die_outfile; + } + } + } else { + ofd = STDOUT_FILENO; + outfile = bb_msg_standard_output; + } + + if (skip) { + if (lseek(ifd, skip * ibs, SEEK_CUR) < 0) { + while (skip-- > 0) { + n = safe_read(ifd, ibuf, ibs); + if (n < 0) + bb_perror_msg_and_die("%s", infile); + if (n == 0) + break; + } + } + } + + if (seek) { + if (lseek(ofd, seek * obs, SEEK_CUR) < 0) + goto die_outfile; + } + + while (in_full + in_part != count) { + if (flags & noerror) { + /* Pre-zero the buffer when doing the noerror thing */ + memset(ibuf, '\0', ibs); + } + + n = safe_read(ifd, ibuf, ibs); + if (n == 0) + break; + if (n < 0) { + if (flags & noerror) { + n = ibs; + bb_perror_msg("%s", infile); + } else + bb_perror_msg_and_die("%s", infile); + } + if ((size_t)n == ibs) + in_full++; + else { + in_part++; + if (flags & sync_flag) { + memset(ibuf + n, '\0', ibs - n); + n = ibs; + } + } + if (flags & twobufs_flag) { + char *tmp = ibuf; + while (n) { + size_t d = obs - oc; + + if (d > n) + d = n; + memcpy(obuf + oc, tmp, d); + n -= d; + tmp += d; + oc += d; + if (oc == obs) { + w = full_write_or_warn(ofd, obuf, obs, outfile); + if (w < 0) goto out_status; + if (w == obs) + out_full++; + else if (w > 0) + out_part++; + oc = 0; + } + } + } else { + w = full_write_or_warn(ofd, ibuf, n, outfile); + if (w < 0) goto out_status; + if (w == obs) + out_full++; + else if (w > 0) + out_part++; + } + } + + if (ENABLE_FEATURE_DD_IBS_OBS && oc) { + w = full_write_or_warn(ofd, obuf, oc, outfile); + if (w < 0) goto out_status; + if (w > 0) + out_part++; + } + if (close(ifd) < 0) { + bb_perror_msg_and_die("%s", infile); + } + + if (close(ofd) < 0) { + die_outfile: + bb_perror_msg_and_die("%s", outfile); + } + out_status: + dd_output_status(0); + + return EXIT_SUCCESS; +} diff --git a/coreutils/df.c b/coreutils/df.c new file mode 100644 index 000000000..6ae3eed33 --- /dev/null +++ b/coreutils/df.c @@ -0,0 +1,154 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini df implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * based on original code by (I think) Bruce Perens . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 _NOT_ compliant -- options -P and -t missing. Also blocksize. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/df.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Size reduction. Removed floating point dependency. Added error checking + * on output. Output stats on 0-sized filesystems if specifically listed on + * the command line. Properly round *-blocks, Used, and Available quantities. + */ + +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +#ifndef CONFIG_FEATURE_HUMAN_READABLE +static long kscale(long b, long bs) +{ + return ( b * (long long) bs + 1024/2 ) / 1024; +} +#endif + +int df_main(int argc, char **argv) +{ + long blocks_used; + long blocks_percent_used; +#ifdef CONFIG_FEATURE_HUMAN_READABLE + unsigned long df_disp_hr = 1024; +#endif + int status = EXIT_SUCCESS; + unsigned opt; + FILE *mount_table; + struct mntent *mount_entry; + struct statfs s; + static const char hdr_1k[] = "1k-blocks"; /* default display is kilobytes */ + const char *disp_units_hdr = hdr_1k; + +#ifdef CONFIG_FEATURE_HUMAN_READABLE + opt_complementary = "h-km:k-hm:m-hk"; + opt = getopt32(argc, argv, "hmk"); + if (opt & 1) { + df_disp_hr = 0; + disp_units_hdr = " Size"; + } + if (opt & 2) { + df_disp_hr = 1024*1024; + disp_units_hdr = "1M-blocks"; + } +#else + opt = getopt32(argc, argv, "k"); +#endif + + printf("Filesystem%11s%-15sUsed Available Use%% Mounted on\n", + "", disp_units_hdr); + + mount_table = NULL; + argv += optind; + if (optind >= argc) { + mount_table = setmntent(bb_path_mtab_file, "r"); + if (!mount_table) { + bb_perror_msg_and_die(bb_path_mtab_file); + } + } + + do { + const char *device; + const char *mount_point; + + if (mount_table) { + mount_entry = getmntent(mount_table); + if (!mount_entry) { + endmntent(mount_table); + break; + } + } else { + mount_point = *argv++; + if (!mount_point) { + break; + } + mount_entry = find_mount_point(mount_point, bb_path_mtab_file); + if (!mount_entry) { + bb_error_msg("%s: can't find mount point", mount_point); + SET_ERROR: + status = EXIT_FAILURE; + continue; + } + } + + device = mount_entry->mnt_fsname; + mount_point = mount_entry->mnt_dir; + + if (statfs(mount_point, &s) != 0) { + bb_perror_msg("%s", mount_point); + goto SET_ERROR; + } + + if ((s.f_blocks > 0) || !mount_table){ + blocks_used = s.f_blocks - s.f_bfree; + blocks_percent_used = 0; + if (blocks_used + s.f_bavail) { + blocks_percent_used = (((long long) blocks_used) * 100 + + (blocks_used + s.f_bavail)/2 + ) / (blocks_used + s.f_bavail); + } + + if (strcmp(device, "rootfs") == 0) { + continue; + } else if (strcmp(device, "/dev/root") == 0) { + /* Adjusts device to be the real root device, + * or leaves device alone if it can't find it */ + device = find_block_device("/"); + if (!device) { + goto SET_ERROR; + } + } + +#ifdef CONFIG_FEATURE_HUMAN_READABLE + printf("%-20s %9s ", device, + make_human_readable_str(s.f_blocks, s.f_bsize, df_disp_hr)); + + printf("%9s ", + make_human_readable_str( (s.f_blocks - s.f_bfree), + s.f_bsize, df_disp_hr)); + + printf("%9s %3ld%% %s\n", + make_human_readable_str(s.f_bavail, s.f_bsize, df_disp_hr), + blocks_percent_used, mount_point); +#else + printf("%-20s %9ld %9ld %9ld %3ld%% %s\n", + device, + kscale(s.f_blocks, s.f_bsize), + kscale(s.f_blocks-s.f_bfree, s.f_bsize), + kscale(s.f_bavail, s.f_bsize), + blocks_percent_used, mount_point); +#endif + } + + } while (1); + + fflush_stdout_and_exit(status); +} diff --git a/coreutils/diff.c b/coreutils/diff.c new file mode 100644 index 000000000..2ed26babe --- /dev/null +++ b/coreutils/diff.c @@ -0,0 +1,1243 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini diff implementation for busybox, adapted from OpenBSD diff. + * + * Copyright (C) 2006 by Robert Sullivan + * Copyright (c) 2003 Todd C. Miller + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +#define FSIZE_MAX 32768 + +/* + * Output flags + */ +#define D_HEADER 1 /* Print a header/footer between files */ +#define D_EMPTY1 2 /* Treat first file as empty (/dev/null) */ +#define D_EMPTY2 4 /* Treat second file as empty (/dev/null) */ + +/* + * Status values for print_status() and diffreg() return values + * Guide: + * D_SAME - files are the same + * D_DIFFER - files differ + * D_BINARY - binary files differ + * D_COMMON - subdirectory common to both dirs + * D_ONLY - file only exists in one dir + * D_MISMATCH1 - path1 a dir, path2 a file + * D_MISMATCH2 - path1 a file, path2 a dir + * D_ERROR - error occurred + * D_SKIPPED1 - skipped path1 as it is a special file + * D_SKIPPED2 - skipped path2 as it is a special file + */ + +#define D_SAME 0 +#define D_DIFFER (1<<0) +#define D_BINARY (1<<1) +#define D_COMMON (1<<2) +#define D_ONLY (1<<3) +#define D_MISMATCH1 (1<<4) +#define D_MISMATCH2 (1<<5) +#define D_ERROR (1<<6) +#define D_SKIPPED1 (1<<7) +#define D_SKIPPED2 (1<<8) + +/* Command line options */ +static unsigned long cmd_flags; + +#define FLAG_a (1<<0) +#define FLAG_b (1<<1) +#define FLAG_d (1<<2) +#define FLAG_i (1<<3) +#define FLAG_L (1<<4) +#define FLAG_N (1<<5) +#define FLAG_q (1<<6) +#define FLAG_r (1<<7) +#define FLAG_s (1<<8) +#define FLAG_S (1<<9) +#define FLAG_t (1<<10) +#define FLAG_T (1<<11) +#define FLAG_U (1<<12) +#define FLAG_w (1<<13) + +/* XXX: FIXME: the following variables should be static, but gcc currently + * creates a much bigger object if we do this. */ +int context, status; +char *start, *label[2]; +struct stat stb1, stb2; +char **dl; +static int dl_count = 0; + +struct cand { + int x; + int y; + int pred; +}; + +struct line { + int serial; + int value; +} *file[2]; + +/* + * The following struct is used to record change information + * doing a "context" or "unified" diff. (see routine "change" to + * understand the highly mnemonic field names) + */ +struct context_vec { + int a; /* start line in old file */ + int b; /* end line in old file */ + int c; /* start line in new file */ + int d; /* end line in new file */ +}; + +static int *J; /* will be overlaid on class */ +static int *class; /* will be overlaid on file[0] */ +static int *klist; /* will be overlaid on file[0] after class */ +static int *member; /* will be overlaid on file[1] */ +static int clen; +static int len[2]; +static int pref, suff; /* length of prefix and suffix */ +static int slen[2]; +static int anychange; +static long *ixnew; /* will be overlaid on file[1] */ +static long *ixold; /* will be overlaid on klist */ +static struct cand *clist; /* merely a free storage pot for candidates */ +static int clistlen; /* the length of clist */ +static struct line *sfile[2]; /* shortened by pruning common prefix/suffix */ +static struct context_vec *context_vec_start; +static struct context_vec *context_vec_end; +static struct context_vec *context_vec_ptr; + +static void print_only(const char *path, size_t dirlen, const char *entry) +{ + if (dirlen > 1) + dirlen--; + printf("Only in %.*s: %s\n", (int) dirlen, path, entry); +} + +static void print_status(int val, char *path1, char *path2, char *entry) +{ + const char *const _entry = entry ? entry : ""; + char *_path1 = entry ? concat_path_file(path1, _entry) : path1; + char *_path2 = entry ? concat_path_file(path2, _entry) : path2; + + switch (val) { + case D_ONLY: + print_only(path1, strlen(path1), entry); + break; + case D_COMMON: + printf("Common subdirectories: %s and %s\n", _path1, _path2); + break; + case D_BINARY: + printf("Binary files %s and %s differ\n", _path1, _path2); + break; + case D_DIFFER: + if (cmd_flags & FLAG_q) + printf("Files %s and %s differ\n", _path1, _path2); + break; + case D_SAME: + if (cmd_flags & FLAG_s) + printf("Files %s and %s are identical\n", _path1, _path2); + break; + case D_MISMATCH1: + printf("File %s is a directory while file %s is a regular file\n", + _path1, _path2); + break; + case D_MISMATCH2: + printf("File %s is a regular file while file %s is a directory\n", + _path1, _path2); + break; + case D_SKIPPED1: + printf("File %s is not a regular file or directory and was skipped\n", + _path1); + break; + case D_SKIPPED2: + printf("File %s is not a regular file or directory and was skipped\n", + _path2); + break; + } + if (entry) { + free(_path1); + free(_path2); + } +} + +/* + * Hash function taken from Robert Sedgewick, Algorithms in C, 3d ed., p 578. + */ +static int readhash(FILE * f) +{ + int i, t, space; + int sum; + + sum = 1; + space = 0; + if (!(cmd_flags & FLAG_b) && !(cmd_flags & FLAG_w)) { + if (FLAG_i) + for (i = 0; (t = getc(f)) != '\n'; i++) { + if (t == EOF) { + if (i == 0) + return 0; + break; + } + sum = sum * 127 + t; + } else + for (i = 0; (t = getc(f)) != '\n'; i++) { + if (t == EOF) { + if (i == 0) + return 0; + break; + } + sum = sum * 127 + t; + } + } else { + for (i = 0;;) { + switch (t = getc(f)) { + case '\t': + case '\r': + case '\v': + case '\f': + case ' ': + space++; + continue; + default: + if (space && !(cmd_flags & FLAG_w)) { + i++; + space = 0; + } + sum = sum * 127 + t; + i++; + continue; + case EOF: + if (i == 0) + return 0; + /* FALLTHROUGH */ + case '\n': + break; + } + break; + } + } + /* + * There is a remote possibility that we end up with a zero sum. + * Zero is used as an EOF marker, so return 1 instead. + */ + return (sum == 0 ? 1 : sum); +} + + + +/* + * Check to see if the given files differ. + * Returns 0 if they are the same, 1 if different, and -1 on error. + */ +static int files_differ(FILE * f1, FILE * f2, int flags) +{ + char buf1[BUFSIZ], buf2[BUFSIZ]; + size_t i, j; + + if ((flags & (D_EMPTY1 | D_EMPTY2)) || stb1.st_size != stb2.st_size || + (stb1.st_mode & S_IFMT) != (stb2.st_mode & S_IFMT)) + return 1; + while (1) { + i = fread(buf1, 1, sizeof(buf1), f1); + j = fread(buf2, 1, sizeof(buf2), f2); + if (i != j) + return 1; + if (i == 0 && j == 0) { + if (ferror(f1) || ferror(f2)) + return 1; + return 0; + } + if (memcmp(buf1, buf2, i) != 0) + return 1; + } +} + +static void prepare(int i, FILE * fd, off_t filesize) +{ + struct line *p; + int h; + size_t j, sz; + + rewind(fd); + + sz = (filesize <= FSIZE_MAX ? filesize : FSIZE_MAX) / 25; + if (sz < 100) + sz = 100; + + p = xmalloc((sz + 3) * sizeof(struct line)); + for (j = 0; (h = readhash(fd));) { + if (j == sz) { + sz = sz * 3 / 2; + p = xrealloc(p, (sz + 3) * sizeof(struct line)); + } + p[++j].value = h; + } + len[i] = j; + file[i] = p; +} + +static void prune(void) +{ + int i, j; + + for (pref = 0; pref < len[0] && pref < len[1] && + file[0][pref + 1].value == file[1][pref + 1].value; pref++); + for (suff = 0; suff < len[0] - pref && suff < len[1] - pref && + file[0][len[0] - suff].value == file[1][len[1] - suff].value; + suff++); + for (j = 0; j < 2; j++) { + sfile[j] = file[j] + pref; + slen[j] = len[j] - pref - suff; + for (i = 0; i <= slen[j]; i++) + sfile[j][i].serial = i; + } +} + +static void equiv(struct line *a, int n, struct line *b, int m, int *c) +{ + int i, j; + + i = j = 1; + while (i <= n && j <= m) { + if (a[i].value < b[j].value) + a[i++].value = 0; + else if (a[i].value == b[j].value) + a[i++].value = j; + else + j++; + } + while (i <= n) + a[i++].value = 0; + b[m + 1].value = 0; + j = 0; + while (++j <= m) { + c[j] = -b[j].serial; + while (b[j + 1].value == b[j].value) { + j++; + c[j] = b[j].serial; + } + } + c[j] = -1; +} + +static int isqrt(int n) +{ + int y, x = 1; + + if (n == 0) + return 0; + + do { + y = x; + x = n / x; + x += y; + x /= 2; + } while ((x - y) > 1 || (x - y) < -1); + + return x; +} + +static int newcand(int x, int y, int pred) +{ + struct cand *q; + + if (clen == clistlen) { + clistlen = clistlen * 11 / 10; + clist = xrealloc(clist, clistlen * sizeof(struct cand)); + } + q = clist + clen; + q->x = x; + q->y = y; + q->pred = pred; + return clen++; +} + + +static int search(int *c, int k, int y) +{ + int i, j, l, t; + + if (clist[c[k]].y < y) /* quick look for typical case */ + return k + 1; + i = 0; + j = k + 1; + while (1) { + l = i + j; + if ((l >>= 1) <= i) + break; + t = clist[c[l]].y; + if (t > y) + j = l; + else if (t < y) + i = l; + else + return l; + } + return l + 1; +} + + +static int stone(int *a, int n, int *b, int *c) +{ + int i, k, y, j, l; + int oldc, tc, oldl; + unsigned int numtries; + +#if ENABLE_FEATURE_DIFF_MINIMAL + const unsigned int bound = + (cmd_flags & FLAG_d) ? UINT_MAX : MAX(256, isqrt(n)); +#else + const unsigned int bound = MAX(256, isqrt(n)); +#endif + k = 0; + c[0] = newcand(0, 0, 0); + for (i = 1; i <= n; i++) { + j = a[i]; + if (j == 0) + continue; + y = -b[j]; + oldl = 0; + oldc = c[0]; + numtries = 0; + do { + if (y <= clist[oldc].y) + continue; + l = search(c, k, y); + if (l != oldl + 1) + oldc = c[l - 1]; + if (l <= k) { + if (clist[c[l]].y <= y) + continue; + tc = c[l]; + c[l] = newcand(i, y, oldc); + oldc = tc; + oldl = l; + numtries++; + } else { + c[l] = newcand(i, y, oldc); + k++; + break; + } + } while ((y = b[++j]) > 0 && numtries < bound); + } + return k; +} + +static void unravel(int p) +{ + struct cand *q; + int i; + + for (i = 0; i <= len[0]; i++) + J[i] = i <= pref ? i : i > len[0] - suff ? i + len[1] - len[0] : 0; + for (q = clist + p; q->y != 0; q = clist + q->pred) + J[q->x + pref] = q->y + pref; +} + + +static void unsort(struct line *f, int l, int *b) +{ + int *a, i; + + a = xmalloc((l + 1) * sizeof(int)); + for (i = 1; i <= l; i++) + a[f[i].serial] = f[i].value; + for (i = 1; i <= l; i++) + b[i] = a[i]; + free(a); +} + +static int skipline(FILE * f) +{ + int i, c; + + for (i = 1; (c = getc(f)) != '\n' && c != EOF; i++) + continue; + return i; +} + + +/* + * Check does double duty: + * 1. ferret out any fortuitous correspondences due + * to confounding by hashing (which result in "jackpot") + * 2. collect random access indexes to the two files + */ +static void check(FILE * f1, FILE * f2) +{ + int i, j, jackpot, c, d; + long ctold, ctnew; + + rewind(f1); + rewind(f2); + j = 1; + ixold[0] = ixnew[0] = 0; + jackpot = 0; + ctold = ctnew = 0; + for (i = 1; i <= len[0]; i++) { + if (J[i] == 0) { + ixold[i] = ctold += skipline(f1); + continue; + } + while (j < J[i]) { + ixnew[j] = ctnew += skipline(f2); + j++; + } + if ((cmd_flags & FLAG_b) || (cmd_flags & FLAG_w) + || (cmd_flags & FLAG_i)) { + while (1) { + c = getc(f1); + d = getc(f2); + /* + * GNU diff ignores a missing newline + * in one file if bflag || wflag. + */ + if (((cmd_flags & FLAG_b) || (cmd_flags & FLAG_w)) && + ((c == EOF && d == '\n') || (c == '\n' && d == EOF))) { + break; + } + ctold++; + ctnew++; + if ((cmd_flags & FLAG_b) && isspace(c) && isspace(d)) { + do { + if (c == '\n') + break; + ctold++; + } while (isspace(c = getc(f1))); + do { + if (d == '\n') + break; + ctnew++; + } while (isspace(d = getc(f2))); + } else if (cmd_flags & FLAG_w) { + while (isspace(c) && c != '\n') { + c = getc(f1); + ctold++; + } + while (isspace(d) && d != '\n') { + d = getc(f2); + ctnew++; + } + } + if (c != d) { + jackpot++; + J[i] = 0; + if (c != '\n' && c != EOF) + ctold += skipline(f1); + if (d != '\n' && c != EOF) + ctnew += skipline(f2); + break; + } + if (c == '\n' || c == EOF) + break; + } + } else { + while (1) { + ctold++; + ctnew++; + if ((c = getc(f1)) != (d = getc(f2))) { + J[i] = 0; + if (c != '\n' && c != EOF) + ctold += skipline(f1); + if (d != '\n' && c != EOF) + ctnew += skipline(f2); + break; + } + if (c == '\n' || c == EOF) + break; + } + } + ixold[i] = ctold; + ixnew[j] = ctnew; + j++; + } + for (; j <= len[1]; j++) + ixnew[j] = ctnew += skipline(f2); +} + +/* shellsort CACM #201 */ +static void sort(struct line *a, int n) +{ + struct line *ai, *aim, w; + int j, m = 0, k; + + if (n == 0) + return; + for (j = 1; j <= n; j *= 2) + m = 2 * j - 1; + for (m /= 2; m != 0; m /= 2) { + k = n - m; + for (j = 1; j <= k; j++) { + for (ai = &a[j]; ai > a; ai -= m) { + aim = &ai[m]; + if (aim < ai) + break; /* wraparound */ + if (aim->value > ai[0].value || + (aim->value == ai[0].value && aim->serial > ai[0].serial)) + break; + w.value = ai[0].value; + ai[0].value = aim->value; + aim->value = w.value; + w.serial = ai[0].serial; + ai[0].serial = aim->serial; + aim->serial = w.serial; + } + } + } +} + + +static void uni_range(int a, int b) +{ + if (a < b) + printf("%d,%d", a, b - a + 1); + else if (a == b) + printf("%d", b); + else + printf("%d,0", b); +} + +static int fetch(long *f, int a, int b, FILE * lb, int ch) +{ + int i, j, c, lastc, col, nc; + + if (a > b) + return 0; + for (i = a; i <= b; i++) { + fseek(lb, f[i - 1], SEEK_SET); + nc = f[i] - f[i - 1]; + if (ch != '\0') { + putchar(ch); + if (cmd_flags & FLAG_T) + putchar('\t'); + } + col = 0; + for (j = 0, lastc = '\0'; j < nc; j++, lastc = c) { + if ((c = getc(lb)) == EOF) { + puts("\n\\ No newline at end of file"); + return 0; + } + if (c == '\t' && (cmd_flags & FLAG_t)) { + do { + putchar(' '); + } while (++col & 7); + } else { + putchar(c); + col++; + } + } + } + return 0; +} + +static int asciifile(FILE * f) +{ +#if ENABLE_FEATURE_DIFF_BINARY + unsigned char buf[BUFSIZ]; + int i, cnt; +#endif + + if ((cmd_flags & FLAG_a) || f == NULL) + return 1; + +#if ENABLE_FEATURE_DIFF_BINARY + rewind(f); + cnt = fread(buf, 1, sizeof(buf), f); + for (i = 0; i < cnt; i++) { + if (!isprint(buf[i]) && !isspace(buf[i])) { + return 0; + } + } +#endif + return 1; +} + +/* dump accumulated "unified" diff changes */ +static void dump_unified_vec(FILE * f1, FILE * f2) +{ + struct context_vec *cvp = context_vec_start; + int lowa, upb, lowc, upd; + int a, b, c, d; + char ch; + + if (context_vec_start > context_vec_ptr) + return; + + b = d = 0; /* gcc */ + lowa = MAX(1, cvp->a - context); + upb = MIN(len[0], context_vec_ptr->b + context); + lowc = MAX(1, cvp->c - context); + upd = MIN(len[1], context_vec_ptr->d + context); + + fputs("@@ -", stdout); + uni_range(lowa, upb); + fputs(" +", stdout); + uni_range(lowc, upd); + fputs(" @@", stdout); + putchar('\n'); + + /* + * Output changes in "unified" diff format--the old and new lines + * are printed together. + */ + for (; cvp <= context_vec_ptr; cvp++) { + a = cvp->a; + b = cvp->b; + c = cvp->c; + d = cvp->d; + + /* + * c: both new and old changes + * d: only changes in the old file + * a: only changes in the new file + */ + if (a <= b && c <= d) + ch = 'c'; + else + ch = (a <= b) ? 'd' : 'a'; + if (ch == 'c' || ch == 'd') { + fetch(ixold, lowa, a - 1, f1, ' '); + fetch(ixold, a, b, f1, '-'); + } + if (ch == 'a') + fetch(ixnew, lowc, c - 1, f2, ' '); + if (ch == 'c' || ch == 'a') + fetch(ixnew, c, d, f2, '+'); + lowa = b + 1; + lowc = d + 1; + } + fetch(ixnew, d + 1, upd, f2, ' '); + + context_vec_ptr = context_vec_start - 1; +} + + +static void print_header(const char *file1, const char *file2) +{ + if (label[0] != NULL) + printf("%s %s\n", "---", label[0]); + else + printf("%s %s\t%s", "---", file1, ctime(&stb1.st_mtime)); + if (label[1] != NULL) + printf("%s %s\n", "+++", label[1]); + else + printf("%s %s\t%s", "+++", file2, ctime(&stb2.st_mtime)); +} + + + +/* + * Indicate that there is a difference between lines a and b of the from file + * to get to lines c to d of the to file. If a is greater then b then there + * are no lines in the from file involved and this means that there were + * lines appended (beginning at b). If c is greater than d then there are + * lines missing from the to file. + */ +static void change(char *file1, FILE * f1, char *file2, FILE * f2, int a, + int b, int c, int d) +{ + static size_t max_context = 64; + + if (a > b && c > d) + return; + if (cmd_flags & FLAG_q) + return; + + /* + * Allocate change records as needed. + */ + if (context_vec_ptr == context_vec_end - 1) { + ptrdiff_t offset = context_vec_ptr - context_vec_start; + + max_context <<= 1; + context_vec_start = xrealloc(context_vec_start, + max_context * + sizeof(struct context_vec)); + context_vec_end = context_vec_start + max_context; + context_vec_ptr = context_vec_start + offset; + } + if (anychange == 0) { + /* + * Print the context/unidiff header first time through. + */ + print_header(file1, file2); + anychange = 1; + } else if (a > context_vec_ptr->b + (2 * context) + 1 && + c > context_vec_ptr->d + (2 * context) + 1) { + /* + * If this change is more than 'context' lines from the + * previous change, dump the record and reset it. + */ + dump_unified_vec(f1, f2); + } + context_vec_ptr++; + context_vec_ptr->a = a; + context_vec_ptr->b = b; + context_vec_ptr->c = c; + context_vec_ptr->d = d; + return; + +} + + +static void output(char *file1, FILE * f1, char *file2, FILE * f2) +{ + + /* Note that j0 and j1 can't be used as they are defined in math.h. + * This also allows the rather amusing variable 'j00'... */ + int m, i0, i1, j00, j01; + + rewind(f1); + rewind(f2); + m = len[0]; + J[0] = 0; + J[m + 1] = len[1] + 1; + for (i0 = 1; i0 <= m; i0 = i1 + 1) { + while (i0 <= m && J[i0] == J[i0 - 1] + 1) + i0++; + j00 = J[i0 - 1] + 1; + i1 = i0 - 1; + while (i1 < m && J[i1 + 1] == 0) + i1++; + j01 = J[i1 + 1] - 1; + J[i1] = j01; + change(file1, f1, file2, f2, i0, i1, j00, j01); + } + if (m == 0) { + change(file1, f1, file2, f2, 1, 0, 1, len[1]); + } + if (anychange != 0) { + dump_unified_vec(f1, f2); + } +} + +/* + * The following code uses an algorithm due to Harold Stone, + * which finds a pair of longest identical subsequences in + * the two files. + * + * The major goal is to generate the match vector J. + * J[i] is the index of the line in file1 corresponding + * to line i file0. J[i] = 0 if there is no + * such line in file1. + * + * Lines are hashed so as to work in core. All potential + * matches are located by sorting the lines of each file + * on the hash (called ``value''). In particular, this + * collects the equivalence classes in file1 together. + * Subroutine equiv replaces the value of each line in + * file0 by the index of the first element of its + * matching equivalence in (the reordered) file1. + * To save space equiv squeezes file1 into a single + * array member in which the equivalence classes + * are simply concatenated, except that their first + * members are flagged by changing sign. + * + * Next the indices that point into member are unsorted into + * array class according to the original order of file0. + * + * The cleverness lies in routine stone. This marches + * through the lines of file0, developing a vector klist + * of "k-candidates". At step i a k-candidate is a matched + * pair of lines x,y (x in file0 y in file1) such that + * there is a common subsequence of length k + * between the first i lines of file0 and the first y + * lines of file1, but there is no such subsequence for + * any smaller y. x is the earliest possible mate to y + * that occurs in such a subsequence. + * + * Whenever any of the members of the equivalence class of + * lines in file1 matable to a line in file0 has serial number + * less than the y of some k-candidate, that k-candidate + * with the smallest such y is replaced. The new + * k-candidate is chained (via pred) to the current + * k-1 candidate so that the actual subsequence can + * be recovered. When a member has serial number greater + * that the y of all k-candidates, the klist is extended. + * At the end, the longest subsequence is pulled out + * and placed in the array J by unravel + * + * With J in hand, the matches there recorded are + * checked against reality to assure that no spurious + * matches have crept in due to hashing. If they have, + * they are broken, and "jackpot" is recorded--a harmless + * matter except that a true match for a spuriously + * mated line may now be unnecessarily reported as a change. + * + * Much of the complexity of the program comes simply + * from trying to minimize core utilization and + * maximize the range of doable problems by dynamically + * allocating what is needed and reusing what is not. + * The core requirements for problems larger than somewhat + * are (in words) 2*length(file0) + length(file1) + + * 3*(number of k-candidates installed), typically about + * 6n words for files of length n. + */ + +static int diffreg(char *ofile1, char *ofile2, int flags) +{ + char *file1 = ofile1; + char *file2 = ofile2; + FILE *f1 = NULL; + FILE *f2 = NULL; + int rval = D_SAME; + int i; + + anychange = 0; + context_vec_ptr = context_vec_start - 1; + + if (S_ISDIR(stb1.st_mode) != S_ISDIR(stb2.st_mode)) + return (S_ISDIR(stb1.st_mode) ? D_MISMATCH1 : D_MISMATCH2); + if (strcmp(file1, "-") == 0 && strcmp(file2, "-") == 0) + goto closem; + + if (flags & D_EMPTY1) + f1 = xfopen(bb_dev_null, "r"); + else { + if (strcmp(file1, "-") == 0) + f1 = stdin; + else + f1 = xfopen(file1, "r"); + } + + if (flags & D_EMPTY2) + f2 = xfopen(bb_dev_null, "r"); + else { + if (strcmp(file2, "-") == 0) + f2 = stdin; + else + f2 = xfopen(file2, "r"); + } + + if ((i = files_differ(f1, f2, flags)) == 0) + goto closem; + else if (i != 1) { /* 1 == ok */ + /* error */ + status |= 2; + goto closem; + } + + if (!asciifile(f1) || !asciifile(f2)) { + rval = D_BINARY; + status |= 1; + goto closem; + } + + prepare(0, f1, stb1.st_size); + prepare(1, f2, stb2.st_size); + prune(); + sort(sfile[0], slen[0]); + sort(sfile[1], slen[1]); + + member = (int *) file[1]; + equiv(sfile[0], slen[0], sfile[1], slen[1], member); + member = xrealloc(member, (slen[1] + 2) * sizeof(int)); + + class = (int *) file[0]; + unsort(sfile[0], slen[0], class); + class = xrealloc(class, (slen[0] + 2) * sizeof(int)); + + klist = xmalloc((slen[0] + 2) * sizeof(int)); + clen = 0; + clistlen = 100; + clist = xmalloc(clistlen * sizeof(struct cand)); + i = stone(class, slen[0], member, klist); + free(member); + free(class); + + J = xrealloc(J, (len[0] + 2) * sizeof(int)); + unravel(klist[i]); + free(clist); + free(klist); + + ixold = xrealloc(ixold, (len[0] + 2) * sizeof(long)); + ixnew = xrealloc(ixnew, (len[1] + 2) * sizeof(long)); + check(f1, f2); + output(file1, f1, file2, f2); + + closem: + if (anychange) { + status |= 1; + if (rval == D_SAME) + rval = D_DIFFER; + } + if (f1 != NULL) + fclose(f1); + if (f2 != NULL) + fclose(f2); + if (file1 != ofile1) + free(file1); + if (file2 != ofile2) + free(file2); + return rval; +} + +#if ENABLE_FEATURE_DIFF_DIR +static void do_diff(char *dir1, char *path1, char *dir2, char *path2) +{ + + int flags = D_HEADER; + int val; + + char *fullpath1 = xasprintf("%s/%s", dir1, path1); + char *fullpath2 = xasprintf("%s/%s", dir2, path2); + + if (stat(fullpath1, &stb1) != 0) { + flags |= D_EMPTY1; + memset(&stb1, 0, sizeof(stb1)); + fullpath1 = xasprintf("%s/%s", dir1, path2); + } + if (stat(fullpath2, &stb2) != 0) { + flags |= D_EMPTY2; + memset(&stb2, 0, sizeof(stb2)); + stb2.st_mode = stb1.st_mode; + fullpath2 = xasprintf("%s/%s", dir2, path1); + } + + if (stb1.st_mode == 0) + stb1.st_mode = stb2.st_mode; + + if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { + printf("Common subdirectories: %s and %s\n", fullpath1, fullpath2); + return; + } + + if (!S_ISREG(stb1.st_mode) && !S_ISDIR(stb1.st_mode)) + val = D_SKIPPED1; + else if (!S_ISREG(stb2.st_mode) && !S_ISDIR(stb2.st_mode)) + val = D_SKIPPED2; + else + val = diffreg(fullpath1, fullpath2, flags); + + print_status(val, fullpath1, fullpath2, NULL); +} +#endif + +#if ENABLE_FEATURE_DIFF_DIR +static int dir_strcmp(const void *p1, const void *p2) +{ + return strcmp(*(char *const *) p1, *(char *const *) p2); +} + +/* This function adds a filename to dl, the directory listing. */ + +static int add_to_dirlist(const char *filename, + struct stat ATTRIBUTE_UNUSED * sb, void *userdata, + int depth ATTRIBUTE_UNUSED) +{ + dl_count++; + dl = xrealloc(dl, dl_count * sizeof(char *)); + dl[dl_count - 1] = xstrdup(filename); + if (cmd_flags & FLAG_r) { + int *pp = (int *) userdata; + int path_len = *pp + 1; + + dl[dl_count - 1] = &(dl[dl_count - 1])[path_len]; + } + return TRUE; +} + +/* This returns a sorted directory listing. */ +static char **get_dir(char *path) +{ + + int i; + char **retval; + + /* If -r has been set, then the recursive_action function will be + * used. Unfortunately, this outputs the root directory along with + * the recursed paths, so use void *userdata to specify the string + * length of the root directory. It can then be removed in + * add_to_dirlist. */ + + int path_len = strlen(path); + void *userdata = &path_len; + + /* Reset dl_count - there's no need to free dl as xrealloc does + * the job nicely. */ + dl_count = 0; + + /* Now fill dl with a listing. */ + if (cmd_flags & FLAG_r) + recursive_action(path, TRUE, TRUE, FALSE, add_to_dirlist, NULL, + userdata, 0); + else { + DIR *dp; + struct dirent *ep; + + dp = warn_opendir(path); + while ((ep = readdir(dp))) { + if ((!strcmp(ep->d_name, "..")) || (!strcmp(ep->d_name, "."))) + continue; + add_to_dirlist(ep->d_name, NULL, NULL, 0); + } + closedir(dp); + } + + /* Sort dl alphabetically. */ + qsort(dl, dl_count, sizeof(char *), dir_strcmp); + + /* Copy dl so that we can return it. */ + retval = xmalloc(dl_count * sizeof(char *)); + for (i = 0; i < dl_count; i++) + retval[i] = xstrdup(dl[i]); + + return retval; +} + +static void diffdir(char *p1, char *p2) +{ + + char **dirlist1, **dirlist2; + char *dp1, *dp2; + int dirlist1_count, dirlist2_count; + int pos; + + /* Check for trailing slashes. */ + + dp1 = last_char_is(p1, '/'); + if (dp1 != NULL) + *dp1 = '\0'; + dp2 = last_char_is(p2, '/'); + if (dp2 != NULL) + *dp2 = '\0'; + + /* Get directory listings for p1 and p2. */ + + dirlist1 = get_dir(p1); + dirlist1_count = dl_count; + dirlist1[dirlist1_count] = NULL; + dirlist2 = get_dir(p2); + dirlist2_count = dl_count; + dirlist2[dirlist2_count] = NULL; + + /* If -S was set, find the starting point. */ + if (start) { + while (*dirlist1 != NULL && strcmp(*dirlist1, start) < 0) + dirlist1++; + while (*dirlist2 != NULL && strcmp(*dirlist2, start) < 0) + dirlist2++; + if ((*dirlist1 == NULL) || (*dirlist2 == NULL)) + bb_error_msg(bb_msg_invalid_arg, "NULL", "-S"); + } + + /* Now that both dirlist1 and dirlist2 contain sorted directory + * listings, we can start to go through dirlist1. If both listings + * contain the same file, then do a normal diff. Otherwise, behaviour + * is determined by whether the -N flag is set. */ + while (*dirlist1 != NULL || *dirlist2 != NULL) { + dp1 = *dirlist1; + dp2 = *dirlist2; + pos = dp1 == NULL ? 1 : dp2 == NULL ? -1 : strcmp(dp1, dp2); + if (pos == 0) { + do_diff(p1, dp1, p2, dp2); + dirlist1++; + dirlist2++; + } else if (pos < 0) { + if (cmd_flags & FLAG_N) + do_diff(p1, dp1, p2, NULL); + else + print_only(p1, strlen(p1) + 1, dp1); + dirlist1++; + } else { + if (cmd_flags & FLAG_N) + do_diff(p1, NULL, p2, dp2); + else + print_only(p2, strlen(p2) + 1, dp2); + dirlist2++; + } + } +} +#endif + + + +int diff_main(int argc, char **argv) +{ + int gotstdin = 0; + + char *U_opt; + llist_t *L_arg = NULL; + + opt_complementary = "L::"; + cmd_flags = getopt32(argc, argv, "abdiL:NqrsS:tTU:wu", + &L_arg, &start, &U_opt); + + if (cmd_flags & FLAG_L) { + while (L_arg) { + if (label[0] == NULL) + label[0] = L_arg->data; + else if (label[1] == NULL) + label[1] = L_arg->data; + else + bb_show_usage(); + + L_arg = L_arg->link; + } + + /* If both label[0] and label[1] were set, they need to be swapped. */ + if (label[0] && label[1]) { + char *tmp; + + tmp = label[1]; + label[1] = label[0]; + label[0] = tmp; + } + } + + context = 3; /* This is the default number of lines of context. */ + if (cmd_flags & FLAG_U) { + context = xatoul_range(U_opt, 1, INT_MAX); + } + argc -= optind; + argv += optind; + + /* + * Do sanity checks, fill in stb1 and stb2 and call the appropriate + * driver routine. Both drivers use the contents of stb1 and stb2. + */ + if (argc < 2) { + bb_error_msg("missing filename"); + bb_show_usage(); + } + if (strcmp(argv[0], "-") == 0) { + fstat(STDIN_FILENO, &stb1); + gotstdin = 1; + } else + xstat(argv[0], &stb1); + if (strcmp(argv[1], "-") == 0) { + fstat(STDIN_FILENO, &stb2); + gotstdin = 1; + } else + xstat(argv[1], &stb2); + if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode))) + bb_error_msg_and_die("can't compare - to a directory"); + if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { +#if ENABLE_FEATURE_DIFF_DIR + diffdir(argv[0], argv[1]); +#else + bb_error_msg_and_die("directory comparison not supported"); +#endif + } else { + if (S_ISDIR(stb1.st_mode)) { + argv[0] = concat_path_file(argv[0], argv[1]); + xstat(argv[0], &stb1); + } + if (S_ISDIR(stb2.st_mode)) { + argv[1] = concat_path_file(argv[1], argv[0]); + xstat(argv[1], &stb2); + } + print_status(diffreg(argv[0], argv[1], 0), argv[0], argv[1], NULL); + } + exit(status); +} diff --git a/coreutils/dirname.c b/coreutils/dirname.c new file mode 100644 index 000000000..e986a9701 --- /dev/null +++ b/coreutils/dirname.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini dirname implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/dirname.html */ + +#include +#include +#include "busybox.h" + +int dirname_main(int argc, char **argv) +{ + if (argc != 2) { + bb_show_usage(); + } + + puts(dirname(argv[1])); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/dos2unix.c b/coreutils/dos2unix.c new file mode 100644 index 000000000..1ed8771c0 --- /dev/null +++ b/coreutils/dos2unix.c @@ -0,0 +1,115 @@ +/* vi: set sw=4 ts=4: */ +/* + * dos2unix for BusyBox + * + * dos2unix '\n' convertor 0.5.0 + * based on Unix2Dos 0.9.0 by Peter Hanecak (made 19.2.1997) + * Copyright 1997,.. by Peter Hanecak . + * All rights reserved. + * + * dos2unix filters reading input from stdin and writing output to stdout. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +*/ + +#include "busybox.h" + +enum ConvType { + CT_UNIX2DOS = 1, + CT_DOS2UNIX +} ConvType; + +/* if fn is NULL then input is stdin and output is stdout */ +static int convert(char *fn) +{ + FILE *in, *out; + int i; + + if (fn != NULL) { + in = xfopen(fn, "rw"); + /* + The file is then created with mode read/write and + permissions 0666 for glibc 2.0.6 and earlier or + 0600 for glibc 2.0.7 and later. + */ + snprintf(bb_common_bufsiz1, sizeof(bb_common_bufsiz1), "%sXXXXXX", fn); + /* + sizeof bb_common_bufsiz1 is 4096, so it should be big enough to + hold the full path. However if the output is truncated the + subsequent call to mkstemp would fail. + */ + if ((i = mkstemp(&bb_common_bufsiz1[0])) == -1 + || chmod(bb_common_bufsiz1, 0600) == -1) { + bb_perror_nomsg_and_die(); + } + out = fdopen(i, "w+"); + if (!out) { + close(i); + remove(bb_common_bufsiz1); + } + } else { + in = stdin; + out = stdout; + } + + while ((i = fgetc(in)) != EOF) { + if (i == '\r') + continue; + if (i == '\n') { + if (ConvType == CT_UNIX2DOS) + fputc('\r', out); + fputc('\n', out); + continue; + } + fputc(i, out); + } + + if (fn != NULL) { + if (fclose(in) < 0 || fclose(out) < 0) { + bb_perror_nomsg(); + remove(bb_common_bufsiz1); + return -2; + } + /* Assume they are both on the same filesystem (which + * should be true since we put them into the same directory + * so we _should_ be ok, but you never know... */ + if (rename(bb_common_bufsiz1, fn) < 0) { + bb_perror_msg("cannot rename '%s' as '%s'", bb_common_bufsiz1, fn); + return -1; + } + } + + return 0; +} + +int dos2unix_main(int argc, char *argv[]) +{ + int o; + + /* See if we are supposed to be doing dos2unix or unix2dos */ + if (applet_name[0] == 'd') { + ConvType = CT_DOS2UNIX; /*2 */ + } else { + ConvType = CT_UNIX2DOS; /*1 */ + } + /* -u and -d are mutally exclusive */ + opt_complementary = "?:u--d:d--u"; + /* process parameters */ + /* -u convert to unix */ + /* -d convert to dos */ + o = getopt32(argc, argv, "du"); + + /* Do the conversion requested by an argument else do the default + * conversion depending on our name. */ + if (o) + ConvType = o; + + if (optind < argc) { + while (optind < argc) + if ((o = convert(argv[optind++])) < 0) + break; + } else + o = convert(NULL); + + return o; +} diff --git a/coreutils/du.c b/coreutils/du.c new file mode 100644 index 000000000..a547b1e14 --- /dev/null +++ b/coreutils/du.c @@ -0,0 +1,250 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini du implementation for busybox + * + * Copyright (C) 1999,2000,2001 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * Copyright (C) 2002 Edward Betts + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant (unless default blocksize set to 1k) */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/du.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Mostly rewritten for SUSv3 compliance and to fix bugs/defects. + * 1) Added support for SUSv3 -a, -H, -L, gnu -c, and (busybox) -d options. + * The -d option allows setting of max depth (similar to gnu --max-depth). + * 2) Fixed incorrect size calculations for links and directories, especially + * when errors occurred. Calculates sizes should now match gnu du output. + * 3) Added error checking of output. + * 4) Fixed busybox bug #1284 involving long overflow with human_readable. + */ + +#include "busybox.h" + +#ifdef CONFIG_FEATURE_HUMAN_READABLE +# ifdef CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K +static unsigned long disp_hr = 1024; +# else +static unsigned long disp_hr = 512; +# endif +#elif defined CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K +static unsigned int disp_k = 1; +#else +static unsigned int disp_k; /* bss inits to 0 */ +#endif + +static int max_print_depth = INT_MAX; +static nlink_t count_hardlinks = 1; + +static int status +#if EXIT_SUCCESS == 0 + = EXIT_SUCCESS +#endif + ; +static int print_files; +static int slink_depth; +static int du_depth; +static int one_file_system; +static dev_t dir_dev; + + +static void print(long size, const char * const filename) +{ + /* TODO - May not want to defer error checking here. */ +#ifdef CONFIG_FEATURE_HUMAN_READABLE + printf("%s\t%s\n", make_human_readable_str(size, 512, disp_hr), + filename); +#else + if (disp_k) { + size++; + size >>= 1; + } + printf("%ld\t%s\n", size, filename); +#endif +} + +/* tiny recursive du */ +static long du(const char * const filename) +{ + struct stat statbuf; + long sum; + + if ((lstat(filename, &statbuf)) != 0) { + bb_perror_msg("%s", filename); + status = EXIT_FAILURE; + return 0; + } + + if (one_file_system) { + if (du_depth == 0) { + dir_dev = statbuf.st_dev; + } else if (dir_dev != statbuf.st_dev) { + return 0; + } + } + + sum = statbuf.st_blocks; + + if (S_ISLNK(statbuf.st_mode)) { + if (slink_depth > du_depth) { /* -H or -L */ + if ((stat(filename, &statbuf)) != 0) { + bb_perror_msg("%s", filename); + status = EXIT_FAILURE; + return 0; + } + sum = statbuf.st_blocks; + if (slink_depth == 1) { + slink_depth = INT_MAX; /* Convert -H to -L. */ + } + } + } + + if (statbuf.st_nlink > count_hardlinks) { + /* Add files/directories with links only once */ + if (is_in_ino_dev_hashtable(&statbuf, NULL)) { + return 0; + } + add_to_ino_dev_hashtable(&statbuf, NULL); + } + + if (S_ISDIR(statbuf.st_mode)) { + DIR *dir; + struct dirent *entry; + char *newfile; + + dir = warn_opendir(filename); + if (!dir) { + status = EXIT_FAILURE; + return sum; + } + + newfile = last_char_is(filename, '/'); + if (newfile) + *newfile = '\0'; + + while ((entry = readdir(dir))) { + char *name = entry->d_name; + + newfile = concat_subpath_file(filename, name); + if(newfile == NULL) + continue; + ++du_depth; + sum += du(newfile); + --du_depth; + free(newfile); + } + closedir(dir); + } else if (du_depth > print_files) { + return sum; + } + if (du_depth <= max_print_depth) { + print(sum, filename); + } + return sum; +} + +int du_main(int argc, char **argv) +{ + long total; + int slink_depth_save; + int print_final_total; + char *smax_print_depth; + unsigned opt; + +#ifdef CONFIG_FEATURE_DU_DEFUALT_BLOCKSIZE_1K + if (getenv("POSIXLY_CORRECT")) { /* TODO - a new libbb function? */ +#ifdef CONFIG_FEATURE_HUMAN_READABLE + disp_hr = 512; +#else + disp_k = 0; +#endif + } +#endif + + /* Note: SUSv3 specifies that -a and -s options cannot be used together + * in strictly conforming applications. However, it also says that some + * du implementations may produce output when -a and -s are used together. + * gnu du exits with an error code in this case. We choose to simply + * ignore -a. This is consistent with -s being equivalent to -d 0. + */ +#ifdef CONFIG_FEATURE_HUMAN_READABLE + opt_complementary = "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s"; + opt = getopt32(argc, argv, "aHkLsx" "d:" "lc" "hm", &smax_print_depth); + if((opt & (1 << 9))) { + /* -h opt */ + disp_hr = 0; + } + if((opt & (1 << 10))) { + /* -m opt */ + disp_hr = 1024*1024; + } + if((opt & (1 << 2))) { + /* -k opt */ + disp_hr = 1024; + } +#else + opt_complementary = "H-L:L-H:s-d:d-s"; + opt = getopt32(argc, argv, "aHkLsx" "d:" "lc", &smax_print_depth); +#if !defined CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K + if((opt & (1 << 2))) { + /* -k opt */ + disp_k = 1; + } +#endif +#endif + if((opt & (1 << 0))) { + /* -a opt */ + print_files = INT_MAX; + } + if((opt & (1 << 1))) { + /* -H opt */ + slink_depth = 1; + } + if((opt & (1 << 3))) { + /* -L opt */ + slink_depth = INT_MAX; + } + if((opt & (1 << 4))) { + /* -s opt */ + max_print_depth = 0; + } + one_file_system = opt & (1 << 5); /* -x opt */ + if((opt & (1 << 6))) { + /* -d opt */ + max_print_depth = xatoi_u(smax_print_depth); + } + if((opt & (1 << 7))) { + /* -l opt */ + count_hardlinks = INT_MAX; + } + print_final_total = opt & (1 << 8); /* -c opt */ + + /* go through remaining args (if any) */ + argv += optind; + if (optind >= argc) { + *--argv = "."; + if (slink_depth == 1) { + slink_depth = 0; + } + } + + slink_depth_save = slink_depth; + total = 0; + do { + total += du(*argv); + slink_depth = slink_depth_save; + } while (*++argv); +#ifdef CONFIG_FEATURE_CLEAN_UP + reset_ino_dev_hashtable(); +#endif + + if (print_final_total) { + print(total, "total"); + } + + fflush_stdout_and_exit(status); +} diff --git a/coreutils/echo.c b/coreutils/echo.c new file mode 100644 index 000000000..99063ae52 --- /dev/null +++ b/coreutils/echo.c @@ -0,0 +1,158 @@ +/* vi: set sw=4 ts=4: */ +/* + * echo implementation for busybox + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Original copyright notice is retained at the end of this file. + */ + +/* BB_AUDIT SUSv3 compliant -- unless configured as fancy echo. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/echo.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Because of behavioral differences, implemented configurable SUSv3 + * or 'fancy' gnu-ish behaviors. Also, reduced size and fixed bugs. + * 1) In handling '\c' escape, the previous version only suppressed the + * trailing newline. SUSv3 specifies _no_ output after '\c'. + * 2) SUSv3 specifies that octal escapes are of the form \0{#{#{#}}}. + * The previous version version did not allow 4-digit octals. + */ + + +#include +#include +#include +#include "busybox.h" + +int bb_echo(int ATTRIBUTE_UNUSED argc, char **argv) +{ +#ifndef CONFIG_FEATURE_FANCY_ECHO +#define eflag '\\' + ++argv; +#else + const char *p; + int nflag = 1; + int eflag = 0; + + while (*++argv && (**argv == '-')) { + /* If it appears that we are handling options, then make sure + * that all of the options specified are actually valid. + * Otherwise, the string should just be echoed. + */ + + if (!*(p = *argv + 1)) { /* A single '-', so echo it. */ + goto just_echo; + } + + do { + if (strrchr("neE", *p) == 0) { + goto just_echo; + } + } while (*++p); + + /* All of the options in this arg are valid, so handle them. */ + p = *argv + 1; + do { + if (*p == 'n') { + nflag = 0; + } else if (*p == 'e') { + eflag = '\\'; + } else { + eflag = 0; + } + } while (*++p); + } + +just_echo: +#endif + while (*argv) { + int c; + + while ((c = *(*argv)++)) { + if (c == eflag) { /* Check for escape seq. */ + if (**argv == 'c') { + /* '\c' means cancel newline and + * ignore all subsequent chars. */ + return 0; + } +#ifndef CONFIG_FEATURE_FANCY_ECHO + /* SUSv3 specifies that octal escapes must begin with '0'. */ + if (((unsigned int)(**argv - '1')) >= 7) +#endif + { + /* Since SUSv3 mandates a first digit of 0, 4-digit octals + * of the form \0### are accepted. */ + if ((**argv == '0') && (((unsigned int)(argv[0][1] - '0')) < 8)) { + (*argv)++; + } + /* bb_process_escape_sequence can handle nul correctly */ + c = bb_process_escape_sequence((const char **) argv); + } + } + putchar(c); + } + + if (*++argv) { + putchar(' '); + } + } + +#ifdef CONFIG_FEATURE_FANCY_ECHO + if (nflag) { + putchar('\n'); + } +#else + putchar('\n'); +#endif + return 0; +} + +int echo_main(int argc, char** argv) +{ + (void)bb_echo(argc, argv); + fflush_stdout_and_exit(EXIT_SUCCESS); +} + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. + * + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)echo.c 8.1 (Berkeley) 5/31/93 + */ diff --git a/coreutils/env.c b/coreutils/env.c new file mode 100644 index 000000000..2ce99b0ad --- /dev/null +++ b/coreutils/env.c @@ -0,0 +1,129 @@ +/* vi: set sw=4 ts=4: */ +/* + * env implementation for busybox + * + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Original copyright notice is retained at the end of this file. + * + * Modified for BusyBox by Erik Andersen + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/env.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Fixed bug involving exit return codes if execvp fails. Also added + * output error checking. + */ + +/* + * Modified by Vladimir Oleynik (C) 2003 + * - correct "-" option usage + * - multiple "-u unsetenv" support + * - GNU long option support + * - use xfunc_error_retval + */ + +#include "busybox.h" +#include +#include /* struct option */ + +#if ENABLE_FEATURE_ENV_LONG_OPTIONS +static const struct option env_long_options[] = { + { "ignore-environment", 0, NULL, 'i' }, + { "unset", 1, NULL, 'u' }, + { 0, 0, 0, 0 } +}; +#endif + +int env_main(int argc, char** argv) +{ + static char *cleanenv[1] = { NULL }; + + char **ep; + unsigned opt; + llist_t *unset_env = NULL; + extern char **environ; + + opt_complementary = "u::"; +#if ENABLE_FEATURE_ENV_LONG_OPTIONS + applet_long_options = env_long_options; +#endif + + opt = getopt32(argc, argv, "+iu:", &unset_env); + + argv += optind; + if (*argv && (argv[0][0] == '-') && !argv[0][1]) { + opt |= 1; + ++argv; + } + + if (opt & 1) + environ = cleanenv; + else if (opt & 2) { + while (unset_env) { + unsetenv(unset_env->data); + unset_env = unset_env->link; + } + } + + while (*argv && (strchr(*argv, '=') != NULL)) { + if (putenv(*argv) < 0) { + bb_perror_msg_and_die("putenv"); + } + ++argv; + } + + if (*argv) { + execvp(*argv, argv); + /* SUSv3-mandated exit codes. */ + xfunc_error_retval = (errno == ENOENT) ? 127 : 126; + bb_perror_msg_and_die("%s", *argv); + } + + for (ep = environ; *ep; ep++) { + puts(*ep); + } + + fflush_stdout_and_exit(0); +} + +/* + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + diff --git a/coreutils/expr.c b/coreutils/expr.c new file mode 100644 index 000000000..191473446 --- /dev/null +++ b/coreutils/expr.c @@ -0,0 +1,505 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini expr implementation for busybox + * + * based on GNU expr Mike Parker. + * Copyright (C) 86, 1991-1997, 1999 Free Software Foundation, Inc. + * + * Busybox modifications + * Copyright (c) 2000 Edward Betts . + * Copyright (C) 2003-2005 Vladimir Oleynik + * - reduced 464 bytes. + * - 64 math support + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* This program evaluates expressions. Each token (operator, operand, + * parenthesis) of the expression must be a separate argument. The + * parser used is a reasonably general one, though any incarnation of + * it is language-specific. It is especially nice for expressions. + * + * No parse tree is needed; a new node is evaluated immediately. + * One function can handle multiple operators all of equal precedence, + * provided they all associate ((x op x) op x). */ + +/* no getopt needed */ + +#include "busybox.h" +#include "xregex.h" + +/* The kinds of value we can have. */ +enum valtype { + integer, + string +}; +typedef enum valtype TYPE; + +#if ENABLE_EXPR_MATH_SUPPORT_64 +typedef int64_t arith_t; + +#define PF_REZ "ll" +#define PF_REZ_TYPE (long long) +#define STRTOL(s, e, b) strtoll(s, e, b) +#else +typedef long arith_t; + +#define PF_REZ "l" +#define PF_REZ_TYPE (long) +#define STRTOL(s, e, b) strtol(s, e, b) +#endif + +/* TODO: use bb_strtol[l]? It's easier to check for errors... */ + +/* A value is.... */ +struct valinfo { + TYPE type; /* Which kind. */ + union { /* The value itself. */ + arith_t i; + char *s; + } u; +}; +typedef struct valinfo VALUE; + +/* The arguments given to the program, minus the program name. */ +static char **args; + +static VALUE *docolon(VALUE * sv, VALUE * pv); +static VALUE *eval(void); +static VALUE *int_value(arith_t i); +static VALUE *str_value(char *s); +static int nextarg(char *str); +static int null(VALUE * v); +static int toarith(VALUE * v); +static void freev(VALUE * v); +static void tostring(VALUE * v); + +int expr_main(int argc, char **argv) +{ + VALUE *v; + + if (argc == 1) { + bb_error_msg_and_die("too few arguments"); + } + + args = argv + 1; + + v = eval(); + if (*args) + bb_error_msg_and_die("syntax error"); + + if (v->type == integer) + printf("%" PF_REZ "d\n", PF_REZ_TYPE v->u.i); + else + puts(v->u.s); + + fflush_stdout_and_exit(null(v)); +} + +/* Return a VALUE for I. */ + +static VALUE *int_value(arith_t i) +{ + VALUE *v; + + v = xmalloc(sizeof(VALUE)); + v->type = integer; + v->u.i = i; + return v; +} + +/* Return a VALUE for S. */ + +static VALUE *str_value(char *s) +{ + VALUE *v; + + v = xmalloc(sizeof(VALUE)); + v->type = string; + v->u.s = xstrdup(s); + return v; +} + +/* Free VALUE V, including structure components. */ + +static void freev(VALUE * v) +{ + if (v->type == string) + free(v->u.s); + free(v); +} + +/* Return nonzero if V is a null-string or zero-number. */ + +static int null(VALUE * v) +{ + if (v->type == integer) + return v->u.i == 0; + else /* string: */ + return v->u.s[0] == '\0' || strcmp(v->u.s, "0") == 0; +} + +/* Coerce V to a string value (can't fail). */ + +static void tostring(VALUE * v) +{ + if (v->type == integer) { + v->u.s = xasprintf("%" PF_REZ "d", PF_REZ_TYPE v->u.i); + v->type = string; + } +} + +/* Coerce V to an integer value. Return 1 on success, 0 on failure. */ + +static int toarith(VALUE * v) +{ + if (v->type == string) { + arith_t i; + char *e; + + /* Don't interpret the empty string as an integer. */ + /* Currently does not worry about overflow or int/long differences. */ + i = STRTOL(v->u.s, &e, 10); + if ((v->u.s == e) || *e) + return 0; + free(v->u.s); + v->u.i = i; + v->type = integer; + } + return 1; +} + +/* Return nonzero if the next token matches STR exactly. + STR must not be NULL. */ + +static int nextarg(char *str) +{ + if (*args == NULL) + return 0; + return strcmp(*args, str) == 0; +} + +/* The comparison operator handling functions. */ + +static int cmp_common(VALUE * l, VALUE * r, int op) +{ + int cmpval; + + if (l->type == string || r->type == string) { + tostring(l); + tostring(r); + cmpval = strcmp(l->u.s, r->u.s); + } else + cmpval = l->u.i - r->u.i; + if (op == '<') + return cmpval < 0; + else if (op == ('L' + 'E')) + return cmpval <= 0; + else if (op == '=') + return cmpval == 0; + else if (op == '!') + return cmpval != 0; + else if (op == '>') + return cmpval > 0; + else /* >= */ + return cmpval >= 0; +} + +/* The arithmetic operator handling functions. */ + +static arith_t arithmetic_common(VALUE * l, VALUE * r, int op) +{ + arith_t li, ri; + + if (!toarith(l) || !toarith(r)) + bb_error_msg_and_die("non-numeric argument"); + li = l->u.i; + ri = r->u.i; + if ((op == '/' || op == '%') && ri == 0) + bb_error_msg_and_die("division by zero"); + if (op == '+') + return li + ri; + else if (op == '-') + return li - ri; + else if (op == '*') + return li * ri; + else if (op == '/') + return li / ri; + else + return li % ri; +} + +/* Do the : operator. + SV is the VALUE for the lhs (the string), + PV is the VALUE for the rhs (the pattern). */ + +static VALUE *docolon(VALUE * sv, VALUE * pv) +{ + VALUE *v; + regex_t re_buffer; + const int NMATCH = 2; + regmatch_t re_regs[NMATCH]; + + tostring(sv); + tostring(pv); + + if (pv->u.s[0] == '^') { + fprintf(stderr, "\ +warning: unportable BRE: `%s': using `^' as the first character\n\ +of a basic regular expression is not portable; it is being ignored", pv->u.s); + } + + memset(&re_buffer, 0, sizeof(re_buffer)); + memset(re_regs, 0, sizeof(*re_regs)); + if (regcomp(&re_buffer, pv->u.s, 0) != 0) + bb_error_msg_and_die("invalid regular expression"); + + /* expr uses an anchored pattern match, so check that there was a + * match and that the match starts at offset 0. */ + if (regexec(&re_buffer, sv->u.s, NMATCH, re_regs, 0) != REG_NOMATCH && + re_regs[0].rm_so == 0) { + /* Were \(...\) used? */ + if (re_buffer.re_nsub > 0) { + sv->u.s[re_regs[1].rm_eo] = '\0'; + v = str_value(sv->u.s + re_regs[1].rm_so); + } else + v = int_value(re_regs[0].rm_eo); + } else { + /* Match failed -- return the right kind of null. */ + if (re_buffer.re_nsub > 0) + v = str_value(""); + else + v = int_value(0); + } + return v; +} + +/* Handle bare operands and ( expr ) syntax. */ + +static VALUE *eval7(void) +{ + VALUE *v; + + if (!*args) + bb_error_msg_and_die("syntax error"); + + if (nextarg("(")) { + args++; + v = eval(); + if (!nextarg(")")) + bb_error_msg_and_die("syntax error"); + args++; + return v; + } + + if (nextarg(")")) + bb_error_msg_and_die("syntax error"); + + return str_value(*args++); +} + +/* Handle match, substr, index, length, and quote keywords. */ + +static VALUE *eval6(void) +{ + VALUE *l, *r, *v, *i1, *i2; + + if (nextarg("quote")) { + args++; + if (!*args) + bb_error_msg_and_die("syntax error"); + return str_value(*args++); + } else if (nextarg("length")) { + args++; + r = eval6(); + tostring(r); + v = int_value(strlen(r->u.s)); + freev(r); + return v; + } else if (nextarg("match")) { + args++; + l = eval6(); + r = eval6(); + v = docolon(l, r); + freev(l); + freev(r); + return v; + } else if (nextarg("index")) { + args++; + l = eval6(); + r = eval6(); + tostring(l); + tostring(r); + v = int_value(strcspn(l->u.s, r->u.s) + 1); + if (v->u.i == (arith_t) strlen(l->u.s) + 1) + v->u.i = 0; + freev(l); + freev(r); + return v; + } else if (nextarg("substr")) { + args++; + l = eval6(); + i1 = eval6(); + i2 = eval6(); + tostring(l); + if (!toarith(i1) || !toarith(i2) + || i1->u.i > (arith_t) strlen(l->u.s) + || i1->u.i <= 0 || i2->u.i <= 0) + v = str_value(""); + else { + v = xmalloc(sizeof(VALUE)); + v->type = string; + v->u.s = xstrndup(l->u.s + i1->u.i - 1, i2->u.i); + } + freev(l); + freev(i1); + freev(i2); + return v; + } else + return eval7(); +} + +/* Handle : operator (pattern matching). + Calls docolon to do the real work. */ + +static VALUE *eval5(void) +{ + VALUE *l, *r, *v; + + l = eval6(); + while (nextarg(":")) { + args++; + r = eval6(); + v = docolon(l, r); + freev(l); + freev(r); + l = v; + } + return l; +} + +/* Handle *, /, % operators. */ + +static VALUE *eval4(void) +{ + VALUE *l, *r; + int op; + arith_t val; + + l = eval5(); + while (1) { + if (nextarg("*")) + op = '*'; + else if (nextarg("/")) + op = '/'; + else if (nextarg("%")) + op = '%'; + else + return l; + args++; + r = eval5(); + val = arithmetic_common(l, r, op); + freev(l); + freev(r); + l = int_value(val); + } +} + +/* Handle +, - operators. */ + +static VALUE *eval3(void) +{ + VALUE *l, *r; + int op; + arith_t val; + + l = eval4(); + while (1) { + if (nextarg("+")) + op = '+'; + else if (nextarg("-")) + op = '-'; + else + return l; + args++; + r = eval4(); + val = arithmetic_common(l, r, op); + freev(l); + freev(r); + l = int_value(val); + } +} + +/* Handle comparisons. */ + +static VALUE *eval2(void) +{ + VALUE *l, *r; + int op; + arith_t val; + + l = eval3(); + while (1) { + if (nextarg("<")) + op = '<'; + else if (nextarg("<=")) + op = 'L' + 'E'; + else if (nextarg("=") || nextarg("==")) + op = '='; + else if (nextarg("!=")) + op = '!'; + else if (nextarg(">=")) + op = 'G' + 'E'; + else if (nextarg(">")) + op = '>'; + else + return l; + args++; + r = eval3(); + toarith(l); + toarith(r); + val = cmp_common(l, r, op); + freev(l); + freev(r); + l = int_value(val); + } +} + +/* Handle &. */ + +static VALUE *eval1(void) +{ + VALUE *l, *r; + + l = eval2(); + while (nextarg("&")) { + args++; + r = eval2(); + if (null(l) || null(r)) { + freev(l); + freev(r); + l = int_value(0); + } else + freev(r); + } + return l; +} + +/* Handle |. */ + +static VALUE *eval(void) +{ + VALUE *l, *r; + + l = eval1(); + while (nextarg("|")) { + args++; + r = eval1(); + if (null(l)) { + freev(l); + l = r; + } else + freev(r); + } + return l; +} diff --git a/coreutils/false.c b/coreutils/false.c new file mode 100644 index 000000000..084bc0c1e --- /dev/null +++ b/coreutils/false.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini false implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/false.html */ + +#include +#include "busybox.h" + +int false_main(int ATTRIBUTE_UNUSED argc, char ATTRIBUTE_UNUSED **argv) +{ + return EXIT_FAILURE; +} diff --git a/coreutils/fold.c b/coreutils/fold.c new file mode 100644 index 000000000..9ca693dff --- /dev/null +++ b/coreutils/fold.c @@ -0,0 +1,154 @@ +/* vi: set sw=4 ts=4: */ +/* fold -- wrap each input line to fit in specified width. + + Written by David MacKenzie, djm@gnu.ai.mit.edu. + Copyright (C) 91, 1995-2002 Free Software Foundation, Inc. + + Modified for busybox based on coreutils v 5.0 + Copyright (C) 2003 Glenn McGrath + + Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +*/ + +#include "busybox.h" + +static unsigned long flags; +#define FLAG_COUNT_BYTES 1 +#define FLAG_BREAK_SPACES 2 +#define FLAG_WIDTH 4 + +/* Assuming the current column is COLUMN, return the column that + printing C will move the cursor to. + The first column is 0. */ + +static int adjust_column(int column, char c) +{ + if (!(flags & FLAG_COUNT_BYTES)) { + if (c == '\b') { + if (column > 0) + column--; + } else if (c == '\r') + column = 0; + else if (c == '\t') + column = column + 8 - column % 8; + else /* if (isprint (c)) */ + column++; + } else + column++; + return column; +} + +int fold_main(int argc, char **argv) +{ + char *w_opt; + int width = 80; + int i; + int errs = 0; + + if(!ENABLE_DEBUG_YANK_SUSv2) { + /* Turn any numeric options into -w options. */ + for (i = 1; i < argc; i++) { + char const *a = argv[i]; + + if (*a++ == '-') { + if (*a == '-' && !a[1]) + break; + if (isdigit(*a)) { + argv[i] = xasprintf("-w%s", a); + } + } + } + } + + flags = getopt32(argc, argv, "bsw:", &w_opt); + if (flags & FLAG_WIDTH) + width = xatoul_range(w_opt, 1, 10000); + + argv += optind; + if (!*argv) { + *--argv = "-"; + } + + do { + FILE *istream = fopen_or_warn_stdin(*argv); + int c; + int column = 0; /* Screen column where next char will go. */ + int offset_out = 0; /* Index in `line_out' for next char. */ + static char *line_out = NULL; + static int allocated_out = 0; + + if (istream == NULL) { + errs |= EXIT_FAILURE; + continue; + } + + while ((c = getc(istream)) != EOF) { + if (offset_out + 1 >= allocated_out) { + allocated_out += 1024; + line_out = xrealloc(line_out, allocated_out); + } + + if (c == '\n') { + line_out[offset_out++] = c; + fwrite(line_out, sizeof(char), (size_t) offset_out, stdout); + column = offset_out = 0; + continue; + } + +rescan: + column = adjust_column(column, c); + + if (column > width) { + /* This character would make the line too long. + Print the line plus a newline, and make this character + start the next line. */ + if (flags & FLAG_BREAK_SPACES) { + /* Look for the last blank. */ + int logical_end; + + for (logical_end = offset_out - 1; logical_end >= 0; logical_end--) { + if (isblank(line_out[logical_end])) { + break; + } + } + if (logical_end >= 0) { + /* Found a blank. Don't output the part after it. */ + logical_end++; + fwrite(line_out, sizeof(char), (size_t) logical_end, stdout); + putchar('\n'); + /* Move the remainder to the beginning of the next line. + The areas being copied here might overlap. */ + memmove(line_out, line_out + logical_end, offset_out - logical_end); + offset_out -= logical_end; + for (column = i = 0; i < offset_out; i++) { + column = adjust_column(column, line_out[i]); + } + goto rescan; + } + } else { + if (offset_out == 0) { + line_out[offset_out++] = c; + continue; + } + } + line_out[offset_out++] = '\n'; + fwrite(line_out, sizeof(char), (size_t) offset_out, stdout); + column = offset_out = 0; + goto rescan; + } + + line_out[offset_out++] = c; + } + + if (offset_out) { + fwrite(line_out, sizeof(char), (size_t) offset_out, stdout); + } + + if (ferror(istream) || fclose_if_not_stdin(istream)) { + bb_perror_msg("%s", *argv); /* Avoid multibyte problems. */ + errs |= EXIT_FAILURE; + } + } while (*++argv); + + fflush_stdout_and_exit(errs); +} diff --git a/coreutils/head.c b/coreutils/head.c new file mode 100644 index 000000000..f2c948300 --- /dev/null +++ b/coreutils/head.c @@ -0,0 +1,139 @@ +/* vi: set sw=4 ts=4: */ +/* + * head implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* BB_AUDIT GNU compatible -c, -q, and -v options in 'fancy' configuration. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/head.html */ + +#include "busybox.h" + +static const char head_opts[] = + "n:" +#if ENABLE_FEATURE_FANCY_HEAD + "c:qv" +#endif + ; + +#if ENABLE_FEATURE_FANCY_HEAD +static const struct suffix_mult head_suffixes[] = { + { "b", 512 }, + { "k", 1024 }, + { "m", 1024*1024 }, + { NULL, 0 } +}; +#endif + +static const char header_fmt_str[] = "\n==> %s <==\n"; + +int head_main(int argc, char **argv) +{ + unsigned long count = 10; + unsigned long i; +#if ENABLE_FEATURE_FANCY_HEAD + int count_bytes = 0; + int header_threshhold = 1; +#endif + + FILE *fp; + const char *fmt; + char *p; + int opt; + int c; + int retval = EXIT_SUCCESS; + +#if !ENABLE_DEBUG_YANK_SUSv2 || ENABLE_FEATURE_FANCY_HEAD + /* Allow legacy syntax of an initial numeric option without -n. */ + if (argc > 1 && argv[1][0] == '-' + && isdigit(argv[1][1]) + ) { + --argc; + ++argv; + p = (*argv) + 1; + goto GET_COUNT; + } +#endif + + /* No size benefit in converting this to getopt32 */ + while ((opt = getopt(argc, argv, head_opts)) > 0) { + switch (opt) { +#if ENABLE_FEATURE_FANCY_HEAD + case 'q': + header_threshhold = INT_MAX; + break; + case 'v': + header_threshhold = -1; + break; + case 'c': + count_bytes = 1; + /* fall through */ +#endif + case 'n': + p = optarg; +#if !ENABLE_DEBUG_YANK_SUSv2 || ENABLE_FEATURE_FANCY_HEAD + GET_COUNT: +#endif + +#if !ENABLE_FEATURE_FANCY_HEAD + count = xatoul(p); +#else + count = xatoul_sfx(p, head_suffixes); +#endif + break; + default: + bb_show_usage(); + } + } + + argv += optind; + if (!*argv) { + *--argv = "-"; + } + + fmt = header_fmt_str + 1; +#if ENABLE_FEATURE_FANCY_HEAD + if (argc - optind <= header_threshhold) { + header_threshhold = 0; + } +#else + if (argc <= optind + 1) { + fmt += 11; + } + /* Now define some things here to avoid #ifdefs in the code below. + * These should optimize out of the if conditions below. */ +#define header_threshhold 1 +#define count_bytes 0 +#endif + + do { + fp = fopen_or_warn_stdin(*argv); + if (fp) { + if (fp == stdin) { + *argv = (char *) bb_msg_standard_input; + } + if (header_threshhold) { + printf(fmt, *argv); + } + i = count; + while (i && ((c = getc(fp)) != EOF)) { + if (count_bytes || (c == '\n')) { + --i; + } + putchar(c); + } + if (fclose_if_not_stdin(fp)) { + bb_perror_msg("%s", *argv); /* Avoid multibyte problems. */ + retval = EXIT_FAILURE; + } + die_if_ferror_stdout(); + } + fmt = header_fmt_str; + } while (*++argv); + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/hostid.c b/coreutils/hostid.c new file mode 100644 index 000000000..65447aa63 --- /dev/null +++ b/coreutils/hostid.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini hostid implementation for busybox + * + * Copyright (C) 2000 Edward Betts . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +#include +#include +#include "busybox.h" + +int hostid_main(int argc, char ATTRIBUTE_UNUSED **argv) +{ + if (argc > 1) { + bb_show_usage(); + } + + printf("%lx\n", gethostid()); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/id.c b/coreutils/id.c new file mode 100644 index 000000000..66874a71e --- /dev/null +++ b/coreutils/id.c @@ -0,0 +1,111 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini id implementation for busybox + * + * Copyright (C) 2000 by Randolph Chung + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 _NOT_ compliant -- option -G is not currently supported. */ +/* Hacked by Tito Ragusa (C) 2004 to handle usernames of whatever length and to + * be more similar to GNU id. + */ + +#include "busybox.h" +#include +#include +#include + +#ifdef CONFIG_SELINUX +#include /* for is_selinux_enabled() */ +#endif + +#define PRINT_REAL 1 +#define NAME_NOT_NUMBER 2 +#define JUST_USER 4 +#define JUST_GROUP 8 + +static short printf_full(unsigned int id, const char *arg, const char prefix) +{ + const char *fmt = "%cid=%u"; + short status = EXIT_FAILURE; + + if (arg) { + fmt = "%cid=%u(%s)"; + status = EXIT_SUCCESS; + } + printf(fmt, prefix, id, arg); + return status; +} + +int id_main(int argc, char **argv) +{ + struct passwd *p; + uid_t uid; + gid_t gid; + unsigned long flags; + short status; + + /* Don't allow -n -r -nr -ug -rug -nug -rnug */ + /* Don't allow more than one username */ + opt_complementary = "?1:?:u--g:g--u:r?ug:n?ug"; + flags = getopt32(argc, argv, "rnug"); + + /* This values could be overwritten later */ + uid = geteuid(); + gid = getegid(); + if (flags & PRINT_REAL) { + uid = getuid(); + gid = getgid(); + } + + if (argv[optind]) { + p = getpwnam(argv[optind]); + /* bb_xgetpwnam is needed because it exits on failure */ + uid = bb_xgetpwnam(argv[optind]); + gid = p->pw_gid; + /* in this case PRINT_REAL is the same */ + } + + if (flags & (JUST_GROUP | JUST_USER)) { + /* JUST_GROUP and JUST_USER are mutually exclusive */ + if (flags & NAME_NOT_NUMBER) { + /* bb_getpwuid and bb_getgrgid exit on failure so puts cannot segfault */ + puts((flags & JUST_USER) ? bb_getpwuid(NULL, uid, -1 ) : bb_getgrgid(NULL, gid, -1 )); + } else { + printf("%u\n", (flags & JUST_USER) ? uid : gid); + } + /* exit */ + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + /* Print full info like GNU id */ + /* bb_getpwuid doesn't exit on failure here */ + status = printf_full(uid, bb_getpwuid(NULL, uid, 0), 'u'); + putchar(' '); + /* bb_getgrgid doesn't exit on failure here */ + status |= printf_full(gid, bb_getgrgid(NULL, gid, 0), 'g'); + +#ifdef CONFIG_SELINUX + if (is_selinux_enabled()) { + security_context_t mysid; + char context[80]; + int len = sizeof(context); + + getcon(&mysid); + context[0] = '\0'; + if (mysid) { + len = strlen(mysid)+1; + safe_strncpy(context, mysid, len); + freecon(mysid); + } else { + safe_strncpy(context, "unknown", 8); + } + printf(" context=%s", context); + } +#endif + + putchar('\n'); + fflush_stdout_and_exit(status); +} diff --git a/coreutils/install.c b/coreutils/install.c new file mode 100644 index 000000000..3e003905e --- /dev/null +++ b/coreutils/install.c @@ -0,0 +1,131 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2003 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * TODO: -d option, need a way of recursively making directories and changing + * owner/group, will probably modify bb_make_directory(...) + */ + +#include "busybox.h" +#include "libcoreutils/coreutils.h" +#include +#include /* struct option */ + +#define INSTALL_OPT_CMD 1 +#define INSTALL_OPT_DIRECTORY 2 +#define INSTALL_OPT_PRESERVE_TIME 4 +#define INSTALL_OPT_STRIP 8 +#define INSTALL_OPT_GROUP 16 +#define INSTALL_OPT_MODE 32 +#define INSTALL_OPT_OWNER 64 + +#if ENABLE_FEATURE_INSTALL_LONG_OPTIONS +static const struct option install_long_options[] = { + { "directory", 0, NULL, 'd' }, + { "preserve-timestamps", 0, NULL, 'p' }, + { "strip", 0, NULL, 's' }, + { "group", 0, NULL, 'g' }, + { "mode", 0, NULL, 'm' }, + { "owner", 0, NULL, 'o' }, + { 0, 0, 0, 0 } +}; +#endif + +int install_main(int argc, char **argv) +{ + mode_t mode; + uid_t uid; + gid_t gid; + char *gid_str = "-1"; + char *uid_str = "-1"; + char *mode_str = "0755"; + int copy_flags = FILEUTILS_DEREFERENCE | FILEUTILS_FORCE; + int ret = EXIT_SUCCESS, flags, i, isdir; + +#if ENABLE_FEATURE_INSTALL_LONG_OPTIONS + applet_long_options = install_long_options; +#endif + opt_complementary = "?:s--d:d--s"; + /* -c exists for backwards compatibility, its needed */ + flags = getopt32(argc, argv, "cdpsg:m:o:", &gid_str, &mode_str, &uid_str); /* 'a' must be 2nd */ + + /* preserve access and modification time, this is GNU behaviour, BSD only preserves modification time */ + if (flags & INSTALL_OPT_PRESERVE_TIME) { + copy_flags |= FILEUTILS_PRESERVE_STATUS; + } + bb_parse_mode(mode_str, &mode); + gid = get_ug_id(gid_str, bb_xgetgrnam); + uid = get_ug_id(uid_str, bb_xgetpwnam); + umask(0); + + /* Create directories + * don't use bb_make_directory() as it can't change uid or gid + * perhaps bb_make_directory() should be improved. + */ + if (flags & INSTALL_OPT_DIRECTORY) { + for (argv += optind; *argv; argv++) { + char *old_argv_ptr = *argv + 1; + char *argv_ptr; + do { + argv_ptr = strchr(old_argv_ptr, '/'); + old_argv_ptr = argv_ptr; + if (argv_ptr) { + *argv_ptr = '\0'; + old_argv_ptr++; + } + if (mkdir(*argv, mode) == -1) { + if (errno != EEXIST) { + bb_perror_msg("cannot create %s", *argv); + ret = EXIT_FAILURE; + break; + } + } + else if (lchown(*argv, uid, gid) == -1) { + bb_perror_msg("cannot change ownership of %s", *argv); + ret = EXIT_FAILURE; + break; + } + if (argv_ptr) { + *argv_ptr = '/'; + } + } while (old_argv_ptr); + } + return ret; + } + + { + struct stat statbuf; + isdir = lstat(argv[argc - 1], &statbuf)<0 + ? 0 : S_ISDIR(statbuf.st_mode); + } + for (i = optind; i < argc - 1; i++) { + char *dest; + + dest = argv[argc - 1]; + if (isdir) dest = concat_path_file(argv[argc - 1], basename(argv[i])); + ret |= copy_file(argv[i], dest, copy_flags); + + /* Set the file mode */ + if (chmod(dest, mode) == -1) { + bb_perror_msg("cannot change permissions of %s", dest); + ret = EXIT_FAILURE; + } + + /* Set the user and group id */ + if (lchown(dest, uid, gid) == -1) { + bb_perror_msg("cannot change ownership of %s", dest); + ret = EXIT_FAILURE; + } + if (flags & INSTALL_OPT_STRIP) { + if (execlp("strip", "strip", dest, NULL) == -1) { + bb_error_msg("strip failed"); + ret = EXIT_FAILURE; + } + } + if(ENABLE_FEATURE_CLEAN_UP && isdir) free(dest); + } + + return ret; +} diff --git a/coreutils/length.c b/coreutils/length.c new file mode 100644 index 000000000..6413be007 --- /dev/null +++ b/coreutils/length.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ + +/* BB_AUDIT SUSv3 N/A -- Apparently a busybox (obsolete?) extension. */ + +#include +#include +#include +#include "busybox.h" + +int length_main(int argc, char **argv) +{ + if ((argc != 2) || (**(++argv) == '-')) { + bb_show_usage(); + } + + printf("%lu\n", (unsigned long)strlen(*argv)); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/libcoreutils/Kbuild b/coreutils/libcoreutils/Kbuild new file mode 100644 index 000000000..755d01f86 --- /dev/null +++ b/coreutils/libcoreutils/Kbuild @@ -0,0 +1,12 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2004 by Erik Andersen +# +# Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_MKFIFO) += getopt_mk_fifo_nod.o +lib-$(CONFIG_MKNOD) += getopt_mk_fifo_nod.o +lib-$(CONFIG_INSTALL) += cp_mv_stat.o +lib-$(CONFIG_CP) += cp_mv_stat.o +lib-$(CONFIG_MV) += cp_mv_stat.o diff --git a/coreutils/libcoreutils/coreutils.h b/coreutils/libcoreutils/coreutils.h new file mode 100644 index 000000000..102c7b5be --- /dev/null +++ b/coreutils/libcoreutils/coreutils.h @@ -0,0 +1,16 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#ifndef COREUTILS_H +#define COREUTILS_H 1 + +typedef int (*stat_func)(const char *fn, struct stat *ps); + +extern int cp_mv_stat2(const char *fn, struct stat *fn_stat, stat_func sf); +extern int cp_mv_stat(const char *fn, struct stat *fn_stat); + +extern mode_t getopt_mk_fifo_nod(int argc, char **argv); + +#endif diff --git a/coreutils/libcoreutils/cp_mv_stat.c b/coreutils/libcoreutils/cp_mv_stat.c new file mode 100644 index 000000000..2e4f25e21 --- /dev/null +++ b/coreutils/libcoreutils/cp_mv_stat.c @@ -0,0 +1,43 @@ +/* vi: set sw=4 ts=4: */ +/* + * coreutils utility routine + * + * Copyright (C) 2003 Manuel Novoa III + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "libbb.h" +#include "coreutils.h" + +int cp_mv_stat2(const char *fn, struct stat *fn_stat, stat_func sf) +{ + if (sf(fn, fn_stat) < 0) { + if (errno != ENOENT) { + bb_perror_msg("cannot stat '%s'", fn); + return -1; + } + return 0; + } else if (S_ISDIR(fn_stat->st_mode)) { + return 3; + } + return 1; +} + +int cp_mv_stat(const char *fn, struct stat *fn_stat) +{ + return cp_mv_stat2(fn, fn_stat, stat); +} diff --git a/coreutils/libcoreutils/getopt_mk_fifo_nod.c b/coreutils/libcoreutils/getopt_mk_fifo_nod.c new file mode 100644 index 000000000..3a3d34118 --- /dev/null +++ b/coreutils/libcoreutils/getopt_mk_fifo_nod.c @@ -0,0 +1,40 @@ +/* vi: set sw=4 ts=4: */ +/* + * coreutils utility routine + * + * Copyright (C) 2003 Manuel Novoa III + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include "libbb.h" +#include "coreutils.h" + +mode_t getopt_mk_fifo_nod(int argc, char **argv) +{ + mode_t mode = 0666; + char *smode = NULL; + + getopt32(argc, argv, "m:", &smode); + if(smode) { + if (bb_parse_mode(smode, &mode)) + umask(0); + } + return mode; +} diff --git a/coreutils/ln.c b/coreutils/ln.c new file mode 100644 index 000000000..231a3bf03 --- /dev/null +++ b/coreutils/ln.c @@ -0,0 +1,104 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini ln implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* BB_AUDIT GNU options missing: -d, -F, -i, and -v. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/ln.html */ + +#include "busybox.h" + +#define LN_SYMLINK 1 +#define LN_FORCE 2 +#define LN_NODEREFERENCE 4 +#define LN_BACKUP 8 +#define LN_SUFFIX 16 + +int ln_main(int argc, char **argv) +{ + int status = EXIT_SUCCESS; + int flag; + char *last; + char *src_name; + char *src; + char *suffix = "~"; + struct stat statbuf; + int (*link_func)(const char *, const char *); + + flag = getopt32(argc, argv, "sfnbS:", &suffix); + + if (argc == optind) { + bb_show_usage(); + } + + last = argv[argc - 1]; + argv += optind; + + if (argc == optind + 1) { + *--argv = last; + last = bb_get_last_path_component(xstrdup(last)); + } + + do { + src_name = NULL; + src = last; + + if (is_directory(src, + (flag & LN_NODEREFERENCE) ^ LN_NODEREFERENCE, + NULL)) { + src_name = xstrdup(*argv); + src = concat_path_file(src, bb_get_last_path_component(src_name)); + free(src_name); + src_name = src; + } + if (!(flag & LN_SYMLINK) && stat(*argv, &statbuf)) { + // coreutils: "ln dangling_symlink new_hardlink" works + if (lstat(*argv, &statbuf) || !S_ISLNK(statbuf.st_mode)) { + bb_perror_msg("%s", *argv); + status = EXIT_FAILURE; + free(src_name); + continue; + } + } + + if (flag & LN_BACKUP) { + char *backup; + backup = xasprintf("%s%s", src, suffix); + if (rename(src, backup) < 0 && errno != ENOENT) { + bb_perror_msg("%s", src); + status = EXIT_FAILURE; + free(backup); + continue; + } + free(backup); + /* + * When the source and dest are both hard links to the same + * inode, a rename may succeed even though nothing happened. + * Therefore, always unlink(). + */ + unlink(src); + } else if (flag & LN_FORCE) { + unlink(src); + } + + link_func = link; + if (flag & LN_SYMLINK) { + link_func = symlink; + } + + if (link_func(*argv, src) != 0) { + bb_perror_msg("%s", src); + status = EXIT_FAILURE; + } + + free(src_name); + + } while ((++argv)[1]); + + return status; +} diff --git a/coreutils/logname.c b/coreutils/logname.c new file mode 100644 index 000000000..695a736c6 --- /dev/null +++ b/coreutils/logname.c @@ -0,0 +1,42 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini logname implementation for busybox + * + * Copyright (C) 2000 Edward Betts . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/logname.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * SUSv3 specifies the string used is that returned from getlogin(). + * The previous implementation used getpwuid() for geteuid(), which + * is _not_ the same. Erik apparently made this change almost 3 years + * ago to avoid failing when no utmp was available. However, the + * correct course of action wrt SUSv3 for a failing getlogin() is + * a diagnostic message and an error return. + */ + +#include +#include +#include +#include "busybox.h" + +int logname_main(int argc, char ATTRIBUTE_UNUSED **argv) +{ + const char *p; + + if (argc > 1) { + bb_show_usage(); + } + + if ((p = getlogin()) != NULL) { + puts(p); + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + bb_perror_msg_and_die("getlogin"); +} diff --git a/coreutils/ls.c b/coreutils/ls.c new file mode 100644 index 000000000..960c161b0 --- /dev/null +++ b/coreutils/ls.c @@ -0,0 +1,940 @@ +/* vi: set sw=4 ts=4: */ +/* + * tiny-ls.c version 0.1.0: A minimalist 'ls' + * Copyright (C) 1996 Brian Candler + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + * To achieve a small memory footprint, this version of 'ls' doesn't do any + * file sorting, and only has the most essential command line switches + * (i.e., the ones I couldn't live without :-) All features which involve + * linking in substantial chunks of libc can be disabled. + * + * Although I don't really want to add new features to this program to + * keep it small, I *am* interested to receive bug fixes and ways to make + * it more portable. + * + * KNOWN BUGS: + * 1. ls -l of a directory doesn't give "total " header + * 2. ls of a symlink to a directory doesn't list directory contents + * 3. hidden files can make column width too large + * + * NON-OPTIMAL BEHAVIOUR: + * 1. autowidth reads directories twice + * 2. if you do a short directory listing without filetype characters + * appended, there's no need to stat each one + * PORTABILITY: + * 1. requires lstat (BSD) - how do you do it without? + */ + +#include "busybox.h" +#include + +enum { + +TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */ +COLUMN_GAP = 2, /* includes the file type char */ + +/* what is the overall style of the listing */ +STYLE_COLUMNS = 1 << 21, /* fill columns */ +STYLE_LONG = 2 << 21, /* one record per line, extended info */ +STYLE_SINGLE = 3 << 21, /* one record per line */ +STYLE_MASK = STYLE_SINGLE, + +/* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */ +/* what file information will be listed */ +LIST_INO = 1 << 0, +LIST_BLOCKS = 1 << 1, +LIST_MODEBITS = 1 << 2, +LIST_NLINKS = 1 << 3, +LIST_ID_NAME = 1 << 4, +LIST_ID_NUMERIC = 1 << 5, +LIST_CONTEXT = 1 << 6, +LIST_SIZE = 1 << 7, +LIST_DEV = 1 << 8, +LIST_DATE_TIME = 1 << 9, +LIST_FULLTIME = 1 << 10, +LIST_FILENAME = 1 << 11, +LIST_SYMLINK = 1 << 12, +LIST_FILETYPE = 1 << 13, +LIST_EXEC = 1 << 14, +LIST_MASK = (LIST_EXEC << 1) - 1, + +/* what files will be displayed */ +DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */ +DISP_HIDDEN = 1 << 16, /* show filenames starting with . */ +DISP_DOT = 1 << 17, /* show . and .. */ +DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */ +DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */ +DISP_ROWS = 1 << 20, /* print across rows */ +DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1), + +/* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */ +SORT_FORWARD = 0, /* sort in reverse order */ +SORT_REVERSE = 1 << 27, /* sort in reverse order */ + +SORT_NAME = 0, /* sort by file name */ +SORT_SIZE = 1 << 28, /* sort by file size */ +SORT_ATIME = 2 << 28, /* sort by last access time */ +SORT_CTIME = 3 << 28, /* sort by last change time */ +SORT_MTIME = 4 << 28, /* sort by last modification time */ +SORT_VERSION = 5 << 28, /* sort by version */ +SORT_EXT = 6 << 28, /* sort by file name extension */ +SORT_DIR = 7 << 28, /* sort by file or directory */ +SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES, + +/* which of the three times will be used */ +TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS, +TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS, +TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS, + +FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS, + +LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE, + +LIST_SHORT = LIST_FILENAME, +LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \ + LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK, + +SPLIT_DIR = 1, +SPLIT_FILE = 0, +SPLIT_SUBDIR = 2, + +}; + +#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f) +#define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)]) +#define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)]) +#define COLOR(mode) ("\000\043\043\043\042\000\043\043"\ + "\000\000\044\000\043\000\000\040" [TYPEINDEX(mode)]) +#define ATTR(mode) ("\00\00\01\00\01\00\01\00"\ + "\00\00\01\00\01\00\00\01" [TYPEINDEX(mode)]) + +/* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */ +#if ENABLE_FEATURE_LS_COLOR +static int show_color; +/* long option entry used only for --color, which has no short option + * equivalent */ +static const struct option ls_color_opt[] = { + { "color", optional_argument, NULL, 1 }, + { NULL, 0, NULL, 0 } +}; +#else +enum { show_color = 0 }; +#endif + +/* + * a directory entry and its stat info are stored here + */ +struct dnode { /* the basic node */ + char *name; /* the dir entry name */ + char *fullname; /* the dir entry name */ + int allocated; + struct stat dstat; /* the file stat info */ + USE_SELINUX(security_context_t sid;) + struct dnode *next; /* point at the next node */ +}; +typedef struct dnode dnode_t; + +static struct dnode **list_dir(const char *); +static struct dnode **dnalloc(int); +static int list_single(struct dnode *); + +static unsigned all_fmt; + +#if ENABLE_FEATURE_AUTOWIDTH +static unsigned tabstops = COLUMN_GAP; +static unsigned terminal_width = TERMINAL_WIDTH; +#else +enum { + tabstops = COLUMN_GAP, + terminal_width = TERMINAL_WIDTH, +}; +#endif + +static int status = EXIT_SUCCESS; + +static struct dnode *my_stat(char *fullname, char *name) +{ + struct stat dstat; + struct dnode *cur; + USE_SELINUX(security_context_t sid = NULL;) + + if (all_fmt & FOLLOW_LINKS) { +#if ENABLE_SELINUX + if (is_selinux_enabled()) { + getfilecon(fullname, &sid); + } +#endif + if (stat(fullname, &dstat)) { + bb_perror_msg("%s", fullname); + status = EXIT_FAILURE; + return 0; + } + } else { +#if ENABLE_SELINUX + if (is_selinux_enabled()) { + lgetfilecon(fullname,&sid); + } +#endif + if (lstat(fullname, &dstat)) { + bb_perror_msg("%s", fullname); + status = EXIT_FAILURE; + return 0; + } + } + + cur = xmalloc(sizeof(struct dnode)); + cur->fullname = fullname; + cur->name = name; + cur->dstat = dstat; + USE_SELINUX(cur->sid = sid;) + return cur; +} + +#if ENABLE_FEATURE_LS_COLOR +static char fgcolor(mode_t mode) +{ + /* Check wheter the file is existing (if so, color it red!) */ + if (errno == ENOENT) + return '\037'; + if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + return COLOR(0xF000); /* File is executable ... */ + return COLOR(mode); +} + +static char bgcolor(mode_t mode) +{ + if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + return ATTR(0xF000); /* File is executable ... */ + return ATTR(mode); +} +#endif + +#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR +static char append_char(mode_t mode) +{ + if (!(all_fmt & LIST_FILETYPE)) + return '\0'; + if (S_ISDIR(mode)) + return '/'; + if (!(all_fmt & LIST_EXEC)) + return '\0'; + if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + return '*'; + return APPCHAR(mode); +} +#endif + +#define countdirs(A, B) count_dirs((A), (B), 1) +#define countsubdirs(A, B) count_dirs((A), (B), 0) +static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs) +{ + int i, dirs; + + if (!dn) + return 0; + dirs = 0; + for (i = 0; i < nfiles; i++) { + char *name; + if (!S_ISDIR(dn[i]->dstat.st_mode)) + continue; + name = dn[i]->name; + if (notsubdirs + || name[0]!='.' || (name[1] && (name[1]!='.' || name[2])) + ) { + dirs++; + } + } + return dirs; +} + +static int countfiles(struct dnode **dnp) +{ + int nfiles; + struct dnode *cur; + + if (dnp == NULL) + return 0; + nfiles = 0; + for (cur = dnp[0]; cur->next; cur = cur->next) + nfiles++; + nfiles++; + return nfiles; +} + +/* get memory to hold an array of pointers */ +static struct dnode **dnalloc(int num) +{ + if (num < 1) + return NULL; + + return xzalloc(num * sizeof(struct dnode *)); +} + +#if ENABLE_FEATURE_LS_RECURSIVE +static void dfree(struct dnode **dnp, int nfiles) +{ + int i; + + if (dnp == NULL) + return; + + for (i = 0; i < nfiles; i++) { + struct dnode *cur = dnp[i]; + if (cur->allocated) + free(cur->fullname); /* free the filename */ + free(cur); /* free the dnode */ + } + free(dnp); /* free the array holding the dnode pointers */ +} +#else +#define dfree(...) do {} while(0) +#endif + +static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which) +{ + int dncnt, i, d; + struct dnode **dnp; + + if (dn == NULL || nfiles < 1) + return NULL; + + /* count how many dirs and regular files there are */ + if (which == SPLIT_SUBDIR) + dncnt = countsubdirs(dn, nfiles); + else { + dncnt = countdirs(dn, nfiles); /* assume we are looking for dirs */ + if (which == SPLIT_FILE) + dncnt = nfiles - dncnt; /* looking for files */ + } + + /* allocate a file array and a dir array */ + dnp = dnalloc(dncnt); + + /* copy the entrys into the file or dir array */ + for (d = i = 0; i < nfiles; i++) { + if (S_ISDIR(dn[i]->dstat.st_mode)) { + char *name; + if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) + continue; + name = dn[i]->name; + if ((which & SPLIT_DIR) + || name[0]!='.' || (name[1] && (name[1]!='.' || name[2])) + ) { + dnp[d++] = dn[i]; + } + } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) { + dnp[d++] = dn[i]; + } + } + return dnp; +} + +#if ENABLE_FEATURE_LS_SORTFILES +static int sortcmp(const void *a, const void *b) +{ + struct dnode *d1 = *(struct dnode **)a; + struct dnode *d2 = *(struct dnode **)b; + unsigned sort_opts = all_fmt & SORT_MASK; + int dif; + + dif = 0; /* assume SORT_NAME */ + // TODO: use pre-initialized function pointer + // instead of branch forest + if (sort_opts == SORT_SIZE) { + dif = (int) (d2->dstat.st_size - d1->dstat.st_size); + } else if (sort_opts == SORT_ATIME) { + dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime); + } else if (sort_opts == SORT_CTIME) { + dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime); + } else if (sort_opts == SORT_MTIME) { + dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime); + } else if (sort_opts == SORT_DIR) { + dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode); + /* } else if (sort_opts == SORT_VERSION) { */ + /* } else if (sort_opts == SORT_EXT) { */ + } + + if (dif == 0) { + /* sort by name - may be a tie_breaker for time or size cmp */ + if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name); + else dif = strcmp(d1->name, d2->name); + } + + if (all_fmt & SORT_REVERSE) { + dif = -dif; + } + return dif; +} + +static void dnsort(struct dnode **dn, int size) +{ + qsort(dn, size, sizeof(*dn), sortcmp); +} +#else +#define dnsort(dn, size) do {} while(0) +#endif + + +static void showfiles(struct dnode **dn, int nfiles) +{ + int i, ncols, nrows, row, nc; + int column = 0; + int nexttab = 0; + int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */ + + if (dn == NULL || nfiles < 1) + return; + + if (all_fmt & STYLE_LONG) { + ncols = 1; + } else { + /* find the longest file name, use that as the column width */ + for (i = 0; i < nfiles; i++) { + int len = strlen(dn[i]->name); + if (column_width < len) + column_width = len; + } + column_width += tabstops + + USE_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + ) + ((all_fmt & LIST_INO) ? 8 : 0) + + ((all_fmt & LIST_BLOCKS) ? 5 : 0); + ncols = (int) (terminal_width / column_width); + } + + if (ncols > 1) { + nrows = nfiles / ncols; + if (nrows * ncols < nfiles) + nrows++; /* round up fractionals */ + } else { + nrows = nfiles; + ncols = 1; + } + + for (row = 0; row < nrows; row++) { + for (nc = 0; nc < ncols; nc++) { + /* reach into the array based on the column and row */ + i = (nc * nrows) + row; /* assume display by column */ + if (all_fmt & DISP_ROWS) + i = (row * ncols) + nc; /* display across row */ + if (i < nfiles) { + if (column > 0) { + nexttab -= column; + printf("%*s", nexttab, ""); + column += nexttab; + } + nexttab = column + column_width; + column += list_single(dn[i]); + } + } + putchar('\n'); + column = 0; + } +} + + +static void showdirs(struct dnode **dn, int ndirs, int first) +{ + int i, nfiles; + struct dnode **subdnp; + int dndirs; + struct dnode **dnd; + + if (dn == NULL || ndirs < 1) + return; + + for (i = 0; i < ndirs; i++) { + if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) { + if (!first) + puts(""); + first = 0; + printf("%s:\n", dn[i]->fullname); + } + subdnp = list_dir(dn[i]->fullname); + nfiles = countfiles(subdnp); + if (nfiles > 0) { + /* list all files at this level */ + dnsort(subdnp, nfiles); + showfiles(subdnp, nfiles); + if (ENABLE_FEATURE_LS_RECURSIVE) { + if (all_fmt & DISP_RECURSIVE) { + /* recursive- list the sub-dirs */ + dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR); + dndirs = countsubdirs(subdnp, nfiles); + if (dndirs > 0) { + dnsort(dnd, dndirs); + showdirs(dnd, dndirs, 0); + /* free the array of dnode pointers to the dirs */ + free(dnd); + } + } + /* free the dnodes and the fullname mem */ + dfree(subdnp, nfiles); + } + } + } +} + + +static struct dnode **list_dir(const char *path) +{ + struct dnode *dn, *cur, **dnp; + struct dirent *entry; + DIR *dir; + int i, nfiles; + + if (path == NULL) + return NULL; + + dn = NULL; + nfiles = 0; + dir = warn_opendir(path); + if (dir == NULL) { + status = EXIT_FAILURE; + return NULL; /* could not open the dir */ + } + while ((entry = readdir(dir)) != NULL) { + char *fullname; + + /* are we going to list the file- it may be . or .. or a hidden file */ + if (entry->d_name[0] == '.') { + if ((entry->d_name[1] == 0 || ( + entry->d_name[1] == '.' + && entry->d_name[2] == 0)) + && !(all_fmt & DISP_DOT)) + continue; + if (!(all_fmt & DISP_HIDDEN)) + continue; + } + fullname = concat_path_file(path, entry->d_name); + cur = my_stat(fullname, strrchr(fullname, '/') + 1); + if (!cur) { + free(fullname); + continue; + } + cur->allocated = 1; + cur->next = dn; + dn = cur; + nfiles++; + } + closedir(dir); + + /* now that we know how many files there are + * allocate memory for an array to hold dnode pointers + */ + if (dn == NULL) + return NULL; + dnp = dnalloc(nfiles); + for (i = 0, cur = dn; i < nfiles; i++) { + dnp[i] = cur; /* save pointer to node in array */ + cur = cur->next; + } + + return dnp; +} + + +static int list_single(struct dnode *dn) +{ + int i, column = 0; + +#if ENABLE_FEATURE_LS_USERNAME + char scratch[16]; +#endif +#if ENABLE_FEATURE_LS_TIMESTAMPS + char *filetime; + time_t ttime, age; +#endif +#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR + struct stat info; + char append; +#endif + + if (dn->fullname == NULL) + return 0; + +#if ENABLE_FEATURE_LS_TIMESTAMPS + ttime = dn->dstat.st_mtime; /* the default time */ + if (all_fmt & TIME_ACCESS) + ttime = dn->dstat.st_atime; + if (all_fmt & TIME_CHANGE) + ttime = dn->dstat.st_ctime; + filetime = ctime(&ttime); +#endif +#if ENABLE_FEATURE_LS_FILETYPES + append = append_char(dn->dstat.st_mode); +#endif + + for (i = 0; i <= 31; i++) { + switch (all_fmt & (1 << i)) { + case LIST_INO: + column += printf("%7ld ", (long) dn->dstat.st_ino); + break; + case LIST_BLOCKS: + column += printf("%4"OFF_FMT"d ", (off_t) dn->dstat.st_blocks >> 1); + break; + case LIST_MODEBITS: + column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode)); + break; + case LIST_NLINKS: + column += printf("%4ld ", (long) dn->dstat.st_nlink); + break; + case LIST_ID_NAME: +#if ENABLE_FEATURE_LS_USERNAME + bb_getpwuid(scratch, dn->dstat.st_uid, sizeof(scratch)); + printf("%-8.8s ", scratch); + bb_getgrgid(scratch, dn->dstat.st_gid, sizeof(scratch)); + printf("%-8.8s", scratch); + column += 17; + break; +#endif + case LIST_ID_NUMERIC: + column += printf("%-8d %-8d", dn->dstat.st_uid, dn->dstat.st_gid); + break; + case LIST_SIZE: + case LIST_DEV: + if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) { + column += printf("%4d, %3d ", (int) major(dn->dstat.st_rdev), + (int) minor(dn->dstat.st_rdev)); + } else { + if (all_fmt & LS_DISP_HR) { + column += printf("%9s ", + make_human_readable_str(dn->dstat.st_size, 1, 0)); + } else { + column += printf("%9"OFF_FMT"d ", (off_t) dn->dstat.st_size); + } + } + break; +#if ENABLE_FEATURE_LS_TIMESTAMPS + case LIST_FULLTIME: + printf("%24.24s ", filetime); + column += 25; + break; + case LIST_DATE_TIME: + if ((all_fmt & LIST_FULLTIME) == 0) { + age = time(NULL) - ttime; + printf("%6.6s ", filetime + 4); + if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) { + /* hh:mm if less than 6 months old */ + printf("%5.5s ", filetime + 11); + } else { + printf(" %4.4s ", filetime + 20); + } + column += 13; + } + break; +#endif +#if ENABLE_SELINUX + case LIST_CONTEXT: + { + char context[80]; + int len = 0; + + if (dn->sid) { + /* I assume sid initilized with NULL */ + len = strlen(dn->sid) + 1; + safe_strncpy(context, dn->sid, len); + freecon(dn->sid); + } else { + safe_strncpy(context, "unknown", 8); + } + printf("%-32s ", context); + column += MAX(33, len); + } + break; +#endif + case LIST_FILENAME: + errno = 0; +#if ENABLE_FEATURE_LS_COLOR + if (show_color && !lstat(dn->fullname, &info)) { + printf("\033[%d;%dm", bgcolor(info.st_mode), + fgcolor(info.st_mode)); + } +#endif + column += printf("%s", dn->name); + if (show_color) { + printf("\033[0m"); + } + break; + case LIST_SYMLINK: + if (S_ISLNK(dn->dstat.st_mode)) { + char *lpath = xreadlink(dn->fullname); + if (!lpath) break; + printf(" -> "); +#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR + if (!stat(dn->fullname, &info)) { + append = append_char(info.st_mode); + } +#endif +#if ENABLE_FEATURE_LS_COLOR + if (show_color) { + errno = 0; + printf("\033[%d;%dm", bgcolor(info.st_mode), + fgcolor(info.st_mode)); + } +#endif + column += printf("%s", lpath) + 4; + if (show_color) { + printf("\033[0m"); + } + free(lpath); + } + break; +#if ENABLE_FEATURE_LS_FILETYPES + case LIST_FILETYPE: + if (append) { + putchar(append); + column++; + } + break; +#endif + } + } + + return column; +} + +/* "[-]Cadil1", POSIX mandated options, busybox always supports */ +/* "[-]gnsx", POSIX non-mandated options, busybox always supports */ +/* "[-]Ak" GNU options, busybox always supports */ +/* "[-]FLRctur", POSIX mandated options, busybox optionally supports */ +/* "[-]p", POSIX non-mandated options, busybox optionally supports */ +/* "[-]SXvThw", GNU options, busybox optionally supports */ +/* "[-]K", SELinux mandated options, busybox optionally supports */ +/* "[-]e", I think we made this one up */ +static const char ls_options[] = "Cadil1gnsxAk" + USE_FEATURE_LS_TIMESTAMPS("cetu") + USE_FEATURE_LS_SORTFILES("SXrv") + USE_FEATURE_LS_FILETYPES("Fp") + USE_FEATURE_LS_FOLLOWLINKS("L") + USE_FEATURE_LS_RECURSIVE("R") + USE_FEATURE_HUMAN_READABLE("h") + USE_SELINUX("K") + USE_FEATURE_AUTOWIDTH("T:w:"); + +enum { + LIST_MASK_TRIGGER = 0, + STYLE_MASK_TRIGGER = STYLE_MASK, + DISP_MASK_TRIGGER = DISP_ROWS, + SORT_MASK_TRIGGER = SORT_MASK, +}; + +static const unsigned opt_flags[] = { + LIST_SHORT | STYLE_COLUMNS, /* C */ + DISP_HIDDEN | DISP_DOT, /* a */ + DISP_NOLIST, /* d */ + LIST_INO, /* i */ + LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */ + LIST_SHORT | STYLE_SINGLE, /* 1 */ + 0, /* g - ingored */ + LIST_ID_NUMERIC, /* n */ + LIST_BLOCKS, /* s */ + DISP_ROWS, /* x */ + DISP_HIDDEN, /* A */ + ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */ +#if ENABLE_FEATURE_LS_TIMESTAMPS + TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */ + LIST_FULLTIME, /* e */ + ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */ + TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */ +#endif +#if ENABLE_FEATURE_LS_SORTFILES + SORT_SIZE, /* S */ + SORT_EXT, /* X */ + SORT_REVERSE, /* r */ + SORT_VERSION, /* v */ +#endif +#if ENABLE_FEATURE_LS_FILETYPES + LIST_FILETYPE | LIST_EXEC, /* F */ + LIST_FILETYPE, /* p */ +#endif +#if ENABLE_FEATURE_LS_FOLLOWLINKS + FOLLOW_LINKS, /* L */ +#endif +#if ENABLE_FEATURE_LS_RECURSIVE + DISP_RECURSIVE, /* R */ +#endif +#if ENABLE_FEATURE_HUMAN_READABLE + LS_DISP_HR, /* h */ +#endif +#if ENABLE_SELINUX + LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */ +#endif +#if ENABLE_FEATURE_AUTOWIDTH + 0, 0, /* T, w - ignored */ +#endif + (1U<<31) +}; + + +int ls_main(int argc, char **argv) +{ + struct dnode **dnd; + struct dnode **dnf; + struct dnode **dnp; + struct dnode *dn; + struct dnode *cur; + unsigned opt; + int nfiles = 0; + int dnfiles; + int dndirs; + int oi; + int ac; + int i; + char **av; + USE_FEATURE_AUTOWIDTH(char *tabstops_str = NULL;) + USE_FEATURE_AUTOWIDTH(char *terminal_width_str = NULL;) + USE_FEATURE_LS_COLOR(char *color_opt;) + + all_fmt = LIST_SHORT | + (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD)); + +#if ENABLE_FEATURE_AUTOWIDTH + /* Obtain the terminal width */ + get_terminal_width_height(STDOUT_FILENO, &terminal_width, NULL); + /* Go one less... */ + terminal_width--; +#endif + + /* process options */ + USE_FEATURE_LS_COLOR(applet_long_options = ls_color_opt;) +#if ENABLE_FEATURE_AUTOWIDTH + opt = getopt32(argc, argv, ls_options, &tabstops_str, &terminal_width_str + USE_FEATURE_LS_COLOR(, &color_opt)); + if (tabstops_str) + tabstops = xatou(tabstops_str); + if (terminal_width_str) + terminal_width = xatou(terminal_width_str); +#else + opt = getopt32(argc, argv, ls_options USE_FEATURE_LS_COLOR(, &color_opt)); +#endif + for (i = 0; opt_flags[i] != (1U<<31); i++) { + if (opt & (1 << i)) { + unsigned flags = opt_flags[i]; + + if (flags & LIST_MASK_TRIGGER) + all_fmt &= ~LIST_MASK; + if (flags & STYLE_MASK_TRIGGER) + all_fmt &= ~STYLE_MASK; + if (flags & SORT_MASK_TRIGGER) + all_fmt &= ~SORT_MASK; + if (flags & DISP_MASK_TRIGGER) + all_fmt &= ~DISP_MASK; + if (flags & TIME_MASK) + all_fmt &= ~TIME_MASK; + if (flags & LIST_CONTEXT) + all_fmt |= STYLE_SINGLE; + if (LS_DISP_HR && opt == 'l') + all_fmt &= ~LS_DISP_HR; + all_fmt |= flags; + } + } + +#if ENABLE_FEATURE_LS_COLOR + /* find color bit value - last position for short getopt */ + if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) { + char *p = getenv("LS_COLORS"); + /* LS_COLORS is unset, or (not empty && not "none") ? */ + if (!p || (p[0] && strcmp(p, "none"))) + show_color = 1; + } + if (opt & (1 << i)) { /* next flag after short options */ + if (!color_opt || !strcmp("always", color_opt)) + show_color = 1; + else if (color_opt && !strcmp("never", color_opt)) + show_color = 0; + else if (color_opt && !strcmp("auto", color_opt) && isatty(STDOUT_FILENO)) + show_color = 1; + } +#endif + + /* sort out which command line options take precedence */ + if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST)) + all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */ + if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) { + if (all_fmt & TIME_CHANGE) + all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME; + if (all_fmt & TIME_ACCESS) + all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME; + } + if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */ + all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC); + if (ENABLE_FEATURE_LS_USERNAME) + if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC)) + all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */ + + /* choose a display format */ + if (!(all_fmt & STYLE_MASK)) + all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE); + + /* + * when there are no cmd line args we have to supply a default "." arg. + * we will create a second argv array, "av" that will hold either + * our created "." arg, or the real cmd line args. The av array + * just holds the pointers- we don't move the date the pointers + * point to. + */ + ac = argc - optind; /* how many cmd line args are left */ + if (ac < 1) { + static const char *const dotdir[] = { "." }; + + av = (char **) dotdir; + ac = 1; + } else { + av = argv + optind; + } + + /* now, everything is in the av array */ + if (ac > 1) + all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */ + + /* stuff the command line file names into a dnode array */ + dn = NULL; + for (oi = 0; oi < ac; oi++) { + cur = my_stat(av[oi], av[oi]); + if (!cur) + continue; + cur->allocated = 0; + cur->next = dn; + dn = cur; + nfiles++; + } + + /* now that we know how many files there are + * allocate memory for an array to hold dnode pointers + */ + dnp = dnalloc(nfiles); + for (i = 0, cur = dn; i < nfiles; i++) { + dnp[i] = cur; /* save pointer to node in array */ + cur = cur->next; + } + + if (all_fmt & DISP_NOLIST) { + dnsort(dnp, nfiles); + if (nfiles > 0) + showfiles(dnp, nfiles); + } else { + dnd = splitdnarray(dnp, nfiles, SPLIT_DIR); + dnf = splitdnarray(dnp, nfiles, SPLIT_FILE); + dndirs = countdirs(dnp, nfiles); + dnfiles = nfiles - dndirs; + if (dnfiles > 0) { + dnsort(dnf, dnfiles); + showfiles(dnf, dnfiles); + if (ENABLE_FEATURE_CLEAN_UP) + free(dnf); + } + if (dndirs > 0) { + dnsort(dnd, dndirs); + showdirs(dnd, dndirs, dnfiles == 0); + if (ENABLE_FEATURE_CLEAN_UP) + free(dnd); + } + } + if (ENABLE_FEATURE_CLEAN_UP) + dfree(dnp, nfiles); + return status; +} diff --git a/coreutils/md5_sha1_sum.c b/coreutils/md5_sha1_sum.c new file mode 100644 index 000000000..e8d3a1509 --- /dev/null +++ b/coreutils/md5_sha1_sum.c @@ -0,0 +1,186 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2003 Glenn L. McGrath + * Copyright (C) 2003-2004 Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +typedef enum { HASH_SHA1, HASH_MD5 } hash_algo_t; + +#define FLAG_SILENT 1 +#define FLAG_CHECK 2 +#define FLAG_WARN 4 + +/* This might be useful elsewhere */ +static unsigned char *hash_bin_to_hex(unsigned char *hash_value, + unsigned hash_length) +{ + int len = 0; + char *hex_value = xmalloc((hash_length * 2) + 2); + while (hash_length--) { + len += sprintf(hex_value + len, "%02x", *hash_value++); + } + return hex_value; +} + +static uint8_t *hash_file(const char *filename, hash_algo_t hash_algo) +{ + int src_fd, hash_len, count; + union _ctx_ { + sha1_ctx_t sha1; + md5_ctx_t md5; + } context; + uint8_t *hash_value = NULL; + RESERVE_CONFIG_UBUFFER(in_buf, 4096); + void (*update)(const void*, size_t, void*); + void (*final)(void*, void*); + + src_fd = STDIN_FILENO; + if (filename[0] != '-' || filename[1]) { /* not "-" */ + src_fd = open(filename, O_RDONLY); + if (src_fd < 0) { + bb_perror_msg("%s", filename); + return NULL; + } + } + + /* figure specific hash algorithims */ + if (ENABLE_MD5SUM && hash_algo==HASH_MD5) { + md5_begin(&context.md5); + update = (void (*)(const void*, size_t, void*))md5_hash; + final = (void (*)(void*, void*))md5_end; + hash_len = 16; + } else if (ENABLE_SHA1SUM && hash_algo==HASH_SHA1) { + sha1_begin(&context.sha1); + update = (void (*)(const void*, size_t, void*))sha1_hash; + final = (void (*)(void*, void*))sha1_end; + hash_len = 20; + } else { + bb_error_msg_and_die("algorithm not supported"); + } + + while (0 < (count = safe_read(src_fd, in_buf, 4096))) { + update(in_buf, count, &context); + } + + if (count == 0) { + final(in_buf, &context); + hash_value = hash_bin_to_hex(in_buf, hash_len); + } + + RELEASE_CONFIG_BUFFER(in_buf); + + if (src_fd != STDIN_FILENO) { + close(src_fd); + } + + return hash_value; +} + +int md5_sha1_sum_main(int argc, char **argv) +{ + int return_value = EXIT_SUCCESS; + uint8_t *hash_value; + unsigned flags; + hash_algo_t hash_algo = ENABLE_MD5SUM + ? (ENABLE_SHA1SUM ? (**argv=='m' ? HASH_MD5 : HASH_SHA1) : HASH_MD5) + : HASH_SHA1; + + if (ENABLE_FEATURE_MD5_SHA1_SUM_CHECK) + flags = getopt32(argc, argv, "scw"); + else optind = 1; + + if (ENABLE_FEATURE_MD5_SHA1_SUM_CHECK && !(flags & FLAG_CHECK)) { + if (flags & FLAG_SILENT) { + bb_error_msg_and_die + ("-%c is meaningful only when verifying checksums", 's'); + } else if (flags & FLAG_WARN) { + bb_error_msg_and_die + ("-%c is meaningful only when verifying checksums", 'w'); + } + } + + if (argc == optind) { + argv[argc++] = "-"; + } + + if (ENABLE_FEATURE_MD5_SHA1_SUM_CHECK && (flags & FLAG_CHECK)) { + FILE *pre_computed_stream; + int count_total = 0; + int count_failed = 0; + char *file_ptr = argv[optind]; + char *line; + + if (optind + 1 != argc) { + bb_error_msg_and_die + ("only one argument may be specified when using -c"); + } + + pre_computed_stream = stdin; + if (file_ptr[0] != '-' || file_ptr[1]) { /* not "-" */ + pre_computed_stream = xfopen(file_ptr, "r"); + } + + while ((line = xmalloc_getline(pre_computed_stream)) != NULL) { + char *filename_ptr; + + count_total++; + filename_ptr = strstr(line, " "); + /* handle format for binary checksums */ + if (filename_ptr == NULL) { + filename_ptr = strstr(line, " *"); + } + if (filename_ptr == NULL) { + if (flags & FLAG_WARN) { + bb_error_msg("invalid format"); + } + count_failed++; + return_value = EXIT_FAILURE; + free(line); + continue; + } + *filename_ptr = '\0'; + filename_ptr += 2; + + hash_value = hash_file(filename_ptr, hash_algo); + + if (hash_value && (strcmp((char*)hash_value, line) == 0)) { + if (!(flags & FLAG_SILENT)) + printf("%s: OK\n", filename_ptr); + } else { + if (!(flags & FLAG_SILENT)) + printf("%s: FAILED\n", filename_ptr); + count_failed++; + return_value = EXIT_FAILURE; + } + /* possible free(NULL) */ + free(hash_value); + free(line); + } + if (count_failed && !(flags & FLAG_SILENT)) { + bb_error_msg("WARNING: %d of %d computed checksums did NOT match", + count_failed, count_total); + } + /* + if (fclose_if_not_stdin(pre_computed_stream) == EOF) { + bb_perror_msg_and_die("cannot close file %s", file_ptr); + } + */ + } else { + while (optind < argc) { + char *file_ptr = argv[optind++]; + + hash_value = hash_file(file_ptr, hash_algo); + if (hash_value == NULL) { + return_value = EXIT_FAILURE; + } else { + printf("%s %s\n", hash_value, file_ptr); + free(hash_value); + } + } + } + return return_value; +} diff --git a/coreutils/mkdir.c b/coreutils/mkdir.c new file mode 100644 index 000000000..2cc9c7a4c --- /dev/null +++ b/coreutils/mkdir.c @@ -0,0 +1,66 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini mkdir implementation for busybox + * + * Copyright (C) 2001 Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/mkdir.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Fixed broken permission setting when -p was used; especially in + * conjunction with -m. + */ + +#include +#include +#include /* struct option */ +#include "busybox.h" + +#if ENABLE_FEATURE_MKDIR_LONG_OPTIONS +static const struct option mkdir_long_options[] = { + { "mode", 1, NULL, 'm' }, + { "parents", 0, NULL, 'p' }, + { 0, 0, 0, 0 } +}; +#endif + +int mkdir_main(int argc, char **argv) +{ + mode_t mode = (mode_t)(-1); + int status = EXIT_SUCCESS; + int flags = 0; + unsigned opt; + char *smode; + +#if ENABLE_FEATURE_MKDIR_LONG_OPTIONS + applet_long_options = mkdir_long_options; +#endif + opt = getopt32(argc, argv, "m:p", &smode); + if (opt & 1) { + mode = 0777; + if (!bb_parse_mode(smode, &mode)) { + bb_error_msg_and_die("invalid mode '%s'", smode); + } + } + if (opt & 2) + flags |= FILEUTILS_RECUR; + + if (optind == argc) { + bb_show_usage(); + } + + argv += optind; + + do { + if (bb_make_directory(*argv, mode, flags)) { + status = EXIT_FAILURE; + } + } while (*++argv); + + return status; +} diff --git a/coreutils/mkfifo.c b/coreutils/mkfifo.c new file mode 100644 index 000000000..24d27e7c0 --- /dev/null +++ b/coreutils/mkfifo.c @@ -0,0 +1,38 @@ +/* vi: set sw=4 ts=4: */ +/* + * mkfifo implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/mkfifo.html */ + +#include +#include +#include +#include "busybox.h" +#include "libcoreutils/coreutils.h" + +int mkfifo_main(int argc, char **argv) +{ + mode_t mode; + int retval = EXIT_SUCCESS; + + mode = getopt_mk_fifo_nod(argc, argv); + + if (!*(argv += optind)) { + bb_show_usage(); + } + + do { + if (mkfifo(*argv, mode) < 0) { + bb_perror_msg("%s", *argv); /* Avoid multibyte problems. */ + retval = EXIT_FAILURE; + } + } while (*++argv); + + return retval; +} diff --git a/coreutils/mknod.c b/coreutils/mknod.c new file mode 100644 index 000000000..8ca511cd5 --- /dev/null +++ b/coreutils/mknod.c @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +/* + * mknod implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +#include // For makedev + +#include "busybox.h" +#include "libcoreutils/coreutils.h" + +static const char modes_chars[] = { 'p', 'c', 'u', 'b', 0, 1, 1, 2 }; +static const mode_t modes_cubp[] = { S_IFIFO, S_IFCHR, S_IFBLK }; + +int mknod_main(int argc, char **argv) +{ + mode_t mode; + dev_t dev; + const char *name; + + mode = getopt_mk_fifo_nod(argc, argv); + argv += optind; + argc -= optind; + + if ((argc >= 2) && ((name = strchr(modes_chars, argv[1][0])) != NULL)) { + mode |= modes_cubp[(int)(name[4])]; + + dev = 0; + if ((*name != 'p') && ((argc -= 2) == 2)) { + /* Autodetect what the system supports; these macros should + * optimize out to two constants. */ + dev = makedev(xatoul_range(argv[2], 0, major(UINT_MAX)), + xatoul_range(argv[3], 0, minor(UINT_MAX))); + } + + if (argc == 2) { + name = *argv; + if (mknod(name, mode, dev) == 0) { + return EXIT_SUCCESS; + } + bb_perror_msg_and_die("%s", name); + } + } + bb_show_usage(); +} diff --git a/coreutils/mv.c b/coreutils/mv.c new file mode 100644 index 000000000..353124b35 --- /dev/null +++ b/coreutils/mv.c @@ -0,0 +1,131 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini mv implementation for busybox + * + * Copyright (C) 2000 by Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Size reduction and improved error checking. + */ + +#include +#include +#include +#include +#include +#include +#include /* struct option */ +#include "busybox.h" +#include "libcoreutils/coreutils.h" + +#if ENABLE_FEATURE_MV_LONG_OPTIONS +static const struct option mv_long_options[] = { + { "interactive", 0, NULL, 'i' }, + { "force", 0, NULL, 'f' }, + { 0, 0, 0, 0 } +}; +#endif + +#define OPT_FILEUTILS_FORCE 1 +#define OPT_FILEUTILS_INTERACTIVE 2 + +static const char fmt[] = "cannot overwrite %sdirectory with %sdirectory"; + +int mv_main(int argc, char **argv) +{ + struct stat dest_stat; + const char *last; + const char *dest; + unsigned long flags; + int dest_exists; + int status = 0; + +#if ENABLE_FEATURE_MV_LONG_OPTIONS + applet_long_options = mv_long_options; +#endif + opt_complementary = "f-i:i-f"; + flags = getopt32(argc, argv, "fi"); + if (optind + 2 > argc) { + bb_show_usage(); + } + + last = argv[argc - 1]; + argv += optind; + + if (optind + 2 == argc) { + dest_exists = cp_mv_stat(last, &dest_stat); + if (dest_exists < 0) { + return 1; + } + + if (!(dest_exists & 2)) { + dest = last; + goto DO_MOVE; + } + } + + do { + dest = concat_path_file(last, bb_get_last_path_component(*argv)); + dest_exists = cp_mv_stat(dest, &dest_stat); + if (dest_exists < 0) { + goto RET_1; + } + +DO_MOVE: + + if (dest_exists && !(flags & OPT_FILEUTILS_FORCE) && + ((access(dest, W_OK) < 0 && isatty(0)) || + (flags & OPT_FILEUTILS_INTERACTIVE))) { + if (fprintf(stderr, "mv: overwrite '%s'? ", dest) < 0) { + goto RET_1; /* Ouch! fprintf failed! */ + } + if (!bb_ask_confirmation()) { + goto RET_0; + } + } + if (rename(*argv, dest) < 0) { + struct stat source_stat; + int source_exists; + + if (errno != EXDEV || + (source_exists = cp_mv_stat(*argv, &source_stat)) < 1) { + bb_perror_msg("cannot rename '%s'", *argv); + } else { + if (dest_exists) { + if (dest_exists == 3) { + if (source_exists != 3) { + bb_error_msg(fmt, "", "non-"); + goto RET_1; + } + } else { + if (source_exists == 3) { + bb_error_msg(fmt, "non-", ""); + goto RET_1; + } + } + if (unlink(dest) < 0) { + bb_perror_msg("cannot remove '%s'", dest); + goto RET_1; + } + } + if ((copy_file(*argv, dest, + FILEUTILS_RECUR | FILEUTILS_PRESERVE_STATUS) >= 0) && + (remove_file(*argv, FILEUTILS_RECUR | FILEUTILS_FORCE) >= 0)) { + goto RET_0; + } + } +RET_1: + status = 1; + } +RET_0: + if (dest != last) { + free((void *) dest); + } + } while (*++argv != last); + + return status; +} diff --git a/coreutils/nice.c b/coreutils/nice.c new file mode 100644 index 000000000..293861842 --- /dev/null +++ b/coreutils/nice.c @@ -0,0 +1,54 @@ +/* vi: set sw=4 ts=4: */ +/* + * nice implementation for busybox + * + * Copyright (C) 2005 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "busybox.h" + +int nice_main(int argc, char **argv) +{ + int old_priority, adjustment; + + old_priority = getpriority(PRIO_PROCESS, 0); + + if (!*++argv) { /* No args, so (GNU) output current nice value. */ + printf("%d\n", old_priority); + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + adjustment = 10; /* Set default adjustment. */ + + if (argv[0][0] == '-') { + if (argv[0][1] == 'n') { /* -n */ + if (argv[0][2]) { /* -nNNNN (w/o space) */ + argv[0] += 2; argv--; argc++; + } + } else { /* -NNN (NNN may be negative) == -n NNN */ + argv[0] += 1; argv--; argc++; + } + if (argc < 4) { /* Missing priority and/or utility! */ + bb_show_usage(); + } + adjustment = xatoi_range(argv[1], INT_MIN/2, INT_MAX/2); + argv += 2; + } + + { /* Set our priority. */ + int prio = old_priority + adjustment; + + if (setpriority(PRIO_PROCESS, 0, prio) < 0) { + bb_perror_msg_and_die("setpriority(%d)", prio); + } + } + + execvp(*argv, argv); /* Now exec the desired program. */ + + /* The exec failed... */ + xfunc_error_retval = (errno == ENOENT) ? 127 : 126; /* SUSv3 */ + bb_perror_msg_and_die("%s", *argv); +} diff --git a/coreutils/nohup.c b/coreutils/nohup.c new file mode 100644 index 000000000..21adfc1c3 --- /dev/null +++ b/coreutils/nohup.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* nohup - invoke a utility immune to hangups. + * + * Busybox version based on nohup specification at + * http://www.opengroup.org/onlinepubs/007904975/utilities/nohup.html + * + * Copyright 2006 Rob Landley + * Copyright 2006 Bernhard Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +int nohup_main(int argc, char **argv) +{ + int temp, nullfd; + char *nohupout, *home = NULL; + + xfunc_error_retval = 127; + + if (argc<2) bb_show_usage(); + + nullfd = xopen(bb_dev_null, O_WRONLY|O_APPEND); + /* If stdin is a tty, detach from it. */ + if (isatty(STDIN_FILENO)) dup2(nullfd, STDIN_FILENO); + + nohupout = "nohup.out"; + /* Redirect stdout to nohup.out, either in "." or in "$HOME". */ + if (isatty(STDOUT_FILENO)) { + close(STDOUT_FILENO); + if (open(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR) < 0) { + home = getenv("HOME"); + if (home) { + nohupout = concat_path_file(home, nohupout); + xopen3(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR); + } + } + } else dup2(nullfd, STDOUT_FILENO); + + /* If we have a tty on strderr, announce filename and redirect to stdout. + * Else redirect to /dev/null. + */ + temp = isatty(STDERR_FILENO); + if (temp) bb_error_msg("appending to %s", nohupout); + dup2(temp ? STDOUT_FILENO : nullfd, STDERR_FILENO); + close(nullfd); + signal (SIGHUP, SIG_IGN); + + execvp(argv[1],argv+1); + if (00 && ENABLE_FEATURE_CLEAN_UP && home) free(nohupout); + bb_perror_msg_and_die("%s", argv[1]); +} diff --git a/coreutils/od.c b/coreutils/od.c new file mode 100644 index 000000000..8de866281 --- /dev/null +++ b/coreutils/od.c @@ -0,0 +1,224 @@ +/* vi: set sw=4 ts=4: */ +/* + * od implementation for busybox + * Based on code from util-linux v 2.11l + * + * Copyright (c) 1990 + * The Regents of the University of California. All rights reserved. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Original copyright notice is retained at the end of this file. + */ + + +#if ENABLE_DESKTOP +/* This one provides -t (busybox's own build script needs it) */ +#include "od_bloaty.c" +#else + +#include +#include "busybox.h" +#include "dump.h" + +#define isdecdigit(c) isdigit(c) +#define ishexdigit(c) (isxdigit)(c) + +static void +odoffset(int argc, char ***argvp) +{ + char *num, *p; + int base; + char *end; + + /* + * The offset syntax of od(1) was genuinely bizarre. First, if + * it started with a plus it had to be an offset. Otherwise, if + * there were at least two arguments, a number or lower-case 'x' + * followed by a number makes it an offset. By default it was + * octal; if it started with 'x' or '0x' it was hex. If it ended + * in a '.', it was decimal. If a 'b' or 'B' was appended, it + * multiplied the number by 512 or 1024 byte units. There was + * no way to assign a block count to a hex offset. + * + * We assumes it's a file if the offset is bad. + */ + p = **argvp; + + if (!p) { + /* hey someone is probably piping to us ... */ + return; + } + + if ((*p != '+') + && (argc < 2 + || (!isdecdigit(p[0]) + && ((p[0] != 'x') || !ishexdigit(p[1]))))) + return; + + base = 0; + /* + * bb_dump_skip over leading '+', 'x[0-9a-fA-f]' or '0x', and + * set base. + */ + if (p[0] == '+') + ++p; + if (p[0] == 'x' && ishexdigit(p[1])) { + ++p; + base = 16; + } else if (p[0] == '0' && p[1] == 'x') { + p += 2; + base = 16; + } + + /* bb_dump_skip over the number */ + if (base == 16) + for (num = p; ishexdigit(*p); ++p); + else + for (num = p; isdecdigit(*p); ++p); + + /* check for no number */ + if (num == p) + return; + + /* if terminates with a '.', base is decimal */ + if (*p == '.') { + if (base) + return; + base = 10; + } + + bb_dump_skip = strtol(num, &end, base ? base : 8); + + /* if end isn't the same as p, we got a non-octal digit */ + if (end != p) + bb_dump_skip = 0; + else { + if (*p) { + if (*p == 'b') { + bb_dump_skip *= 512; + ++p; + } else if (*p == 'B') { + bb_dump_skip *= 1024; + ++p; + } + } + if (*p) + bb_dump_skip = 0; + else { + ++*argvp; + /* + * If the offset uses a non-octal base, the base of + * the offset is changed as well. This isn't pretty, + * but it's easy. + */ +#define TYPE_OFFSET 7 + { + char x_or_d; + if (base == 16) { + x_or_d = 'x'; + goto DO_X_OR_D; + } + if (base == 10) { + x_or_d = 'd'; + DO_X_OR_D: + bb_dump_fshead->nextfu->fmt[TYPE_OFFSET] + = bb_dump_fshead->nextfs->nextfu->fmt[TYPE_OFFSET] + = x_or_d; + } + } + } + } +} + +static const char * const add_strings[] = { + "16/1 \"%3_u \" \"\\n\"", /* a */ + "8/2 \" %06o \" \"\\n\"", /* B, o */ + "16/1 \"%03o \" \"\\n\"", /* b */ + "16/1 \"%3_c \" \"\\n\"", /* c */ + "8/2 \" %05u \" \"\\n\"", /* d */ + "4/4 \" %010u \" \"\\n\"", /* D */ + "2/8 \" %21.14e \" \"\\n\"", /* e (undocumented in od), F */ + "4/4 \" %14.7e \" \"\\n\"", /* f */ + "4/4 \" %08x \" \"\\n\"", /* H, X */ + "8/2 \" %04x \" \"\\n\"", /* h, x */ + "4/4 \" %11d \" \"\\n\"", /* I, L, l */ + "8/2 \" %6d \" \"\\n\"", /* i */ + "4/4 \" %011o \" \"\\n\"", /* O */ +}; + +static const char od_opts[] = "aBbcDdeFfHhIiLlOoXxv"; + +static const char od_o2si[] = { + 0, 1, 2, 3, 5, + 4, 6, 6, 7, 8, + 9, 0xa, 0xb, 0xa, 0xa, + 0xb, 1, 8, 9, +}; + +int od_main(int argc, char **argv) +{ + int ch; + int first = 1; + char *p; + bb_dump_vflag = FIRST; + bb_dump_length = -1; + + while ((ch = getopt(argc, argv, od_opts)) > 0) { + if (ch == 'v') { + bb_dump_vflag = ALL; + } else if (((p = strchr(od_opts, ch)) != NULL) && (*p != '\0')) { + if (first) { + first = 0; + bb_dump_add("\"%07.7_Ao\n\""); + bb_dump_add("\"%07.7_ao \""); + } else { + bb_dump_add("\" \""); + } + bb_dump_add(add_strings[(int)od_o2si[(p-od_opts)]]); + } else { /* P, p, s, w, or other unhandled */ + bb_show_usage(); + } + } + if (!bb_dump_fshead) { + bb_dump_add("\"%07.7_Ao\n\""); + bb_dump_add("\"%07.7_ao \" 8/2 \"%06o \" \"\\n\""); + } + + argc -= optind; + argv += optind; + + odoffset(argc, &argv); + + return bb_dump_dump(argv); +} +#endif /* ENABLE_DESKTOP */ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/coreutils/od_bloaty.c b/coreutils/od_bloaty.c new file mode 100644 index 000000000..9f1a582ef --- /dev/null +++ b/coreutils/od_bloaty.c @@ -0,0 +1,1466 @@ +/* od -- dump files in octal and other formats + Copyright (C) 92, 1995-2004 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* Written by Jim Meyering. */ + +/* Busyboxed by Denis Vlasenko + +Based on od.c from coreutils-5.2.1 +Top bloat sources: + +00000073 t parse_old_offset +0000007b t get_lcm +00000090 r long_options +00000092 t print_named_ascii +000000bf t print_ascii +00000168 t write_block +00000366 t decode_format_string +00000a71 T od_main + +Tested for compat with coreutils 6.3 +using this script. Minor differences fixed. + +#!/bin/sh +echo STD +time /path/to/coreutils/od \ +...params... \ +>std +echo Exit code $? +echo BBOX +time ./busybox od \ +...params... \ +>bbox +echo Exit code $? +diff -u -a std bbox >bbox.diff || { echo Different!; sleep 1; } + +*/ + + +#include "busybox.h" +#include + +#define assert(a) ((void)0) + +/* Check for 0x7f is a coreutils 6.3 addition */ +#define ISPRINT(c) (((c)>=' ') && (c) != 0x7f) + +typedef long double longdouble_t; +typedef unsigned long long ulonglong_t; +typedef long long llong; + +#if ENABLE_LFS +# define xstrtooff_sfx xstrtoull_sfx +#else +# define xstrtooff_sfx xstrtoul_sfx +#endif + +/* The default number of input bytes per output line. */ +#define DEFAULT_BYTES_PER_BLOCK 16 + +/* The number of decimal digits of precision in a float. */ +#ifndef FLT_DIG +# define FLT_DIG 7 +#endif + +/* The number of decimal digits of precision in a double. */ +#ifndef DBL_DIG +# define DBL_DIG 15 +#endif + +/* The number of decimal digits of precision in a long double. */ +#ifndef LDBL_DIG +# define LDBL_DIG DBL_DIG +#endif + +enum size_spec { + NO_SIZE, + CHAR, + SHORT, + INT, + LONG, + LONG_LONG, + FLOAT_SINGLE, + FLOAT_DOUBLE, + FLOAT_LONG_DOUBLE, + N_SIZE_SPECS +}; + +enum output_format { + SIGNED_DECIMAL, + UNSIGNED_DECIMAL, + OCTAL, + HEXADECIMAL, + FLOATING_POINT, + NAMED_CHARACTER, + CHARACTER +}; + +/* Each output format specification (from '-t spec' or from + old-style options) is represented by one of these structures. */ +struct tspec { + enum output_format fmt; + enum size_spec size; + void (*print_function) (size_t, const char *, const char *); + char *fmt_string; + int hexl_mode_trailer; + int field_width; +}; + +/* Convert the number of 8-bit bytes of a binary representation to + the number of characters (digits + sign if the type is signed) + required to represent the same quantity in the specified base/type. + For example, a 32-bit (4-byte) quantity may require a field width + as wide as the following for these types: + 11 unsigned octal + 11 signed decimal + 10 unsigned decimal + 8 unsigned hexadecimal */ + +static const uint8_t bytes_to_oct_digits[] = +{0, 3, 6, 8, 11, 14, 16, 19, 22, 25, 27, 30, 32, 35, 38, 41, 43}; + +static const uint8_t bytes_to_signed_dec_digits[] = +{1, 4, 6, 8, 11, 13, 16, 18, 20, 23, 25, 28, 30, 33, 35, 37, 40}; + +static const uint8_t bytes_to_unsigned_dec_digits[] = +{0, 3, 5, 8, 10, 13, 15, 17, 20, 22, 25, 27, 29, 32, 34, 37, 39}; + +static const uint8_t bytes_to_hex_digits[] = +{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32}; + +/* Convert enum size_spec to the size of the named type. */ +static const signed char width_bytes[] = { + -1, + sizeof(char), + sizeof(short), + sizeof(int), + sizeof(long), + sizeof(ulonglong_t), + sizeof(float), + sizeof(double), + sizeof(longdouble_t) +}; + +/* Ensure that for each member of 'enum size_spec' there is an + initializer in the width_bytes array. */ +struct dummy { + int assert_width_bytes_matches_size_spec_decl + [sizeof width_bytes / sizeof width_bytes[0] == N_SIZE_SPECS ? 1 : -1]; +}; + +static size_t string_min; +static int flag_dump_strings; + +/* Non-zero if an old-style 'pseudo-address' was specified. */ +static int flag_pseudo_start; + +/* The difference between the old-style pseudo starting address and + the number of bytes to skip. */ +static off_t pseudo_offset; + +/* Function that accepts an address and an optional following char, + and prints the address and char to stdout. */ +static void (*format_address) (off_t, char); + +/* The number of input bytes to skip before formatting and writing. */ +static off_t n_bytes_to_skip; // = 0; + +/* When zero, MAX_BYTES_TO_FORMAT and END_OFFSET are ignored, and all + input is formatted. */ +static int limit_bytes_to_format; // = 0; + +/* The maximum number of bytes that will be formatted. */ +static off_t max_bytes_to_format; + +/* The offset of the first byte after the last byte to be formatted. */ +static off_t end_offset; + +/* When nonzero and two or more consecutive blocks are equal, format + only the first block and output an asterisk alone on the following + line to indicate that identical blocks have been elided. */ +static int abbreviate_duplicate_blocks = 1; + +/* An array of specs describing how to format each input block. */ +static size_t n_specs; +static struct tspec *spec; + +/* The number of input bytes formatted per output line. It must be + a multiple of the least common multiple of the sizes associated with + the specified output types. It should be as large as possible, but + no larger than 16 -- unless specified with the -w option. */ +static size_t bytes_per_block; + +/* Human-readable representation of *file_list (for error messages). + It differs from *file_list only when *file_list is "-". */ +static char const *input_filename; + +/* A NULL-terminated list of the file-arguments from the command line. */ +static char const *const *file_list; + +/* Initializer for file_list if no file-arguments + were specified on the command line. */ +static char const *const default_file_list[] = { "-", NULL }; + +/* The input stream associated with the current file. */ +static FILE *in_stream; + +static int ioerror; + +#define MAX_INTEGRAL_TYPE_SIZE sizeof(ulonglong_t) +static unsigned char integral_type_size[MAX_INTEGRAL_TYPE_SIZE + 1] = { + [sizeof(char)] = CHAR, +#if USHRT_MAX != UCHAR_MAX + [sizeof(short)] = SHORT, +#endif +#if UINT_MAX != USHRT_MAX + [sizeof(int)] = INT, +#endif +#if ULONG_MAX != UINT_MAX + [sizeof(long)] = LONG, +#endif +#if ULLONG_MAX != ULONG_MAX + [sizeof(ulonglong_t)] = LONG_LONG, +#endif +}; + +#define MAX_FP_TYPE_SIZE sizeof(longdouble_t) +static unsigned char fp_type_size[MAX_FP_TYPE_SIZE + 1] = { + /* gcc seems to allow repeated indexes. Last one stays */ + [sizeof(longdouble_t)] = FLOAT_LONG_DOUBLE, + [sizeof(double)] = FLOAT_DOUBLE, + [sizeof(float)] = FLOAT_SINGLE, +}; + + +static unsigned +gcd(unsigned u, unsigned v) +{ + unsigned t; + while (v != 0) { + t = u % v; + u = v; + v = t; + } + return u; +} + +/* Compute the least common multiple of U and V. */ +static unsigned +lcm(unsigned u, unsigned v) { + unsigned t = gcd(u, v); + if (t == 0) + return 0; + return u * v / t; +} + +static void +print_s_char(size_t n_bytes, const char *block, const char *fmt_string) +{ + while (n_bytes--) { + int tmp = *(signed char *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned char); + } +} + +static void +print_char(size_t n_bytes, const char *block, const char *fmt_string) +{ + while (n_bytes--) { + unsigned tmp = *(unsigned char *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned char); + } +} + +static void +print_s_short(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(signed short); + while (n_bytes--) { + int tmp = *(signed short *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned short); + } +} + +static void +print_short(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(unsigned short); + while (n_bytes--) { + unsigned tmp = *(unsigned short *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned short); + } +} + +static void +print_int(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(unsigned); + while (n_bytes--) { + unsigned tmp = *(unsigned *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned); + } +} + +#if UINT_MAX == ULONG_MAX +# define print_long print_int +#else +static void +print_long(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(unsigned long); + while (n_bytes--) { + unsigned long tmp = *(unsigned long *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned long); + } +} +#endif + +#if ULONG_MAX == ULLONG_MAX +# define print_long_long print_long +#else +static void +print_long_long(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(ulonglong_t); + while (n_bytes--) { + ulonglong_t tmp = *(ulonglong_t *) block; + printf(fmt_string, tmp); + block += sizeof(ulonglong_t); + } +} +#endif + +static void +print_float(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(float); + while (n_bytes--) { + float tmp = *(float *) block; + printf(fmt_string, tmp); + block += sizeof(float); + } +} + +static void +print_double(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(double); + while (n_bytes--) { + double tmp = *(double *) block; + printf(fmt_string, tmp); + block += sizeof(double); + } +} + +static void +print_long_double(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(longdouble_t); + while (n_bytes--) { + longdouble_t tmp = *(longdouble_t *) block; + printf(fmt_string, tmp); + block += sizeof(longdouble_t); + } +} + +/* print_[named]_ascii are optimized for speed. + * Remember, someday you may want to pump gigabytes thru this thing. + * Saving a dozen of .text bytes here is counter-productive */ + +static void +print_named_ascii(size_t n_bytes, const char *block, + const char *unused_fmt_string ATTRIBUTE_UNUSED) +{ + /* Names for some non-printing characters. */ + static const char charname[33][3] = { + "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", + " bs", " ht", " nl", " vt", " ff", " cr", " so", " si", + "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb", + "can", " em", "sub", "esc", " fs", " gs", " rs", " us", + " sp" + }; + // buf[N] pos: 01234 56789 + char buf[12] = " x\0 0xx\0"; + // actually " x\0 xxx\0", but I want to share the string with below. + // [12] because we take three 32bit stack slots anyway, and + // gcc is too dumb to initialize with constant stores, + // it copies initializer from rodata. Oh well. + + while (n_bytes--) { + unsigned masked_c = *(unsigned char *) block++; + + masked_c &= 0x7f; + if (masked_c == 0x7f) { + fputs(" del", stdout); + continue; + } + if (masked_c > ' ') { + buf[3] = masked_c; + fputs(buf, stdout); + continue; + } + /* Why? Because printf(" %3.3s") is much slower... */ + buf[6] = charname[masked_c][0]; + buf[7] = charname[masked_c][1]; + buf[8] = charname[masked_c][2]; + fputs(buf+5, stdout); + } +} + +static void +print_ascii(size_t n_bytes, const char *block, + const char *unused_fmt_string ATTRIBUTE_UNUSED) +{ + // buf[N] pos: 01234 56789 + char buf[12] = " x\0 0xx\0"; + + while (n_bytes--) { + const char *s; + unsigned c = *(unsigned char *) block++; + + if (ISPRINT(c)) { + buf[3] = c; + fputs(buf, stdout); + continue; + } + switch (c) { + case '\0': + s = " \\0"; + break; + case '\007': + s = " \\a"; + break; + case '\b': + s = " \\b"; + break; + case '\f': + s = " \\f"; + break; + case '\n': + s = " \\n"; + break; + case '\r': + s = " \\r"; + break; + case '\t': + s = " \\t"; + break; + case '\v': + s = " \\v"; + break; + case '\x7f': + s = " 177"; + break; + default: /* c is never larger than 040 */ + buf[7] = (c >> 3) + '0'; + buf[8] = (c & 7) + '0'; + s = buf + 5; + } + fputs(s, stdout); + } +} + +/* Given a list of one or more input filenames FILE_LIST, set the global + file pointer IN_STREAM and the global string INPUT_FILENAME to the + first one that can be successfully opened. Modify FILE_LIST to + reference the next filename in the list. A file name of "-" is + interpreted as standard input. If any file open fails, give an error + message and return nonzero. */ + +static void +open_next_file(void) +{ + while(1) { + input_filename = *file_list; + if (!input_filename) + return; + file_list++; + in_stream = fopen_or_warn_stdin(input_filename); + if (in_stream) { + if (in_stream == stdin) + input_filename = bb_msg_standard_input; + break; + } + ioerror = 1; + } + + if (limit_bytes_to_format && !flag_dump_strings) + setbuf(in_stream, NULL); +} + +/* Test whether there have been errors on in_stream, and close it if + it is not standard input. Return nonzero if there has been an error + on in_stream or stdout; return zero otherwise. This function will + report more than one error only if both a read and a write error + have occurred. IN_ERRNO, if nonzero, is the error number + corresponding to the most recent action for IN_STREAM. */ + +static void +check_and_close(void) +{ + if (in_stream) { + if (ferror(in_stream)) { + bb_error_msg("%s: read error", input_filename); + ioerror = 1; + } + fclose_if_not_stdin(in_stream); + in_stream = NULL; + } + + if (ferror(stdout)) { + bb_error_msg("write error"); + ioerror = 1; + } +} + +/* If S points to a single valid modern od format string, put + a description of that format in *TSPEC, make *NEXT point at the + character following the just-decoded format (if *NEXT is non-NULL), + and return zero. For example, if S were "d4afL" + *NEXT would be set to "afL" and *TSPEC would be + { + fmt = SIGNED_DECIMAL; + size = INT or LONG; (whichever integral_type_size[4] resolves to) + print_function = print_int; (assuming size == INT) + fmt_string = "%011d%c"; + } + S_ORIG is solely for reporting errors. It should be the full format + string argument. */ + +static void +decode_one_format(const char *s_orig, const char *s, const char **next, + struct tspec *tspec) +{ + enum size_spec size_spec; + unsigned size; + enum output_format fmt; + const char *p; + char *end; + char *fmt_string = NULL; + void (*print_function) (size_t, const char *, const char *); + unsigned c; + unsigned field_width = 0; + int pos; + + assert(tspec != NULL); + + switch (*s) { + case 'd': + case 'o': + case 'u': + case 'x': { + static const char CSIL[] = "CSIL"; + + c = *s++; + p = strchr(CSIL, *s); + if (!p) { + size = sizeof(int); + if (isdigit(s[0])) { + size = bb_strtou(s, &end, 0); + if (errno == ERANGE + || MAX_INTEGRAL_TYPE_SIZE < size + || integral_type_size[size] == NO_SIZE + ) { + bb_error_msg_and_die("invalid type string '%s'; " + "%u-byte %s type is not supported", + s_orig, size, "integral"); + } + s = end; + } + } else { + static const uint8_t CSIL_sizeof[] = { + sizeof(char), + sizeof(short), + sizeof(int), + sizeof(long), + }; + size = CSIL_sizeof[p - CSIL]; + } + +#define ISPEC_TO_FORMAT(Spec, Min_format, Long_format, Max_format) \ + ((Spec) == LONG_LONG ? (Max_format) \ + : ((Spec) == LONG ? (Long_format) : (Min_format))) + +#define FMT_BYTES_ALLOCATED 9 + size_spec = integral_type_size[size]; + + { + static const char doux[] = "doux"; + static const char doux_fmt_letter[][4] = { + "lld", "llo", "llu", "llx" + }; + static const enum output_format doux_fmt[] = { + SIGNED_DECIMAL, + OCTAL, + UNSIGNED_DECIMAL, + HEXADECIMAL, + }; + static const uint8_t *const doux_bytes_to_XXX[] = { + bytes_to_signed_dec_digits, + bytes_to_oct_digits, + bytes_to_unsigned_dec_digits, + bytes_to_hex_digits, + }; + static const char doux_fmtstring[][sizeof(" %%0%u%s")] = { + " %%%u%s", + " %%0%u%s", + " %%%u%s", + " %%0%u%s", + }; + + pos = strchr(doux, c) - doux; + fmt = doux_fmt[pos]; + field_width = doux_bytes_to_XXX[pos][size]; + p = doux_fmt_letter[pos] + 2; + if (size_spec == LONG) p--; + if (size_spec == LONG_LONG) p -= 2; + fmt_string = xasprintf(doux_fmtstring[pos], field_width, p); + } + + switch (size_spec) { + case CHAR: + print_function = (fmt == SIGNED_DECIMAL + ? print_s_char + : print_char); + break; + case SHORT: + print_function = (fmt == SIGNED_DECIMAL + ? print_s_short + : print_short); + break; + case INT: + print_function = print_int; + break; + case LONG: + print_function = print_long; + break; + default: /* case LONG_LONG: */ + print_function = print_long_long; + break; + } + break; + } + + case 'f': { + static const char FDL[] = "FDL"; + + fmt = FLOATING_POINT; + ++s; + p = strchr(FDL, *s); + if (!p) { + size = sizeof(double); + if (isdigit(s[0])) { + size = bb_strtou(s, &end, 0); + if (errno == ERANGE || size > MAX_FP_TYPE_SIZE + || fp_type_size[size] == NO_SIZE + ) { + bb_error_msg_and_die("invalid type string '%s'; " + "%u-byte %s type is not supported", + s_orig, size, "floating point"); + } + s = end; + } + } else { + static const uint8_t FDL_sizeof[] = { + sizeof(float), + sizeof(double), + sizeof(longdouble_t), + }; + + size = FDL_sizeof[p - FDL]; + } + + size_spec = fp_type_size[size]; + + switch (size_spec) { + case FLOAT_SINGLE: + print_function = print_float; + field_width = FLT_DIG + 8; + /* Don't use %#e; not all systems support it. */ + fmt_string = xasprintf(" %%%d.%de", field_width, FLT_DIG); + break; + case FLOAT_DOUBLE: + print_function = print_double; + field_width = DBL_DIG + 8; + fmt_string = xasprintf(" %%%d.%de", field_width, DBL_DIG); + break; + default: /* case FLOAT_LONG_DOUBLE: */ + print_function = print_long_double; + field_width = LDBL_DIG + 8; + fmt_string = xasprintf(" %%%d.%dLe", field_width, LDBL_DIG); + break; + } + break; + } + + case 'a': + ++s; + fmt = NAMED_CHARACTER; + size_spec = CHAR; + print_function = print_named_ascii; + field_width = 3; + break; + case 'c': + ++s; + fmt = CHARACTER; + size_spec = CHAR; + print_function = print_ascii; + field_width = 3; + break; + default: + bb_error_msg_and_die("invalid character '%c' " + "in type string '%s'", *s, s_orig); + } + + tspec->size = size_spec; + tspec->fmt = fmt; + tspec->print_function = print_function; + tspec->fmt_string = fmt_string; + + tspec->field_width = field_width; + tspec->hexl_mode_trailer = (*s == 'z'); + if (tspec->hexl_mode_trailer) + s++; + + if (next != NULL) + *next = s; +} + +/* Decode the modern od format string S. Append the decoded + representation to the global array SPEC, reallocating SPEC if + necessary. Return zero if S is valid, nonzero otherwise. */ + +static void +decode_format_string(const char *s) +{ + const char *s_orig = s; + + while (*s != '\0') { + struct tspec tspec; + const char *next; + + decode_one_format(s_orig, s, &next, &tspec); + + assert(s != next); + s = next; + n_specs++; + spec = xrealloc(spec, n_specs * sizeof(*spec)); + memcpy(&spec[n_specs-1], &tspec, sizeof *spec); + } +} + +/* Given a list of one or more input filenames FILE_LIST, set the global + file pointer IN_STREAM to position N_SKIP in the concatenation of + those files. If any file operation fails or if there are fewer than + N_SKIP bytes in the combined input, give an error message and return + nonzero. When possible, use seek rather than read operations to + advance IN_STREAM. */ + +static void +skip(off_t n_skip) +{ + if (n_skip == 0) + return; + + while (in_stream) { /* !EOF */ + struct stat file_stats; + + /* First try seeking. For large offsets, this extra work is + worthwhile. If the offset is below some threshold it may be + more efficient to move the pointer by reading. There are two + issues when trying to seek: + - the file must be seekable. + - before seeking to the specified position, make sure + that the new position is in the current file. + Try to do that by getting file's size using fstat. + But that will work only for regular files. */ + + /* The st_size field is valid only for regular files + (and for symbolic links, which cannot occur here). + If the number of bytes left to skip is at least + as large as the size of the current file, we can + decrement n_skip and go on to the next file. */ + if (fstat(fileno(in_stream), &file_stats) == 0 + && S_ISREG(file_stats.st_mode) && file_stats.st_size >= 0 + ) { + if (file_stats.st_size < n_skip) { + n_skip -= file_stats.st_size; + /* take check&close / open_next route */ + } else { + if (fseeko(in_stream, n_skip, SEEK_CUR) != 0) + ioerror = 1; + return; + } + } else { + /* If it's not a regular file with nonnegative size, + position the file pointer by reading. */ + char buf[BUFSIZ]; + size_t n_bytes_read, n_bytes_to_read = BUFSIZ; + + while (n_skip > 0) { + if (n_skip < n_bytes_to_read) + n_bytes_to_read = n_skip; + n_bytes_read = fread(buf, 1, n_bytes_to_read, in_stream); + n_skip -= n_bytes_read; + if (n_bytes_read != n_bytes_to_read) + break; /* EOF on this file or error */ + } + } + if (n_skip == 0) + return; + + check_and_close(); + open_next_file(); + } + + if (n_skip) + bb_error_msg_and_die("cannot skip past end of combined input"); +} + + +typedef void FN_format_address(off_t address, char c); + +static void +format_address_none(off_t address ATTRIBUTE_UNUSED, char c ATTRIBUTE_UNUSED) +{ +} + +static char address_fmt[] = "%0n"OFF_FMT"xc"; +/* Corresponds to 'x' above */ +#define address_base_char address_fmt[sizeof(address_fmt)-3] +/* Corresponds to 'n' above */ +#define address_pad_len_char address_fmt[2] + +static void +format_address_std(off_t address, char c) +{ + /* Corresponds to 'c' */ + address_fmt[sizeof(address_fmt)-2] = c; + printf(address_fmt, address); +} + +#if ENABLE_GETOPT_LONG +/* only used with --traditional */ +static void +format_address_paren(off_t address, char c) +{ + putchar('('); + format_address_std(address, ')'); + /* BUG in coreutils 5.2.1! must be "if (c) putchar(c);" */ + putchar(c); +} + +static void +format_address_label(off_t address, char c) +{ + format_address_std(address, ' '); + format_address_paren(address + pseudo_offset, c); +} +#endif + +static void +dump_hexl_mode_trailer(size_t n_bytes, const char *block) +{ + fputs(" >", stdout); + while (n_bytes--) { + unsigned c = *(unsigned char *) block++; + c = (ISPRINT(c) ? c : '.'); + putchar(c); + } + putchar('<'); +} + +/* Write N_BYTES bytes from CURR_BLOCK to standard output once for each + of the N_SPEC format specs. CURRENT_OFFSET is the byte address of + CURR_BLOCK in the concatenation of input files, and it is printed + (optionally) only before the output line associated with the first + format spec. When duplicate blocks are being abbreviated, the output + for a sequence of identical input blocks is the output for the first + block followed by an asterisk alone on a line. It is valid to compare + the blocks PREV_BLOCK and CURR_BLOCK only when N_BYTES == BYTES_PER_BLOCK. + That condition may be false only for the last input block -- and then + only when it has not been padded to length BYTES_PER_BLOCK. */ + +static void +write_block(off_t current_offset, size_t n_bytes, + const char *prev_block, const char *curr_block) +{ + static char first = 1; + static char prev_pair_equal = 0; + size_t i; + + if (abbreviate_duplicate_blocks + && !first + && n_bytes == bytes_per_block + && memcmp(prev_block, curr_block, bytes_per_block) == 0 + ) { + if (prev_pair_equal) { + /* The two preceding blocks were equal, and the current + block is the same as the last one, so print nothing. */ + } else { + puts("*"); + prev_pair_equal = 1; + } + } else { + first = 0; + prev_pair_equal = 0; + for (i = 0; i < n_specs; i++) { + if (i == 0) + format_address(current_offset, '\0'); + else + printf("%*s", address_pad_len_char - '0', ""); + (*spec[i].print_function) (n_bytes, curr_block, spec[i].fmt_string); + if (spec[i].hexl_mode_trailer) { + /* space-pad out to full line width, then dump the trailer */ + int datum_width = width_bytes[spec[i].size]; + int blank_fields = (bytes_per_block - n_bytes) / datum_width; + int field_width = spec[i].field_width + 1; + printf("%*s", blank_fields * field_width, ""); + dump_hexl_mode_trailer(n_bytes, curr_block); + } + putchar('\n'); + } + } +} + +static void +read_block(size_t n, char *block, size_t *n_bytes_in_buffer) +{ + assert(0 < n && n <= bytes_per_block); + + *n_bytes_in_buffer = 0; + + if (n == 0) + return; + + while (in_stream != NULL) { /* EOF. */ + size_t n_needed; + size_t n_read; + + n_needed = n - *n_bytes_in_buffer; + n_read = fread(block + *n_bytes_in_buffer, 1, n_needed, in_stream); + *n_bytes_in_buffer += n_read; + if (n_read == n_needed) + break; + /* error check is done in check_and_close */ + check_and_close(); + open_next_file(); + } +} + +/* Return the least common multiple of the sizes associated + with the format specs. */ + +static int +get_lcm(void) +{ + size_t i; + int l_c_m = 1; + + for (i = 0; i < n_specs; i++) + l_c_m = lcm(l_c_m, width_bytes[(int) spec[i].size]); + return l_c_m; +} + +#if ENABLE_GETOPT_LONG +/* If S is a valid traditional offset specification with an optional + leading '+' return nonzero and set *OFFSET to the offset it denotes. */ + +static int +parse_old_offset(const char *s, off_t *offset) +{ + static const struct suffix_mult Bb[] = { + { "B", 1024 }, + { "b", 512 }, + { NULL, 0 } + }; + char *p; + int radix; + + /* Skip over any leading '+'. */ + if (s[0] == '+') ++s; + + /* Determine the radix we'll use to interpret S. If there is a '.', + * it's decimal, otherwise, if the string begins with '0X'or '0x', + * it's hexadecimal, else octal. */ + p = strchr(s, '.'); + radix = 8; + if (p) { + p[0] = '\0'; /* cheating */ + radix = 10; + } else if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) + radix = 16; + + *offset = xstrtooff_sfx(s, radix, Bb); + if (p) p[0] = '.'; + + return (*offset >= 0); +} +#endif + +/* Read a chunk of size BYTES_PER_BLOCK from the input files, write the + formatted block to standard output, and repeat until the specified + maximum number of bytes has been read or until all input has been + processed. If the last block read is smaller than BYTES_PER_BLOCK + and its size is not a multiple of the size associated with a format + spec, extend the input block with zero bytes until its length is a + multiple of all format spec sizes. Write the final block. Finally, + write on a line by itself the offset of the byte after the last byte + read. Accumulate return values from calls to read_block and + check_and_close, and if any was nonzero, return nonzero. + Otherwise, return zero. */ + +static void +dump(void) +{ + char *block[2]; + off_t current_offset; + int idx; + size_t n_bytes_read; + + block[0] = xmalloc(2*bytes_per_block); + block[1] = block[0] + bytes_per_block; + + current_offset = n_bytes_to_skip; + + idx = 0; + if (limit_bytes_to_format) { + while (1) { + size_t n_needed; + if (current_offset >= end_offset) { + n_bytes_read = 0; + break; + } + n_needed = MIN(end_offset - current_offset, + (off_t) bytes_per_block); + read_block(n_needed, block[idx], &n_bytes_read); + if (n_bytes_read < bytes_per_block) + break; + assert(n_bytes_read == bytes_per_block); + write_block(current_offset, n_bytes_read, + block[!idx], block[idx]); + current_offset += n_bytes_read; + idx = !idx; + } + } else { + while (1) { + read_block(bytes_per_block, block[idx], &n_bytes_read); + if (n_bytes_read < bytes_per_block) + break; + assert(n_bytes_read == bytes_per_block); + write_block(current_offset, n_bytes_read, + block[!idx], block[idx]); + current_offset += n_bytes_read; + idx = !idx; + } + } + + if (n_bytes_read > 0) { + int l_c_m; + size_t bytes_to_write; + + l_c_m = get_lcm(); + + /* Make bytes_to_write the smallest multiple of l_c_m that + is at least as large as n_bytes_read. */ + bytes_to_write = l_c_m * ((n_bytes_read + l_c_m - 1) / l_c_m); + + memset(block[idx] + n_bytes_read, 0, bytes_to_write - n_bytes_read); + write_block(current_offset, bytes_to_write, + block[!idx], block[idx]); + current_offset += n_bytes_read; + } + + format_address(current_offset, '\n'); + + if (limit_bytes_to_format && current_offset >= end_offset) + check_and_close(); + + free(block[0]); +} + +/* Read a single byte into *C from the concatenation of the input files + named in the global array FILE_LIST. On the first call to this + function, the global variable IN_STREAM is expected to be an open + stream associated with the input file INPUT_FILENAME. If IN_STREAM + is at end-of-file, close it and update the global variables IN_STREAM + and INPUT_FILENAME so they correspond to the next file in the list. + Then try to read a byte from the newly opened file. Repeat if + necessary until EOF is reached for the last file in FILE_LIST, then + set *C to EOF and return. Subsequent calls do likewise. The return + value is nonzero if any errors occured, zero otherwise. */ + +static void +read_char(int *c) +{ + while (in_stream) { /* !EOF */ + *c = fgetc(in_stream); + if (*c != EOF) + return; + check_and_close(); + open_next_file(); + } + *c = EOF; +} + +/* Read N bytes into BLOCK from the concatenation of the input files + named in the global array FILE_LIST. On the first call to this + function, the global variable IN_STREAM is expected to be an open + stream associated with the input file INPUT_FILENAME. If all N + bytes cannot be read from IN_STREAM, close IN_STREAM and update + the global variables IN_STREAM and INPUT_FILENAME. Then try to + read the remaining bytes from the newly opened file. Repeat if + necessary until EOF is reached for the last file in FILE_LIST. + On subsequent calls, don't modify BLOCK and return zero. Set + *N_BYTES_IN_BUFFER to the number of bytes read. If an error occurs, + it will be detected through ferror when the stream is about to be + closed. If there is an error, give a message but continue reading + as usual and return nonzero. Otherwise return zero. */ + +/* STRINGS mode. Find each "string constant" in the input. + A string constant is a run of at least 'string_min' ASCII + graphic (or formatting) characters terminated by a null. + Based on a function written by Richard Stallman for a + traditional version of od. Return nonzero if an error + occurs. Otherwise, return zero. */ + +static void +dump_strings(void) +{ + size_t bufsize = MAX(100, string_min); + char *buf = xmalloc(bufsize); + off_t address = n_bytes_to_skip; + + while (1) { + size_t i; + int c; + + /* See if the next 'string_min' chars are all printing chars. */ + tryline: + if (limit_bytes_to_format && (end_offset - string_min <= address)) + break; + i = 0; + while (!limit_bytes_to_format || address < end_offset) { + if (i == bufsize) { + bufsize += bufsize/8; + buf = xrealloc(buf, bufsize); + } + read_char(&c); + if (c < 0) { /* EOF */ + free(buf); + return; + } + address++; + if (!c) + break; + if (!ISPRINT(c)) + goto tryline; /* It isn't; give up on this string. */ + buf[i++] = c; /* String continues; store it all. */ + } + + if (i < string_min) /* Too short! */ + goto tryline; + + /* If we get here, the string is all printable and null-terminated, + * so print it. It is all in 'buf' and 'i' is its length. */ + buf[i] = 0; + format_address(address - i - 1, ' '); + + for (i = 0; (c = buf[i]); i++) { + switch (c) { + case '\007': fputs("\\a", stdout); break; + case '\b': fputs("\\b", stdout); break; + case '\f': fputs("\\f", stdout); break; + case '\n': fputs("\\n", stdout); break; + case '\r': fputs("\\r", stdout); break; + case '\t': fputs("\\t", stdout); break; + case '\v': fputs("\\v", stdout); break; + default: putc(c, stdout); + } + } + putchar('\n'); + } + + /* We reach this point only if we search through + (max_bytes_to_format - string_min) bytes before reaching EOF. */ + free(buf); + + check_and_close(); +} + +int +od_main(int argc, char **argv) +{ + static const struct suffix_mult bkm[] = { + { "b", 512 }, + { "k", 1024 }, + { "m", 1024*1024 }, + { NULL, 0 } + }; + unsigned opt; + int l_c_m; + /* The old-style 'pseudo starting address' to be printed in parentheses + after any true address. */ + off_t pseudo_start = 0; // only for gcc + enum { + OPT_A = 1 << 0, + OPT_N = 1 << 1, + OPT_a = 1 << 2, + OPT_b = 1 << 3, + OPT_c = 1 << 4, + OPT_d = 1 << 5, + OPT_f = 1 << 6, + OPT_h = 1 << 7, + OPT_i = 1 << 8, + OPT_j = 1 << 9, + OPT_l = 1 << 10, + OPT_o = 1 << 11, + OPT_t = 1 << 12, + OPT_v = 1 << 13, + OPT_x = 1 << 14, + OPT_s = 1 << 15, + OPT_S = 1 << 16, + OPT_w = 1 << 17, + OPT_traditional = (1 << 18) * ENABLE_GETOPT_LONG, + }; +#if ENABLE_GETOPT_LONG + static const struct option long_options[] = { + { "skip-bytes", required_argument, NULL, 'j' }, + { "address-radix", required_argument, NULL, 'A' }, + { "read-bytes", required_argument, NULL, 'N' }, + { "format", required_argument, NULL, 't' }, + { "output-duplicates", no_argument, NULL, 'v' }, + { "strings", optional_argument, NULL, 'S' }, + { "width", optional_argument, NULL, 'w' }, + { "traditional", no_argument, NULL, 0xff }, + { NULL, 0, NULL, 0 } + }; +#endif + char *str_A, *str_N, *str_j, *str_S; + char *str_w = NULL; + llist_t *lst_t = NULL; + + spec = NULL; + format_address = format_address_std; + address_base_char = 'o'; + address_pad_len_char = '7'; + flag_dump_strings = 0; + + /* Parse command line */ + opt_complementary = "t::"; // list +#if ENABLE_GETOPT_LONG + applet_long_options = long_options; +#endif + opt = getopt32(argc, argv, "A:N:abcdfhij:lot:vxsS:" + "w::", // -w with optional param + // -S was -s and also had optional parameter + // but in coreutils 6.3 it was renamed and now has + // _mandatory_ parameter + &str_A, &str_N, &str_j, &lst_t, &str_S, &str_w); + argc -= optind; + argv += optind; + if (opt & OPT_A) { + static const char doxn[] = "doxn"; + static const char doxn_address_base_char[] = { + 'u', 'o', 'x', /* '?' fourth one is not important */ + }; + static const uint8_t doxn_address_pad_len_char[] = { + '7', '7', '6', /* '?' */ + }; + char *p; + int pos; + p = strchr(doxn, str_A[0]); + if (!p) + bb_error_msg_and_die("bad output address radix " + "'%c' (must be [doxn])", str_A[0]); + pos = p - doxn; + if (pos == 3) format_address = format_address_none; + address_base_char = doxn_address_base_char[pos]; + address_pad_len_char = doxn_address_pad_len_char[pos]; + } + if (opt & OPT_N) { + limit_bytes_to_format = 1; + max_bytes_to_format = xstrtooff_sfx(str_N, 0, bkm); + } + if (opt & OPT_a) decode_format_string("a"); + if (opt & OPT_b) decode_format_string("oC"); + if (opt & OPT_c) decode_format_string("c"); + if (opt & OPT_d) decode_format_string("u2"); + if (opt & OPT_f) decode_format_string("fF"); + if (opt & OPT_h) decode_format_string("x2"); + if (opt & OPT_i) decode_format_string("d2"); + if (opt & OPT_j) n_bytes_to_skip = xstrtooff_sfx(str_j, 0, bkm); + if (opt & OPT_l) decode_format_string("d4"); + if (opt & OPT_o) decode_format_string("o2"); + //if (opt & OPT_t)... + lst_t = rev_llist(lst_t); + while (lst_t) { + decode_format_string(lst_t->data); + lst_t = lst_t->link; + } + if (opt & OPT_v) abbreviate_duplicate_blocks = 0; + if (opt & OPT_x) decode_format_string("x2"); + if (opt & OPT_s) decode_format_string("d2"); + if (opt & OPT_S) { + string_min = 3; + string_min = xstrtou_sfx(str_S, 0, bkm); + flag_dump_strings = 1; + } + //if (opt & OPT_w)... + //if (opt & OPT_traditional)... + + if (flag_dump_strings && n_specs > 0) + bb_error_msg_and_die("no type may be specified when dumping strings"); + + /* If the --traditional option is used, there may be from + * 0 to 3 remaining command line arguments; handle each case + * separately. + * od [file] [[+]offset[.][b] [[+]label[.][b]]] + * The offset and pseudo_start have the same syntax. + * + * FIXME: POSIX 1003.1-2001 with XSI requires support for the + * traditional syntax even if --traditional is not given. */ + +#if ENABLE_GETOPT_LONG + if (opt & OPT_traditional) { + off_t o1, o2; + + if (argc == 1) { + if (parse_old_offset(argv[0], &o1)) { + n_bytes_to_skip = o1; + --argc; + ++argv; + } + } else if (argc == 2) { + if (parse_old_offset(argv[0], &o1) + && parse_old_offset(argv[1], &o2) + ) { + n_bytes_to_skip = o1; + flag_pseudo_start = 1; + pseudo_start = o2; + argv += 2; + argc -= 2; + } else if (parse_old_offset(argv[1], &o2)) { + n_bytes_to_skip = o2; + --argc; + argv[1] = argv[0]; + ++argv; + } else { + bb_error_msg_and_die("invalid second operand " + "in compatibility mode '%s'", argv[1]); + } + } else if (argc == 3) { + if (parse_old_offset(argv[1], &o1) + && parse_old_offset(argv[2], &o2) + ) { + n_bytes_to_skip = o1; + flag_pseudo_start = 1; + pseudo_start = o2; + argv[2] = argv[0]; + argv += 2; + argc -= 2; + } else { + bb_error_msg_and_die("in compatibility mode " + "the last two arguments must be offsets"); + } + } else if (argc > 3) { + bb_error_msg_and_die("compatibility mode supports " + "at most three arguments"); + } + + if (flag_pseudo_start) { + if (format_address == format_address_none) { + address_base_char = 'o'; + address_pad_len_char = '7'; + format_address = format_address_paren; + } else + format_address = format_address_label; + } + } +#endif + + if (limit_bytes_to_format) { + end_offset = n_bytes_to_skip + max_bytes_to_format; + if (end_offset < n_bytes_to_skip) + bb_error_msg_and_die("skip-bytes + read-bytes is too large"); + } + + if (n_specs == 0) { + decode_format_string("o2"); + n_specs = 1; + } + + /* If no files were listed on the command line, + set the global pointer FILE_LIST so that it + references the null-terminated list of one name: "-". */ + file_list = default_file_list; + if (argc > 0) { + /* Set the global pointer FILE_LIST so that it + references the first file-argument on the command-line. */ + file_list = (char const *const *) argv; + } + + /* open the first input file */ + open_next_file(); + /* skip over any unwanted header bytes */ + skip(n_bytes_to_skip); + if (!in_stream) + return 1; + + pseudo_offset = (flag_pseudo_start ? pseudo_start - n_bytes_to_skip : 0); + + /* Compute output block length. */ + l_c_m = get_lcm(); + + if (opt & OPT_w) { /* -w: width */ + bytes_per_block = 32; + if (str_w) + bytes_per_block = xatou(str_w); + if (!bytes_per_block || bytes_per_block % l_c_m != 0) { + bb_error_msg("warning: invalid width %u; using %d instead", + bytes_per_block, l_c_m); + bytes_per_block = l_c_m; + } + } else { + bytes_per_block = l_c_m; + if (l_c_m < DEFAULT_BYTES_PER_BLOCK) + bytes_per_block *= DEFAULT_BYTES_PER_BLOCK / l_c_m; + } + +#ifdef DEBUG + for (i = 0; i < n_specs; i++) { + printf("%d: fmt=\"%s\" width=%d\n", + i, spec[i].fmt_string, width_bytes[spec[i].size]); + } +#endif + + if (flag_dump_strings) + dump_strings(); + else + dump(); + + if (fclose(stdin) == EOF) + bb_perror_msg_and_die(bb_msg_standard_input); + + return (ioerror != 0); /* err != 0 - return 1 (failure) */ +} diff --git a/coreutils/printenv.c b/coreutils/printenv.c new file mode 100644 index 000000000..ec50f7151 --- /dev/null +++ b/coreutils/printenv.c @@ -0,0 +1,41 @@ +/* vi: set sw=4 ts=4: */ +/* + * printenv implementation for busybox + * + * Copyright (C) 2005 by Erik Andersen + * Copyright (C) 2005 by Mike Frysinger + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include "busybox.h" + +int printenv_main(int argc, char **argv) +{ + extern char **environ; + int e = 0; + + /* no variables specified, show whole env */ + if (argc == 1) + while (environ[e]) + puts(environ[e++]); + + /* search for specified variables and print them out if found */ + else { + int i; + size_t l; + char *arg, *env; + + for (i=1; (arg = argv[i]); ++i) + for (; (env = environ[e]); ++e) { + l = strlen(arg); + if (!strncmp(env, arg, l) && env[l] == '=') + puts(env + l + 1); + } + } + + fflush_stdout_and_exit(0); +} diff --git a/coreutils/printf.c b/coreutils/printf.c new file mode 100644 index 000000000..0e818354f --- /dev/null +++ b/coreutils/printf.c @@ -0,0 +1,321 @@ +/* vi: set sw=4 ts=4: */ +/* printf - format and print data + + Copyright 1999 Dave Cinege + Portions copyright (C) 1990-1996 Free Software Foundation, Inc. + + Licensed under GPL v2 or later, see file LICENSE in this tarball for details. +*/ + +/* Usage: printf format [argument...] + + A front end to the printf function that lets it be used from the shell. + + Backslash escapes: + + \" = double quote + \\ = backslash + \a = alert (bell) + \b = backspace + \c = produce no further output + \f = form feed + \n = new line + \r = carriage return + \t = horizontal tab + \v = vertical tab + \0ooo = octal number (ooo is 0 to 3 digits) + \xhhh = hexadecimal number (hhh is 1 to 3 digits) + + Additional directive: + + %b = print an argument string, interpreting backslash escapes + + The 'format' argument is re-used as many times as necessary + to convert all of the given arguments. + + David MacKenzie */ + + +// 19990508 Busy Boxed! Dave Cinege + +#include "busybox.h" + +static int print_formatted(char *format, int argc, char **argv); +static void print_direc(char *start, size_t length, + int field_width, int precision, char *argument); + +typedef void (*converter)(char *arg, void *result); + +static void multiconvert(char *arg, void *result, converter convert) +{ + char s[16]; + if (*arg == '"' || *arg == '\'') { + sprintf(s, "%d", (unsigned char)arg[1]); + arg = s; + } + convert(arg, result); + if (errno) /* Huh, looks strange... bug? */ + fputs(arg, stderr); +} + +static void conv_strtoul(char *arg, void *result) +{ + *(unsigned long*)result = bb_strtoul(arg, NULL, 10); +} +static void conv_strtol(char *arg, void *result) +{ + *(long*)result = bb_strtol(arg, NULL, 10); +} +static void conv_strtod(char *arg, void *result) +{ + char *end; + /* Well, this one allows leading whitespace... so what */ + /* What I like much less is that "-" is accepted too! :( */ + *(double*)result = strtod(arg, &end); + if (end[0]) errno = ERANGE; +} + +static unsigned long my_xstrtoul(char *arg) +{ + unsigned long result; + multiconvert(arg, &result, conv_strtoul); + return result; +} + +static long my_xstrtol(char *arg) +{ + long result; + multiconvert(arg, &result, conv_strtol); + return result; +} + +static double my_xstrtod(char *arg) +{ + double result; + multiconvert(arg, &result, conv_strtod); + return result; +} + +static void print_esc_string(char *str) +{ + for (; *str; str++) { + if (*str == '\\') { + str++; + putchar(bb_process_escape_sequence((const char **)&str)); + } else { + putchar(*str); + } + + } +} + +int printf_main(int argc, char **argv) +{ + char *format; + int args_used; + + if (argc <= 1 || argv[1][0] == '-') { + bb_show_usage(); + } + + format = argv[1]; + argc -= 2; + argv += 2; + + do { + args_used = print_formatted(format, argc, argv); + argc -= args_used; + argv += args_used; + } + while (args_used > 0 && argc > 0); + +/* if (argc > 0) + fprintf(stderr, "excess args ignored"); +*/ + + return EXIT_SUCCESS; +} + +/* Print the text in FORMAT, using ARGV (with ARGC elements) for + arguments to any '%' directives. + Return the number of elements of ARGV used. */ + +static int print_formatted(char *format, int argc, char **argv) +{ + int save_argc = argc; /* Preserve original value. */ + char *f; /* Pointer into 'format'. */ + char *direc_start; /* Start of % directive. */ + size_t direc_length; /* Length of % directive. */ + int field_width; /* Arg to first '*', or -1 if none. */ + int precision; /* Arg to second '*', or -1 if none. */ + + for (f = format; *f; ++f) { + switch (*f) { + case '%': + direc_start = f++; + direc_length = 1; + field_width = precision = -1; + if (*f == '%') { + putchar('%'); + break; + } + if (*f == 'b') { + if (argc > 0) { + print_esc_string(*argv); + ++argv; + --argc; + } + break; + } + if (strchr("-+ #", *f)) { + ++f; + ++direc_length; + } + if (*f == '*') { + ++f; + ++direc_length; + if (argc > 0) { + field_width = my_xstrtoul(*argv); + ++argv; + --argc; + } else + field_width = 0; + } else + while (isdigit(*f)) { + ++f; + ++direc_length; + } + if (*f == '.') { + ++f; + ++direc_length; + if (*f == '*') { + ++f; + ++direc_length; + if (argc > 0) { + precision = my_xstrtoul(*argv); + ++argv; + --argc; + } else + precision = 0; + } else + while (isdigit(*f)) { + ++f; + ++direc_length; + } + } + if (*f == 'l' || *f == 'L' || *f == 'h') { + ++f; + ++direc_length; + } + /* + if (!strchr ("diouxXfeEgGcs", *f)) + fprintf(stderr, "%%%c: invalid directive", *f); + */ + ++direc_length; + if (argc > 0) { + print_direc(direc_start, direc_length, field_width, + precision, *argv); + ++argv; + --argc; + } else + print_direc(direc_start, direc_length, field_width, + precision, ""); + break; + + case '\\': + if (*++f == 'c') + exit(0); + putchar(bb_process_escape_sequence((const char **)&f)); + f--; + break; + + default: + putchar(*f); + } + } + + return save_argc - argc; +} + +static void +print_direc(char *start, size_t length, int field_width, int precision, + char *argument) +{ + char *p; /* Null-terminated copy of % directive. */ + + p = xmalloc((unsigned) (length + 1)); + strncpy(p, start, length); + p[length] = 0; + + switch (p[length - 1]) { + case 'd': + case 'i': + if (field_width < 0) { + if (precision < 0) + printf(p, my_xstrtol(argument)); + else + printf(p, precision, my_xstrtol(argument)); + } else { + if (precision < 0) + printf(p, field_width, my_xstrtol(argument)); + else + printf(p, field_width, precision, my_xstrtol(argument)); + } + break; + + case 'o': + case 'u': + case 'x': + case 'X': + if (field_width < 0) { + if (precision < 0) + printf(p, my_xstrtoul(argument)); + else + printf(p, precision, my_xstrtoul(argument)); + } else { + if (precision < 0) + printf(p, field_width, my_xstrtoul(argument)); + else + printf(p, field_width, precision, my_xstrtoul(argument)); + } + break; + + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + if (field_width < 0) { + if (precision < 0) + printf(p, my_xstrtod(argument)); + else + printf(p, precision, my_xstrtod(argument)); + } else { + if (precision < 0) + printf(p, field_width, my_xstrtod(argument)); + else + printf(p, field_width, precision, my_xstrtod(argument)); + } + break; + + case 'c': + printf(p, *argument); + break; + + case 's': + if (field_width < 0) { + if (precision < 0) + printf(p, argument); + else + printf(p, precision, argument); + } else { + if (precision < 0) + printf(p, field_width, argument); + else + printf(p, field_width, precision, argument); + } + break; + } + + free(p); +} diff --git a/coreutils/pwd.c b/coreutils/pwd.c new file mode 100644 index 000000000..bd36d627e --- /dev/null +++ b/coreutils/pwd.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini pwd implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "busybox.h" + +int pwd_main(int argc, char **argv) +{ + char *buf; + + if ((buf = xgetcwd(NULL)) != NULL) { + puts(buf); + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + return EXIT_FAILURE; +} diff --git a/coreutils/realpath.c b/coreutils/realpath.c new file mode 100644 index 000000000..5f3242fb5 --- /dev/null +++ b/coreutils/realpath.c @@ -0,0 +1,48 @@ +/* vi: set sw=4 ts=4: */ + +/* BB_AUDIT SUSv3 N/A -- Apparently a busybox extension. */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Now does proper error checking on output and returns a failure exit code + * if one or more paths cannot be resolved. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "busybox.h" + +int realpath_main(int argc, char **argv) +{ + int retval = EXIT_SUCCESS; + +#if PATH_MAX > (BUFSIZ+1) + RESERVE_CONFIG_BUFFER(resolved_path, PATH_MAX); +# define resolved_path_MUST_FREE 1 +#else +#define resolved_path bb_common_bufsiz1 +# define resolved_path_MUST_FREE 0 +#endif + + if (--argc == 0) { + bb_show_usage(); + } + + do { + argv++; + if (realpath(*argv, resolved_path) != NULL) { + puts(resolved_path); + } else { + retval = EXIT_FAILURE; + bb_perror_msg("%s", *argv); + } + } while (--argc); + +#if ENABLE_FEATURE_CLEAN_UP && resolved_path_MUST_FREE + RELEASE_CONFIG_BUFFER(resolved_path); +#endif + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/rm.c b/coreutils/rm.c new file mode 100644 index 000000000..5df7d5f65 --- /dev/null +++ b/coreutils/rm.c @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini rm implementation for busybox + * + * Copyright (C) 2001 Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/rm.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Size reduction. + */ + +#include +#include "busybox.h" + +int rm_main(int argc, char **argv) +{ + int status = 0; + int flags = 0; + unsigned opt; + + opt_complementary = "f-i:i-f"; + opt = getopt32(argc, argv, "fiRr"); + if(opt & 1) + flags |= FILEUTILS_FORCE; + if(opt & 2) + flags |= FILEUTILS_INTERACTIVE; + if(opt & 12) + flags |= FILEUTILS_RECUR; + + if (*(argv += optind) != NULL) { + do { + const char *base = bb_get_last_path_component(*argv); + + if ((base[0] == '.') && (!base[1] || ((base[1] == '.') && !base[2]))) { + bb_error_msg("cannot remove '.' or '..'"); + } else if (remove_file(*argv, flags) >= 0) { + continue; + } + status = 1; + } while (*++argv); + } else if (!(flags & FILEUTILS_FORCE)) { + bb_show_usage(); + } + + return status; +} diff --git a/coreutils/rmdir.c b/coreutils/rmdir.c new file mode 100644 index 000000000..c61bae795 --- /dev/null +++ b/coreutils/rmdir.c @@ -0,0 +1,60 @@ +/* vi: set sw=4 ts=4: */ +/* + * rmdir implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/rmdir.html */ + +#include +#include +#include +#include "busybox.h" + +int rmdir_main(int argc, char **argv) +{ + int status = EXIT_SUCCESS; + int flags; + int do_dot; + char *path; + + flags = getopt32(argc, argv, "p"); + + argv += optind; + + if (!*argv) { + bb_show_usage(); + } + + do { + path = *argv; + + /* Record if the first char was a '.' so we can use dirname later. */ + do_dot = (*path == '.'); + + do { + if (rmdir(path) < 0) { + bb_perror_msg("'%s'", path); /* Match gnu rmdir msg. */ + status = EXIT_FAILURE; + } else if (flags) { + /* Note: path was not empty or null since rmdir succeeded. */ + path = dirname(path); + /* Path is now just the parent component. Note that dirname + * returns "." if there are no parents. We must distinguish + * this from the case of the original path starting with '.'. + */ + if (do_dot || (*path != '.') || path[1]) { + continue; + } + } + break; + } while (1); + + } while (*++argv); + + return status; +} diff --git a/coreutils/seq.c b/coreutils/seq.c new file mode 100644 index 000000000..f2b4706f2 --- /dev/null +++ b/coreutils/seq.c @@ -0,0 +1,40 @@ +/* vi: set sw=4 ts=4: */ +/* + * seq implementation for busybox + * + * Copyright (C) 2004, Glenn McGrath + * + * Licensed under the GPL v2, see the file LICENSE in this tarball. + */ + +#include +#include +#include "busybox.h" + +int seq_main(int argc, char **argv) +{ + double last, first, increment, i; + + first = increment = 1; + switch (argc) { + case 4: + increment = atof(argv[2]); + case 3: + first = atof(argv[1]); + case 2: + last = atof(argv[argc-1]); + break; + default: + bb_show_usage(); + } + + /* You should note that this is pos-5.0.91 semantics, -- FK. */ + for (i = first; + (increment > 0 && i <= last) || (increment < 0 && i >=last); + i += increment) + { + printf("%g\n", i); + } + + return EXIT_SUCCESS; +} diff --git a/coreutils/sleep.c b/coreutils/sleep.c new file mode 100644 index 000000000..e32e2157d --- /dev/null +++ b/coreutils/sleep.c @@ -0,0 +1,67 @@ +/* vi: set sw=4 ts=4: */ +/* + * sleep implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* BB_AUDIT GNU issues -- fancy version matches except args must be ints. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/sleep.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Rewritten to do proper arg and error checking. + * Also, added a 'fancy' configuration to accept multiple args with + * time suffixes for seconds, minutes, hours, and days. + */ + +#include +#include +#include +#include "busybox.h" + +#ifdef CONFIG_FEATURE_FANCY_SLEEP +static const struct suffix_mult sfx[] = { + { "s", 1 }, + { "m", 60 }, + { "h", 60*60 }, + { "d", 24*60*60 }, + { NULL, 0 } +}; +#endif + +int sleep_main(int argc, char **argv) +{ + unsigned int duration; + +#ifdef CONFIG_FEATURE_FANCY_SLEEP + + if (argc < 2) { + bb_show_usage(); + } + + ++argv; + duration = 0; + do { + duration += xatoul_range_sfx(*argv, 0, UINT_MAX-duration, sfx); + } while (*++argv); + +#else /* CONFIG_FEATURE_FANCY_SLEEP */ + + if (argc != 2) { + bb_show_usage(); + } + + duration = xatou(argv[1]); + +#endif /* CONFIG_FEATURE_FANCY_SLEEP */ + + if (sleep(duration)) { + bb_perror_nomsg_and_die(); + } + + return EXIT_SUCCESS; +} diff --git a/coreutils/sort.c b/coreutils/sort.c new file mode 100644 index 000000000..c23bf9c60 --- /dev/null +++ b/coreutils/sort.c @@ -0,0 +1,357 @@ +/* vi: set sw=4 ts=4: */ +/* + * SuS3 compliant sort implementation for busybox + * + * Copyright (C) 2004 by Rob Landley + * + * MAINTAINER: Rob Landley + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * See SuS3 sort standard at: + * http://www.opengroup.org/onlinepubs/007904975/utilities/sort.html + */ + +#include "busybox.h" + +static int global_flags; + +/* + sort [-m][-o output][-bdfinru][-t char][-k keydef]... [file...] + sort -c [-bdfinru][-t char][-k keydef][file] +*/ + +/* These are sort types */ +#define FLAG_n 1 /* Numeric sort */ +#define FLAG_g 2 /* Sort using strtod() */ +#define FLAG_M 4 /* Sort date */ +/* ucsz apply to root level only, not keys. b at root level implies bb */ +#define FLAG_u 8 /* Unique */ +#define FLAG_c 16 /* Check: no output, exit(!ordered) */ +#define FLAG_s 32 /* Stable sort, no ascii fallback at end */ +#define FLAG_z 64 /* Input is null terminated, not \n */ +/* These can be applied to search keys, the previous four can't */ +#define FLAG_b 128 /* Ignore leading blanks */ +#define FLAG_r 256 /* Reverse */ +#define FLAG_d 512 /* Ignore !(isalnum()|isspace()) */ +#define FLAG_f 1024 /* Force uppercase */ +#define FLAG_i 2048 /* Ignore !isprint() */ +#define FLAG_bb 32768 /* Ignore trailing blanks */ + + +#if ENABLE_FEATURE_SORT_BIG +static char key_separator; + +static struct sort_key { + struct sort_key *next_key; /* linked list */ + unsigned short range[4]; /* start word, start char, end word, end char */ + int flags; +} *key_list; + +static char *get_key(char *str, struct sort_key *key, int flags) +{ + int start = 0, end = 0, len, i, j; + + /* Special case whole string, so we don't have to make a copy */ + if (key->range[0] == 1 && !key->range[1] && !key->range[2] && !key->range[3] + && !(flags & (FLAG_b | FLAG_d | FLAG_f | FLAG_i | FLAG_bb)) + ) { + return str; + } + + /* Find start of key on first pass, end on second pass*/ + len = strlen(str); + for (j = 0; j < 2; j++) { + if (!key->range[2*j]) + end = len; + /* Loop through fields */ + else { + end = 0; + for (i = 1; i < key->range[2*j] + j; i++) { + /* Skip leading blanks or first separator */ + if (str[end]) { + if (!key_separator) + while (isspace(str[end])) end++; + } + /* Skip body of key */ + for (; str[end]; end++) { + if (key_separator) { + if (str[end] == key_separator) + break; + } else { + if (isspace(str[end])) + break; + } + } + } + } + if (!j) start = end; + } + /* Key with explicit separator starts after separator */ + if (key_separator && str[start] == key_separator) + start++; + /* Strip leading whitespace if necessary */ +//XXX: skip_whitespace() + if (flags & FLAG_b) + while (isspace(str[start])) start++; + /* Strip trailing whitespace if necessary */ + if (flags & FLAG_bb) + while (end > start && isspace(str[end-1])) end--; + /* Handle offsets on start and end */ + if (key->range[3]) { + end += key->range[3] - 1; + if (end > len) end = len; + } + if (key->range[1]) { + start += key->range[1] - 1; + if (start > len) start = len; + } + /* Make the copy */ + if (end < start) end = start; + str = xstrndup(str+start, end-start); + /* Handle -d */ + if (flags & FLAG_d) { + for (start = end = 0; str[end]; end++) + if (isspace(str[end]) || isalnum(str[end])) + str[start++] = str[end]; + str[start] = '\0'; + } + /* Handle -i */ + if (flags & FLAG_i) { + for (start = end = 0; str[end]; end++) + if (isprint(str[end])) + str[start++] = str[end]; + str[start] = '\0'; + } + /* Handle -f */ + if (flags & FLAG_f) + for (i = 0; str[i]; i++) + str[i] = toupper(str[i]); + + return str; +} + +static struct sort_key *add_key(void) +{ + struct sort_key **pkey = &key_list; + while (*pkey) + pkey = &((*pkey)->next_key); + return *pkey = xzalloc(sizeof(struct sort_key)); +} + +#define GET_LINE(fp) \ + ((global_flags & FLAG_z) \ + ? bb_get_chunk_from_file(fp, NULL) \ + : xmalloc_getline(fp)) +#else +#define GET_LINE(fp) xmalloc_getline(fp) +#endif + +/* Iterate through keys list and perform comparisons */ +static int compare_keys(const void *xarg, const void *yarg) +{ + int flags = global_flags, retval = 0; + char *x, *y; + +#if ENABLE_FEATURE_SORT_BIG + struct sort_key *key; + + for (key = key_list; !retval && key; key = key->next_key) { + flags = key->flags ? key->flags : global_flags; + /* Chop out and modify key chunks, handling -dfib */ + x = get_key(*(char **)xarg, key, flags); + y = get_key(*(char **)yarg, key, flags); +#else + /* This curly bracket serves no purpose but to match the nesting + level of the for() loop we're not using */ + { + x = *(char **)xarg; + y = *(char **)yarg; +#endif + /* Perform actual comparison */ + switch (flags & 7) { + default: + bb_error_msg_and_die("unknown sort type"); + break; + /* Ascii sort */ + case 0: + retval = strcmp(x, y); + break; +#if ENABLE_FEATURE_SORT_BIG + case FLAG_g: { + char *xx, *yy; + double dx = strtod(x, &xx); + double dy = strtod(y, &yy); + /* not numbers < NaN < -infinity < numbers < +infinity) */ + if (x == xx) + retval = (y == yy ? 0 : -1); + else if (y == yy) + retval = 1; + /* Check for isnan */ + else if (dx != dx) + retval = (dy != dy) ? 0 : -1; + else if (dy != dy) + retval = 1; + /* Check for infinity. Could underflow, but it avoids libm. */ + else if (1.0 / dx == 0.0) { + if (dx < 0) + retval = (1.0 / dy == 0.0 && dy < 0) ? 0 : -1; + else + retval = (1.0 / dy == 0.0 && dy > 0) ? 0 : 1; + } else if (1.0 / dy == 0.0) + retval = (dy < 0) ? 1 : -1; + else + retval = (dx > dy) ? 1 : ((dx < dy) ? -1 : 0); + break; + } + case FLAG_M: { + struct tm thyme; + int dx; + char *xx, *yy; + + xx = strptime(x, "%b", &thyme); + dx = thyme.tm_mon; + yy = strptime(y, "%b", &thyme); + if (!xx) + retval = (!yy) ? 0 : -1; + else if (!yy) + retval = 1; + else + retval = (dx == thyme.tm_mon) ? 0 : dx - thyme.tm_mon; + break; + } + /* Full floating point version of -n */ + case FLAG_n: { + double dx = atof(x); + double dy = atof(y); + retval = (dx > dy) ? 1 : ((dx < dy) ? -1 : 0); + break; + } + } /* switch */ + /* Free key copies. */ + if (x != *(char **)xarg) free(x); + if (y != *(char **)yarg) free(y); + /* if (retval) break; - done by for() anyway */ +#else + /* Integer version of -n for tiny systems */ + case FLAG_n: + retval = atoi(x) - atoi(y); + break; + } /* switch */ +#endif + } + /* Perform fallback sort if necessary */ + if (!retval && !(global_flags & FLAG_s)) + retval = strcmp(*(char **)xarg, *(char **)yarg); + + if (flags & FLAG_r) return -retval; + return retval; +} + +int sort_main(int argc, char **argv) +{ + FILE *fp, *outfile = NULL; + int linecount = 0, i, flag; + char *line, **lines = NULL, *optlist = "ngMucszbrdfimS:T:o:k:t:"; + int c; + + xfunc_error_retval = 2; + /* Parse command line options */ + while ((c = getopt(argc, argv, optlist)) > 0) { + line = strchr(optlist, c); + if (!line) bb_show_usage(); + switch (*line) { +#if ENABLE_FEATURE_SORT_BIG + case 'o': + if (outfile) bb_error_msg_and_die("too many -o"); + outfile = xfopen(optarg, "w"); + break; + case 't': + if (key_separator || optarg[1]) + bb_error_msg_and_die("too many -t"); + key_separator = *optarg; + break; + /* parse sort key */ + case 'k': { + struct sort_key *key = add_key(); + char *temp, *temp2; + + temp = optarg; + for (i = 0; *temp;) { + /* Start of range */ + key->range[2*i] = (unsigned short)strtol(temp, &temp, 10); + if (*temp == '.') + key->range[(2*i)+1] = (unsigned short)strtol(temp+1, &temp, 10); + for (; *temp; temp++) { + if (*temp == ',' && !i++) { + temp++; + break; + } /* no else needed: fall through to syntax error + because comma isn't in optlist */ + temp2 = strchr(optlist, *temp); + flag = (1 << (temp2 - optlist)); + if (!temp2 || (flag > FLAG_M && flag < FLAG_b)) + bb_error_msg_and_die("unknown key option"); + /* b after ',' means strip _trailing_ space */ + if (i && flag == FLAG_b) flag = FLAG_bb; + key->flags |= flag; + } + } + break; + } +#endif + default: + global_flags |= (1 << (line - optlist)); + /* global b strips leading and trailing spaces */ + if (global_flags & FLAG_b) global_flags |= FLAG_bb; + break; + } + } + /* Open input files and read data */ + for (i = argv[optind] ? optind : optind-1; argv[i]; i++) { + fp = stdin; + if (i >= optind && (argv[i][0] != '-' || argv[i][1])) + fp = xfopen(argv[i], "r"); + for (;;) { + line = GET_LINE(fp); + if (!line) break; + if (!(linecount & 63)) + lines = xrealloc(lines, sizeof(char *) * (linecount + 64)); + lines[linecount++] = line; + } + fclose(fp); + } +#if ENABLE_FEATURE_SORT_BIG + /* if no key, perform alphabetic sort */ + if (!key_list) + add_key()->range[0] = 1; + /* handle -c */ + if (global_flags & FLAG_c) { + int j = (global_flags & FLAG_u) ? -1 : 0; + for (i = 1; i < linecount; i++) + if (compare_keys(&lines[i-1], &lines[i]) > j) { + fprintf(stderr, "Check line %d\n", i); + return 1; + } + return 0; + } +#endif + /* Perform the actual sort */ + qsort(lines, linecount, sizeof(char *), compare_keys); + /* handle -u */ + if (global_flags & FLAG_u) { + for (flag = 0, i = 1; i < linecount; i++) { + if (!compare_keys(&lines[flag], &lines[i])) + free(lines[i]); + else + lines[++flag] = lines[i]; + } + if (linecount) linecount = flag+1; + } + /* Print it */ + if (!outfile) outfile = stdout; + for (i = 0; i < linecount; i++) + fprintf(outfile, "%s\n", lines[i]); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/stat.c b/coreutils/stat.c new file mode 100644 index 000000000..31dd6624e --- /dev/null +++ b/coreutils/stat.c @@ -0,0 +1,538 @@ +/* vi: set sw=4 ts=4: */ +/* + * stat -- display file or file system status + * + * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation. + * Copyright (C) 2005 by Erik Andersen + * Copyright (C) 2005 by Mike Frysinger + * + * Written by Michael Meskes + * Taken from coreutils and turned into a busybox applet by Mike Frysinger + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* vars to control behavior */ +#define OPT_TERSE 2 +#define OPT_DEREFERENCE 4 +static long flags; + +static char const *file_type(struct stat const *st) +{ + /* See POSIX 1003.1-2001 XCU Table 4-8 lines 17093-17107 + * for some of these formats. + * To keep diagnostics grammatical in English, the + * returned string must start with a consonant. + */ + if (S_ISREG(st->st_mode)) return st->st_size == 0 ? "regular empty file" : "regular file"; + if (S_ISDIR(st->st_mode)) return "directory"; + if (S_ISBLK(st->st_mode)) return "block special file"; + if (S_ISCHR(st->st_mode)) return "character special file"; + if (S_ISFIFO(st->st_mode)) return "fifo"; + if (S_ISLNK(st->st_mode)) return "symbolic link"; + if (S_ISSOCK(st->st_mode)) return "socket"; + if (S_TYPEISMQ(st)) return "message queue"; + if (S_TYPEISSEM(st)) return "semaphore"; + if (S_TYPEISSHM(st)) return "shared memory object"; +#ifdef S_TYPEISTMO + if (S_TYPEISTMO(st)) return "typed memory object"; +#endif + return "weird file"; +} + +static char const *human_time(time_t t) +{ + /* Old + static char *str; + str = ctime(&t); + str[strlen(str)-1] = '\0'; + return str; + */ + /* coreutils 6.3 compat: */ + static char buf[sizeof("YYYY-MM-DD HH:MM:SS.000000000")]; + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S.000000000", localtime(&t)); + return buf; +} + +/* Return the type of the specified file system. + * Some systems have statfvs.f_basetype[FSTYPSZ]. (AIX, HP-UX, and Solaris) + * Others have statfs.f_fstypename[MFSNAMELEN]. (NetBSD 1.5.2) + * Still others have neither and have to get by with f_type (Linux). + */ +static char const *human_fstype(long f_type) +{ + int i; + static const struct types { + long type; + const char *fs; + } humantypes[] = { + { 0xADFF, "affs" }, + { 0x1Cd1, "devpts" }, + { 0x137D, "ext" }, + { 0xEF51, "ext2" }, + { 0xEF53, "ext2/ext3" }, + { 0x3153464a, "jfs" }, + { 0x58465342, "xfs" }, + { 0xF995E849, "hpfs" }, + { 0x9660, "isofs" }, + { 0x4000, "isofs" }, + { 0x4004, "isofs" }, + { 0x137F, "minix" }, + { 0x138F, "minix (30 char.)" }, + { 0x2468, "minix v2" }, + { 0x2478, "minix v2 (30 char.)" }, + { 0x4d44, "msdos" }, + { 0x4006, "fat" }, + { 0x564c, "novell" }, + { 0x6969, "nfs" }, + { 0x9fa0, "proc" }, + { 0x517B, "smb" }, + { 0x012FF7B4, "xenix" }, + { 0x012FF7B5, "sysv4" }, + { 0x012FF7B6, "sysv2" }, + { 0x012FF7B7, "coh" }, + { 0x00011954, "ufs" }, + { 0x012FD16D, "xia" }, + { 0x5346544e, "ntfs" }, + { 0x1021994, "tmpfs" }, + { 0x52654973, "reiserfs" }, + { 0x28cd3d45, "cramfs" }, + { 0x7275, "romfs" }, + { 0x858458f6, "romfs" }, + { 0x73717368, "squashfs" }, + { 0x62656572, "sysfs" }, + { 0, "UNKNOWN" } + }; + for (i=0; humantypes[i].type; ++i) + if (humantypes[i].type == f_type) + break; + return humantypes[i].fs; +} + +#ifdef CONFIG_FEATURE_STAT_FORMAT +/* print statfs info */ +static void print_statfs(char *pformat, size_t buf_len, char m, + char const *filename, void const *data) +{ + struct statfs const *statfsbuf = data; + + switch (m) { + case 'n': + strncat(pformat, "s", buf_len); + printf(pformat, filename); + break; + case 'i': + strncat(pformat, "Lx", buf_len); + printf(pformat, statfsbuf->f_fsid); + break; + case 'l': + strncat(pformat, "lu", buf_len); + printf(pformat, statfsbuf->f_namelen); + break; + case 't': + strncat(pformat, "lx", buf_len); + printf(pformat, (unsigned long int) (statfsbuf->f_type)); /* no equiv. */ + break; + case 'T': + strncat(pformat, "s", buf_len); + printf(pformat, human_fstype(statfsbuf->f_type)); + break; + case 'b': + strncat(pformat, "jd", buf_len); + printf(pformat, (intmax_t) (statfsbuf->f_blocks)); + break; + case 'f': + strncat(pformat, "jd", buf_len); + printf(pformat, (intmax_t) (statfsbuf->f_bfree)); + break; + case 'a': + strncat(pformat, "jd", buf_len); + printf(pformat, (intmax_t) (statfsbuf->f_bavail)); + break; + case 'S': + case 's': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) (statfsbuf->f_bsize)); + break; + case 'c': + strncat(pformat, "jd", buf_len); + printf(pformat, (intmax_t) (statfsbuf->f_files)); + break; + case 'd': + strncat(pformat, "jd", buf_len); + printf(pformat, (intmax_t) (statfsbuf->f_ffree)); + break; + default: + strncat(pformat, "c", buf_len); + printf(pformat, m); + break; + } +} + +/* print stat info */ +static void print_stat(char *pformat, size_t buf_len, char m, + char const *filename, void const *data) +{ +#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) + struct stat *statbuf = (struct stat *) data; + struct passwd *pw_ent; + struct group *gw_ent; + + switch (m) { + case 'n': + strncat(pformat, "s", buf_len); + printf(pformat, filename); + break; + case 'N': + strncat(pformat, "s", buf_len); + if (S_ISLNK(statbuf->st_mode)) { + char *linkname = xreadlink(filename); + if (linkname == NULL) { + bb_perror_msg("cannot read symbolic link '%s'", filename); + return; + } + /*printf("\"%s\" -> \"%s\"", filename, linkname); */ + printf(pformat, filename); + printf(" -> "); + printf(pformat, linkname); + } else { + printf(pformat, filename); + } + break; + case 'd': + strncat(pformat, "ju", buf_len); + printf(pformat, (uintmax_t) statbuf->st_dev); + break; + case 'D': + strncat(pformat, "jx", buf_len); + printf(pformat, (uintmax_t) statbuf->st_dev); + break; + case 'i': + strncat(pformat, "ju", buf_len); + printf(pformat, (uintmax_t) statbuf->st_ino); + break; + case 'a': + strncat(pformat, "lo", buf_len); + printf(pformat, (unsigned long int) (statbuf->st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO))); + break; + case 'A': + strncat(pformat, "s", buf_len); + printf(pformat, bb_mode_string(statbuf->st_mode)); + break; + case 'f': + strncat(pformat, "lx", buf_len); + printf(pformat, (unsigned long int) statbuf->st_mode); + break; + case 'F': + strncat(pformat, "s", buf_len); + printf(pformat, file_type(statbuf)); + break; + case 'h': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_nlink); + break; + case 'u': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_uid); + break; + case 'U': + strncat(pformat, "s", buf_len); + setpwent(); + pw_ent = getpwuid(statbuf->st_uid); + printf(pformat, (pw_ent != 0L) ? pw_ent->pw_name : "UNKNOWN"); + break; + case 'g': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_gid); + break; + case 'G': + strncat(pformat, "s", buf_len); + setgrent(); + gw_ent = getgrgid(statbuf->st_gid); + printf(pformat, (gw_ent != 0L) ? gw_ent->gr_name : "UNKNOWN"); + break; + case 't': + strncat(pformat, "lx", buf_len); + printf(pformat, (unsigned long int) major(statbuf->st_rdev)); + break; + case 'T': + strncat(pformat, "lx", buf_len); + printf(pformat, (unsigned long int) minor(statbuf->st_rdev)); + break; + case 's': + strncat(pformat, "ju", buf_len); + printf(pformat, (uintmax_t) (statbuf->st_size)); + break; + case 'B': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) 512); //ST_NBLOCKSIZE + break; + case 'b': + strncat(pformat, "ju", buf_len); + printf(pformat, (uintmax_t) statbuf->st_blocks); + break; + case 'o': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_blksize); + break; + case 'x': + strncat(pformat, "s", buf_len); + printf(pformat, human_time(statbuf->st_atime)); + break; + case 'X': + strncat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_atime); + break; + case 'y': + strncat(pformat, "s", buf_len); + printf(pformat, human_time(statbuf->st_mtime)); + break; + case 'Y': + strncat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_mtime); + break; + case 'z': + strncat(pformat, "s", buf_len); + printf(pformat, human_time(statbuf->st_ctime)); + break; + case 'Z': + strncat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_ctime); + break; + default: + strncat(pformat, "c", buf_len); + printf(pformat, m); + break; + } +} + +static void print_it(char const *masterformat, char const *filename, + void (*print_func) (char *, size_t, char, char const *, void const *), + void const *data) +{ + char *b; + + /* create a working copy of the format string */ + char *format = xstrdup(masterformat); + + /* Add 2 to accomodate our conversion of the stat '%s' format string + * to the printf '%llu' one. */ + size_t n_alloc = strlen(format) + 2 + 1; + char *dest = xmalloc(n_alloc); + + b = format; + while (b) { + size_t len; + char *p = strchr(b, '%'); + if (!p) { + /* coreutils 6.3 always print at the end */ + /*fputs(b, stdout);*/ + puts(b); + break; + } + *p++ = '\0'; + fputs(b, stdout); + + len = strspn(p, "#-+.I 0123456789"); + dest[0] = '%'; + memcpy(dest + 1, p, len); + dest[1 + len] = 0; + p += len; + + b = p + 1; + switch (*p) { + case '\0': + b = NULL; + /* fall through */ + case '%': + putchar('%'); + break; + default: + print_func(dest, n_alloc, *p, filename, data); + break; + } + } + + free(format); + free(dest); +} +#endif + +/* Stat the file system and print what we find. */ +static int do_statfs(char const *filename, char const *format) +{ + struct statfs statfsbuf; + + if (statfs(filename, &statfsbuf) != 0) { + bb_perror_msg("cannot read file system information for '%s'", filename); + return 0; + } + +#ifdef CONFIG_FEATURE_STAT_FORMAT + if (format == NULL) + format = (flags & OPT_TERSE + ? "%n %i %l %t %s %b %f %a %c %d\n" + : " File: \"%n\"\n" + " ID: %-8i Namelen: %-7l Type: %T\n" + "Block size: %-10s\n" + "Blocks: Total: %-10b Free: %-10f Available: %a\n" + "Inodes: Total: %-10c Free: %d"); + print_it(format, filename, print_statfs, &statfsbuf); +#else + + format = (flags & OPT_TERSE + ? "%s %llx %lu " + : " File: \"%s\"\n" + " ID: %-8Lx Namelen: %-7lu "); + printf(format, + filename, + statfsbuf.f_fsid, + statfsbuf.f_namelen); + + if (flags & OPT_TERSE) + printf("%lx ", (unsigned long int) (statfsbuf.f_type)); + else + printf("Type: %s\n", human_fstype(statfsbuf.f_type)); + + format = (flags & OPT_TERSE + ? "%lu %ld %ld %ld %ld %ld\n" + : "Block size: %-10lu\n" + "Blocks: Total: %-10jd Free: %-10jd Available: %jd\n" + "Inodes: Total: %-10jd Free: %jd\n"); + printf(format, + (unsigned long int) (statfsbuf.f_bsize), + (intmax_t) (statfsbuf.f_blocks), + (intmax_t) (statfsbuf.f_bfree), + (intmax_t) (statfsbuf.f_bavail), + (intmax_t) (statfsbuf.f_files), + (intmax_t) (statfsbuf.f_ffree)); +#endif + + return 1; +} + +/* stat the file and print what we find */ +static int do_stat(char const *filename, char const *format) +{ + struct stat statbuf; + + if ((flags & OPT_DEREFERENCE ? stat : lstat) (filename, &statbuf) != 0) { + bb_perror_msg("cannot stat '%s'", filename); + return 0; + } + +#ifdef CONFIG_FEATURE_STAT_FORMAT + if (format == NULL) { + if (flags & OPT_TERSE) { + format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o"; + } else { + if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode)) { + format = + " File: \"%N\"\n" + " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" + "Device: %Dh/%dd\tInode: %-10i Links: %-5h" + " Device type: %t,%T\n" + "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" + "Access: %x\n" "Modify: %y\n" "Change: %z\n"; + } else { + format = + " File: \"%N\"\n" + " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" + "Device: %Dh/%dd\tInode: %-10i Links: %h\n" + "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" + "Access: %x\n" "Modify: %y\n" "Change: %z\n"; + } + } + } + print_it(format, filename, print_stat, &statbuf); +#else + if (flags & OPT_TERSE) { + printf("%s %ju %ju %lx %lu %lu %jx %ju %lu %lx %lx %lu %lu %lu %lu\n", + filename, + (uintmax_t) (statbuf.st_size), + (uintmax_t) statbuf.st_blocks, + (unsigned long int) statbuf.st_mode, + (unsigned long int) statbuf.st_uid, + (unsigned long int) statbuf.st_gid, + (uintmax_t) statbuf.st_dev, + (uintmax_t) statbuf.st_ino, + (unsigned long int) statbuf.st_nlink, + (unsigned long int) major(statbuf.st_rdev), + (unsigned long int) minor(statbuf.st_rdev), + (unsigned long int) statbuf.st_atime, + (unsigned long int) statbuf.st_mtime, + (unsigned long int) statbuf.st_ctime, + (unsigned long int) statbuf.st_blksize + ); + } else { + char *linkname = NULL; + + struct passwd *pw_ent; + struct group *gw_ent; + setgrent(); + gw_ent = getgrgid(statbuf.st_gid); + setpwent(); + pw_ent = getpwuid(statbuf.st_uid); + + if (S_ISLNK(statbuf.st_mode)) + linkname = xreadlink(filename); + if (linkname) + printf(" File: \"%s\" -> \"%s\"\n", filename, linkname); + else + printf(" File: \"%s\"\n", filename); + + printf(" Size: %-10ju\tBlocks: %-10ju IO Block: %-6lu %s\n" + "Device: %jxh/%jud\tInode: %-10ju Links: %-5lu", + (uintmax_t) (statbuf.st_size), + (uintmax_t) statbuf.st_blocks, + (unsigned long int) statbuf.st_blksize, + file_type(&statbuf), + (uintmax_t) statbuf.st_dev, + (uintmax_t) statbuf.st_dev, + (uintmax_t) statbuf.st_ino, + (unsigned long int) statbuf.st_nlink); + if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode)) + printf(" Device type: %lx,%lx\n", + (unsigned long int) major(statbuf.st_rdev), + (unsigned long int) minor(statbuf.st_rdev)); + else + putchar('\n'); + printf("Access: (%04lo/%10.10s) Uid: (%5lu/%8s) Gid: (%5lu/%8s)\n" + "Access: %s\n" "Modify: %s\n" "Change: %s\n", + (unsigned long int) (statbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)), + bb_mode_string(statbuf.st_mode), + (unsigned long int) statbuf.st_uid, + (pw_ent != 0L) ? pw_ent->pw_name : "UNKNOWN", + (unsigned long int) statbuf.st_gid, + (gw_ent != 0L) ? gw_ent->gr_name : "UNKNOWN", + human_time(statbuf.st_atime), + human_time(statbuf.st_mtime), + human_time(statbuf.st_ctime)); + } +#endif + return 1; +} + +int stat_main(int argc, char **argv) +{ + int i; + char *format = NULL; + int ok = 1; + int (*statfunc)(char const *, char const *) = do_stat; + + flags = getopt32(argc, argv, "ftL" + USE_FEATURE_STAT_FORMAT("c:", &format) + ); + + if (flags & 1) /* -f */ + statfunc = do_statfs; + if (argc == optind) /* files */ + bb_show_usage(); + + for (i = optind; i < argc; ++i) + ok &= statfunc(argv[i], format); + + return (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/coreutils/stty.c b/coreutils/stty.c new file mode 100644 index 000000000..22784a260 --- /dev/null +++ b/coreutils/stty.c @@ -0,0 +1,1302 @@ +/* vi: set sw=4 ts=4: */ +/* stty -- change and print terminal line settings + Copyright (C) 1990-1999 Free Software Foundation, Inc. + + Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +*/ +/* Usage: stty [-ag] [-F device] [setting...] + + Options: + -a Write all current settings to stdout in human-readable form. + -g Write all current settings to stdout in stty-readable form. + -F Open and use the specified device instead of stdin + + If no args are given, write to stdout the baud rate and settings that + have been changed from their defaults. Mode reading and changes + are done on the specified device, or stdin if none was specified. + + David MacKenzie + + Special for busybox ported by Vladimir Oleynik 2001 + + */ + +#include "busybox.h" + +#ifndef _POSIX_VDISABLE +# define _POSIX_VDISABLE ((unsigned char) 0) +#endif + +#define Control(c) ((c) & 0x1f) +/* Canonical values for control characters */ +#ifndef CINTR +# define CINTR Control('c') +#endif +#ifndef CQUIT +# define CQUIT 28 +#endif +#ifndef CERASE +# define CERASE 127 +#endif +#ifndef CKILL +# define CKILL Control('u') +#endif +#ifndef CEOF +# define CEOF Control('d') +#endif +#ifndef CEOL +# define CEOL _POSIX_VDISABLE +#endif +#ifndef CSTART +# define CSTART Control('q') +#endif +#ifndef CSTOP +# define CSTOP Control('s') +#endif +#ifndef CSUSP +# define CSUSP Control('z') +#endif +#if defined(VEOL2) && !defined(CEOL2) +# define CEOL2 _POSIX_VDISABLE +#endif +/* ISC renamed swtch to susp for termios, but we'll accept either name */ +#if defined(VSUSP) && !defined(VSWTCH) +# define VSWTCH VSUSP +# define CSWTCH CSUSP +#endif +#if defined(VSWTCH) && !defined(CSWTCH) +# define CSWTCH _POSIX_VDISABLE +#endif + +/* SunOS 5.3 loses (^Z doesn't work) if 'swtch' is the same as 'susp'. + So the default is to disable 'swtch.' */ +#if defined (__sparc__) && defined (__svr4__) +# undef CSWTCH +# define CSWTCH _POSIX_VDISABLE +#endif + +#if defined(VWERSE) && !defined (VWERASE) /* AIX-3.2.5 */ +# define VWERASE VWERSE +#endif +#if defined(VDSUSP) && !defined (CDSUSP) +# define CDSUSP Control('y') +#endif +#if !defined(VREPRINT) && defined(VRPRNT) /* Irix 4.0.5 */ +# define VREPRINT VRPRNT +#endif +#if defined(VREPRINT) && !defined(CRPRNT) +# define CRPRNT Control('r') +#endif +#if defined(VWERASE) && !defined(CWERASE) +# define CWERASE Control('w') +#endif +#if defined(VLNEXT) && !defined(CLNEXT) +# define CLNEXT Control('v') +#endif +#if defined(VDISCARD) && !defined(VFLUSHO) +# define VFLUSHO VDISCARD +#endif +#if defined(VFLUSH) && !defined(VFLUSHO) /* Ultrix 4.2 */ +# define VFLUSHO VFLUSH +#endif +#if defined(CTLECH) && !defined(ECHOCTL) /* Ultrix 4.3 */ +# define ECHOCTL CTLECH +#endif +#if defined(TCTLECH) && !defined(ECHOCTL) /* Ultrix 4.2 */ +# define ECHOCTL TCTLECH +#endif +#if defined(CRTKIL) && !defined(ECHOKE) /* Ultrix 4.2 and 4.3 */ +# define ECHOKE CRTKIL +#endif +#if defined(VFLUSHO) && !defined(CFLUSHO) +# define CFLUSHO Control('o') +#endif +#if defined(VSTATUS) && !defined(CSTATUS) +# define CSTATUS Control('t') +#endif + +/* Which speeds to set */ +enum speed_setting { + input_speed, output_speed, both_speeds +}; + +/* Which member(s) of 'struct termios' a mode uses */ +enum { + /* Do NOT change the order or values, as mode_type_flag() + * depends on them */ + control, input, output, local, combination +}; + +static const char evenp [] = "evenp"; +static const char raw [] = "raw"; +static const char stty_min [] = "min"; +static const char stty_time [] = "time"; +static const char stty_swtch[] = "swtch"; +static const char stty_eol [] = "eol"; +static const char stty_eof [] = "eof"; +static const char parity [] = "parity"; +static const char stty_oddp [] = "oddp"; +static const char stty_nl [] = "nl"; +static const char stty_ek [] = "ek"; +static const char stty_sane [] = "sane"; +static const char cbreak [] = "cbreak"; +static const char stty_pass8[] = "pass8"; +static const char litout [] = "litout"; +static const char cooked [] = "cooked"; +static const char decctlq [] = "decctlq"; +static const char stty_tabs [] = "tabs"; +static const char stty_lcase[] = "lcase"; +static const char stty_LCASE[] = "LCASE"; +static const char stty_crt [] = "crt"; +static const char stty_dec [] = "dec"; + +/* Flags for 'struct mode_info' */ +#define SANE_SET 1 /* Set in 'sane' mode */ +#define SANE_UNSET 2 /* Unset in 'sane' mode */ +#define REV 4 /* Can be turned off by prepending '-' */ +#define OMIT 8 /* Don't display value */ + +/* Each mode */ +struct mode_info { + const char *name; /* Name given on command line */ + char type; /* Which structure element to change */ + char flags; /* Setting and display options */ + unsigned short mask; /* Other bits to turn off for this mode */ + unsigned long bits; /* Bits to set for this mode */ +}; + +#define MI_ENTRY(N,T,F,B,M) { N, T, F, M, B } + +static const struct mode_info mode_info[] = { + MI_ENTRY("parenb", control, REV, PARENB, 0 ), + MI_ENTRY("parodd", control, REV, PARODD, 0 ), + MI_ENTRY("cs5", control, 0, CS5, CSIZE), + MI_ENTRY("cs6", control, 0, CS6, CSIZE), + MI_ENTRY("cs7", control, 0, CS7, CSIZE), + MI_ENTRY("cs8", control, 0, CS8, CSIZE), + MI_ENTRY("hupcl", control, REV, HUPCL, 0 ), + MI_ENTRY("hup", control, REV | OMIT, HUPCL, 0 ), + MI_ENTRY("cstopb", control, REV, CSTOPB, 0 ), + MI_ENTRY("cread", control, SANE_SET | REV, CREAD, 0 ), + MI_ENTRY("clocal", control, REV, CLOCAL, 0 ), +#ifdef CRTSCTS + MI_ENTRY("crtscts", control, REV, CRTSCTS, 0 ), +#endif + MI_ENTRY("ignbrk", input, SANE_UNSET | REV, IGNBRK, 0 ), + MI_ENTRY("brkint", input, SANE_SET | REV, BRKINT, 0 ), + MI_ENTRY("ignpar", input, REV, IGNPAR, 0 ), + MI_ENTRY("parmrk", input, REV, PARMRK, 0 ), + MI_ENTRY("inpck", input, REV, INPCK, 0 ), + MI_ENTRY("istrip", input, REV, ISTRIP, 0 ), + MI_ENTRY("inlcr", input, SANE_UNSET | REV, INLCR, 0 ), + MI_ENTRY("igncr", input, SANE_UNSET | REV, IGNCR, 0 ), + MI_ENTRY("icrnl", input, SANE_SET | REV, ICRNL, 0 ), + MI_ENTRY("ixon", input, REV, IXON, 0 ), + MI_ENTRY("ixoff", input, SANE_UNSET | REV, IXOFF, 0 ), + MI_ENTRY("tandem", input, REV | OMIT, IXOFF, 0 ), +#ifdef IUCLC + MI_ENTRY("iuclc", input, SANE_UNSET | REV, IUCLC, 0 ), +#endif +#ifdef IXANY + MI_ENTRY("ixany", input, SANE_UNSET | REV, IXANY, 0 ), +#endif +#ifdef IMAXBEL + MI_ENTRY("imaxbel", input, SANE_SET | REV, IMAXBEL, 0 ), +#endif + MI_ENTRY("opost", output, SANE_SET | REV, OPOST, 0 ), +#ifdef OLCUC + MI_ENTRY("olcuc", output, SANE_UNSET | REV, OLCUC, 0 ), +#endif +#ifdef OCRNL + MI_ENTRY("ocrnl", output, SANE_UNSET | REV, OCRNL, 0 ), +#endif +#ifdef ONLCR + MI_ENTRY("onlcr", output, SANE_SET | REV, ONLCR, 0 ), +#endif +#ifdef ONOCR + MI_ENTRY("onocr", output, SANE_UNSET | REV, ONOCR, 0 ), +#endif +#ifdef ONLRET + MI_ENTRY("onlret", output, SANE_UNSET | REV, ONLRET, 0 ), +#endif +#ifdef OFILL + MI_ENTRY("ofill", output, SANE_UNSET | REV, OFILL, 0 ), +#endif +#ifdef OFDEL + MI_ENTRY("ofdel", output, SANE_UNSET | REV, OFDEL, 0 ), +#endif +#ifdef NLDLY + MI_ENTRY("nl1", output, SANE_UNSET, NL1, NLDLY), + MI_ENTRY("nl0", output, SANE_SET, NL0, NLDLY), +#endif +#ifdef CRDLY + MI_ENTRY("cr3", output, SANE_UNSET, CR3, CRDLY), + MI_ENTRY("cr2", output, SANE_UNSET, CR2, CRDLY), + MI_ENTRY("cr1", output, SANE_UNSET, CR1, CRDLY), + MI_ENTRY("cr0", output, SANE_SET, CR0, CRDLY), +#endif + +#ifdef TABDLY + MI_ENTRY("tab3", output, SANE_UNSET, TAB3, TABDLY), + MI_ENTRY("tab2", output, SANE_UNSET, TAB2, TABDLY), + MI_ENTRY("tab1", output, SANE_UNSET, TAB1, TABDLY), + MI_ENTRY("tab0", output, SANE_SET, TAB0, TABDLY), +#else +# ifdef OXTABS + MI_ENTRY("tab3", output, SANE_UNSET, OXTABS, 0 ), +# endif +#endif + +#ifdef BSDLY + MI_ENTRY("bs1", output, SANE_UNSET, BS1, BSDLY), + MI_ENTRY("bs0", output, SANE_SET, BS0, BSDLY), +#endif +#ifdef VTDLY + MI_ENTRY("vt1", output, SANE_UNSET, VT1, VTDLY), + MI_ENTRY("vt0", output, SANE_SET, VT0, VTDLY), +#endif +#ifdef FFDLY + MI_ENTRY("ff1", output, SANE_UNSET, FF1, FFDLY), + MI_ENTRY("ff0", output, SANE_SET, FF0, FFDLY), +#endif + MI_ENTRY("isig", local, SANE_SET | REV, ISIG, 0 ), + MI_ENTRY("icanon", local, SANE_SET | REV, ICANON, 0 ), +#ifdef IEXTEN + MI_ENTRY("iexten", local, SANE_SET | REV, IEXTEN, 0 ), +#endif + MI_ENTRY("echo", local, SANE_SET | REV, ECHO, 0 ), + MI_ENTRY("echoe", local, SANE_SET | REV, ECHOE, 0 ), + MI_ENTRY("crterase", local, REV | OMIT, ECHOE, 0 ), + MI_ENTRY("echok", local, SANE_SET | REV, ECHOK, 0 ), + MI_ENTRY("echonl", local, SANE_UNSET | REV, ECHONL, 0 ), + MI_ENTRY("noflsh", local, SANE_UNSET | REV, NOFLSH, 0 ), +#ifdef XCASE + MI_ENTRY("xcase", local, SANE_UNSET | REV, XCASE, 0 ), +#endif +#ifdef TOSTOP + MI_ENTRY("tostop", local, SANE_UNSET | REV, TOSTOP, 0 ), +#endif +#ifdef ECHOPRT + MI_ENTRY("echoprt", local, SANE_UNSET | REV, ECHOPRT, 0 ), + MI_ENTRY("prterase", local, REV | OMIT, ECHOPRT, 0 ), +#endif +#ifdef ECHOCTL + MI_ENTRY("echoctl", local, SANE_SET | REV, ECHOCTL, 0 ), + MI_ENTRY("ctlecho", local, REV | OMIT, ECHOCTL, 0 ), +#endif +#ifdef ECHOKE + MI_ENTRY("echoke", local, SANE_SET | REV, ECHOKE, 0 ), + MI_ENTRY("crtkill", local, REV | OMIT, ECHOKE, 0 ), +#endif + MI_ENTRY(evenp, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(parity, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(stty_oddp, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(stty_nl, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(stty_ek, combination, OMIT, 0, 0 ), + MI_ENTRY(stty_sane, combination, OMIT, 0, 0 ), + MI_ENTRY(cooked, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(raw, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(stty_pass8, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(litout, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(cbreak, combination, REV | OMIT, 0, 0 ), +#ifdef IXANY + MI_ENTRY(decctlq, combination, REV | OMIT, 0, 0 ), +#endif +#if defined(TABDLY) || defined(OXTABS) + MI_ENTRY(stty_tabs, combination, REV | OMIT, 0, 0 ), +#endif +#if defined(XCASE) && defined(IUCLC) && defined(OLCUC) + MI_ENTRY(stty_lcase, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(stty_LCASE, combination, REV | OMIT, 0, 0 ), +#endif + MI_ENTRY(stty_crt, combination, OMIT, 0, 0 ), + MI_ENTRY(stty_dec, combination, OMIT, 0, 0 ), +}; + +enum { + NUM_mode_info = (sizeof(mode_info) / sizeof(mode_info[0])) +}; + +/* Control character settings */ +struct control_info { + const char *name; /* Name given on command line */ + unsigned char saneval; /* Value to set for 'stty sane' */ + unsigned char offset; /* Offset in c_cc */ +}; + +/* Control characters */ + +static const struct control_info control_info[] = { + {"intr", CINTR, VINTR}, + {"quit", CQUIT, VQUIT}, + {"erase", CERASE, VERASE}, + {"kill", CKILL, VKILL}, + {stty_eof, CEOF, VEOF}, + {stty_eol, CEOL, VEOL}, +#ifdef VEOL2 + {"eol2", CEOL2, VEOL2}, +#endif +#ifdef VSWTCH + {stty_swtch, CSWTCH, VSWTCH}, +#endif + {"start", CSTART, VSTART}, + {"stop", CSTOP, VSTOP}, + {"susp", CSUSP, VSUSP}, +#ifdef VDSUSP + {"dsusp", CDSUSP, VDSUSP}, +#endif +#ifdef VREPRINT + {"rprnt", CRPRNT, VREPRINT}, +#endif +#ifdef VWERASE + {"werase", CWERASE, VWERASE}, +#endif +#ifdef VLNEXT + {"lnext", CLNEXT, VLNEXT}, +#endif +#ifdef VFLUSHO + {"flush", CFLUSHO, VFLUSHO}, +#endif +#ifdef VSTATUS + {"status", CSTATUS, VSTATUS}, +#endif + /* These must be last because of the display routines */ + {stty_min, 1, VMIN}, + {stty_time, 0, VTIME}, +}; + +enum { + NUM_control_info = (sizeof(control_info) / sizeof(control_info[0])) +}; + +/* The width of the screen, for output wrapping */ +static unsigned max_col = 80; /* default */ +/* Current position, to know when to wrap */ +static unsigned current_col; +static const char *device_name = bb_msg_standard_input; + +/* Return a string that is the printable representation of character CH */ +/* Adapted from 'cat' by Torbjorn Granlund */ +static const char *visible(unsigned int ch) +{ + static char buf[10]; + char *bpout = buf; + + if (ch == _POSIX_VDISABLE) + return ""; + + if (ch >= 128) { + ch -= 128; + *bpout++ = 'M'; + *bpout++ = '-'; + } + + if (ch < 32) { + *bpout++ = '^'; + *bpout++ = ch + 64; + } else if (ch < 127) { + *bpout++ = ch; + } else { + *bpout++ = '^'; + *bpout++ = '?'; + } + + *bpout = '\0'; + return buf; +} + +static tcflag_t *mode_type_flag(unsigned type, const struct termios *mode) +{ + static const unsigned char tcflag_offsets[] = { + offsetof(struct termios, c_cflag), /* control */ + offsetof(struct termios, c_iflag), /* input */ + offsetof(struct termios, c_oflag), /* output */ + offsetof(struct termios, c_lflag), /* local */ + }; + + if (type <= local) { + return (tcflag_t*) (((char*)mode) + tcflag_offsets[type]); + } + return NULL; +} + +static speed_t string_to_baud_or_die(const char *arg) +{ + return tty_value_to_baud(xatou(arg)); +} + +static void set_speed_or_die(enum speed_setting type, const char *arg, + struct termios *mode) +{ + speed_t baud; + + baud = string_to_baud_or_die(arg); + + if (type != output_speed) { /* either input or both */ + cfsetispeed(mode, baud); + } + if (type != input_speed) { /* either output or both */ + cfsetospeed(mode, baud); + } +} + +static ATTRIBUTE_NORETURN void perror_on_device_and_die(const char *fmt) +{ + bb_perror_msg_and_die(fmt, device_name); +} + +static void perror_on_device(const char *fmt) +{ + bb_perror_msg(fmt, device_name); +} + +/* No, inline won't be as efficient (gcc 3.4.3) */ +#define streq(a,b) (!strcmp((a),(b))) + +/* Print format string MESSAGE and optional args. + Wrap to next line first if it won't fit. + Print a space first unless MESSAGE will start a new line */ +static void wrapf(const char *message, ...) +{ + char buf[128]; + va_list args; + int buflen; + + va_start(args, message); + vsnprintf(buf, sizeof(buf), message, args); + va_end(args); + buflen = strlen(buf); + if (!buflen) return; + + if (current_col > 0) { + current_col++; + if (buf[0] != '\n') { + if (current_col + buflen >= max_col) { + putchar('\n'); + current_col = 0; + } else + putchar(' '); + } + } + fputs(buf, stdout); + current_col += buflen; + if (buf[buflen-1] == '\n') + current_col = 0; +} + +#ifdef TIOCGWINSZ + +static int get_win_size(int fd, struct winsize *win) +{ + return ioctl(fd, TIOCGWINSZ, (char *) win); +} + +static void set_window_size(int rows, int cols) +{ + struct winsize win; + + if (get_win_size(STDIN_FILENO, &win)) { + if (errno != EINVAL) { + perror_on_device("%s"); + return; + } + memset(&win, 0, sizeof(win)); + } + + if (rows >= 0) + win.ws_row = rows; + if (cols >= 0) + win.ws_col = cols; + +# ifdef TIOCSSIZE + /* Alexander Dupuy wrote: + The following code deals with a bug in the SunOS 4.x (and 3.x?) kernel. + This comment from sys/ttold.h describes Sun's twisted logic - a better + test would have been (ts_lines > 64k || ts_cols > 64k || ts_cols == 0). + At any rate, the problem is gone in Solaris 2.x */ + + if (win.ws_row == 0 || win.ws_col == 0) { + struct ttysize ttysz; + + ttysz.ts_lines = win.ws_row; + ttysz.ts_cols = win.ws_col; + + win.ws_row = win.ws_col = 1; + + if ((ioctl(STDIN_FILENO, TIOCSWINSZ, (char *) &win) != 0) + || (ioctl(STDIN_FILENO, TIOCSSIZE, (char *) &ttysz) != 0)) { + perror_on_device("%s"); + } + return; + } +# endif + + if (ioctl(STDIN_FILENO, TIOCSWINSZ, (char *) &win)) + perror_on_device("%s"); +} + +static void display_window_size(int fancy) +{ + const char *fmt_str = "%s\0%s: no size information for this device"; + struct winsize win; + + if (get_win_size(STDIN_FILENO, &win)) { + if ((errno != EINVAL) || ((fmt_str += 2), !fancy)) { + perror_on_device(fmt_str); + } + } else { + wrapf(fancy ? "rows %d; columns %d;" : "%d %d\n", + win.ws_row, win.ws_col); + } +} + +#else /* !TIOCGWINSZ */ + +static inline void display_window_size(int fancy) {} + +#endif /* !TIOCGWINSZ */ + +static int screen_columns_or_die(void) +{ + const char *s; + +#ifdef TIOCGWINSZ + struct winsize win; + + /* With Solaris 2.[123], this ioctl fails and errno is set to + EINVAL for telnet (but not rlogin) sessions. + On ISC 3.0, it fails for the console and the serial port + (but it works for ptys). + It can also fail on any system when stdout isn't a tty. + In case of any failure, just use the default */ + if (get_win_size(STDOUT_FILENO, &win) == 0 && win.ws_col > 0) + return win.ws_col; +#endif + + s = getenv("COLUMNS"); + if (s) + return xatoi_u(s); + return 80; +} + +static const struct suffix_mult stty_suffixes[] = { + {"b", 512 }, + {"k", 1024}, + {"B", 1024}, + {NULL, 0 } +}; + +static const struct mode_info *find_mode(const char *name) +{ + int i; + for (i = 0; i < NUM_mode_info; ++i) + if (streq(name, mode_info[i].name)) + return &mode_info[i]; + return 0; +} + +static const struct control_info *find_control(const char *name) +{ + int i; + for (i = 0; i < NUM_control_info; ++i) + if (streq(name, control_info[i].name)) + return &control_info[i]; + return 0; +} + +enum { + param_need_arg = 0x80, + param_line = 1 | 0x80, + param_rows = 2 | 0x80, + param_cols = 3 | 0x80, + param_size = 4, + param_speed = 5, + param_ispeed = 6 | 0x80, + param_ospeed = 7 | 0x80, +}; + +static int find_param(const char *name) +{ +#ifdef HAVE_C_LINE + if (streq(name, "line")) return param_line; +#endif +#ifdef TIOCGWINSZ + if (streq(name, "rows")) return param_rows; + if (streq(name, "cols")) return param_cols; + if (streq(name, "columns")) return param_cols; + if (streq(name, "size")) return param_size; +#endif + if (streq(name, "speed")) return param_speed; + if (streq(name, "ispeed")) return param_ispeed; + if (streq(name, "ospeed")) return param_ospeed; + return 0; +} + + +static int recover_mode(const char *arg, struct termios *mode); +static void set_mode(const struct mode_info *info, + int reversed, struct termios *mode); +static void display_all(const struct termios *mode); +static void display_changed(const struct termios *mode); +static void display_recoverable(const struct termios *mode); +static void display_speed(const struct termios *mode, int fancy); +static void sane_mode(struct termios *mode); +static void set_control_char_or_die(const struct control_info *info, + const char *arg, struct termios *mode); + +int stty_main(int argc, char **argv) +{ + struct termios mode; + void (*output_func)(const struct termios *); + const char *file_name = NULL; + int require_set_attr; + int speed_was_set; + int verbose_output; + int recoverable_output; + int noargs; + int k; + + output_func = display_changed; + noargs = 1; + speed_was_set = 0; + require_set_attr = 0; + verbose_output = 0; + recoverable_output = 0; + + /* First pass: only parse/verify command line params */ + k = 0; + while (argv[++k]) { + const struct mode_info *mp; + const struct control_info *cp; + const char *arg = argv[k]; + const char *argnext = argv[k+1]; + int param; + + if (arg[0] == '-') { + int i; + mp = find_mode(arg+1); + if (mp) { + if (!(mp->flags & REV)) + bb_error_msg_and_die("invalid argument '%s'", arg); + noargs = 0; + continue; + } + /* It is an option - parse it */ + i = 0; + while (arg[++i]) { + switch (arg[i]) { + case 'a': + verbose_output = 1; + output_func = display_all; + break; + case 'g': + recoverable_output = 1; + output_func = display_recoverable; + break; + case 'F': + if (file_name) + bb_error_msg_and_die("only one device may be specified"); + file_name = &arg[i+1]; /* "-Fdevice" ? */ + if (!file_name[0]) { /* nope, "-F device" */ + int p = k+1; /* argv[p] is argnext */ + file_name = argnext; + if (!file_name) + bb_error_msg_and_die(bb_msg_requires_arg, "-F"); + /* remove -F param from arg[vc] */ + --argc; + while (argv[p]) { argv[p] = argv[p+1]; ++p; } + } + goto end_option; + default: + bb_error_msg_and_die("invalid argument '%s'", arg); + } + } +end_option: + continue; + } + + mp = find_mode(arg); + if (mp) { + noargs = 0; + continue; + } + + cp = find_control(arg); + if (cp) { + if (!argnext) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + /* called for the side effect of xfunc death only */ + set_control_char_or_die(cp, argnext, &mode); + noargs = 0; + ++k; + continue; + } + + param = find_param(arg); + if (param & param_need_arg) { + if (!argnext) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ++k; + } + + switch (param) { +#ifdef HAVE_C_LINE + case param_line: +# ifndef TIOCGWINSZ + xatoul_range_sfx(argnext, 1, INT_MAX, stty_suffixes); + break; +# endif /* else fall-through */ +#endif +#ifdef TIOCGWINSZ + case param_rows: + case param_cols: + xatoul_range_sfx(argnext, 1, INT_MAX, stty_suffixes); + break; + case param_size: +#endif + case param_speed: + break; + case param_ispeed: + /* called for the side effect of xfunc death only */ + set_speed_or_die(input_speed, argnext, &mode); + break; + case param_ospeed: + /* called for the side effect of xfunc death only */ + set_speed_or_die(output_speed, argnext, &mode); + break; + default: + if (recover_mode(arg, &mode) == 1) break; + if (string_to_baud_or_die(arg) != (speed_t) -1) break; + bb_error_msg_and_die("invalid argument '%s'", arg); + } + noargs = 0; + } + + /* Specifying both -a and -g is an error */ + if (verbose_output && recoverable_output) + bb_error_msg_and_die("verbose and stty-readable output styles are mutually exclusive"); + /* Specifying -a or -g with non-options is an error */ + if (!noargs && (verbose_output || recoverable_output)) + bb_error_msg_and_die("modes may not be set when specifying an output style"); + + /* Now it is safe to start doing things */ + if (file_name) { + int fd, fdflags; + device_name = file_name; + fd = xopen(device_name, O_RDONLY | O_NONBLOCK); + if (fd != STDIN_FILENO) { + dup2(fd, STDIN_FILENO); + close(fd); + } + fdflags = fcntl(STDIN_FILENO, F_GETFL); + if (fdflags == -1 || fcntl(STDIN_FILENO, F_SETFL, fdflags & ~O_NONBLOCK) < 0) + perror_on_device_and_die("%s: cannot reset non-blocking mode"); + } + + /* Initialize to all zeroes so there is no risk memcmp will report a + spurious difference in an uninitialized portion of the structure */ + memset(&mode, 0, sizeof(mode)); + if (tcgetattr(STDIN_FILENO, &mode)) + perror_on_device_and_die("%s"); + + if (verbose_output || recoverable_output || noargs) { + max_col = screen_columns_or_die(); + output_func(&mode); + return EXIT_SUCCESS; + } + + /* Second pass: perform actions */ + k = 0; + while (argv[++k]) { + const struct mode_info *mp; + const struct control_info *cp; + const char *arg = argv[k]; + const char *argnext = argv[k+1]; + int param; + + if (arg[0] == '-') { + mp = find_mode(arg+1); + if (mp) { + set_mode(mp, 1 /* reversed */, &mode); + } + /* It is an option - already parsed. Skip it */ + continue; + } + + mp = find_mode(arg); + if (mp) { + set_mode(mp, 0 /* non-reversed */, &mode); + continue; + } + + cp = find_control(arg); + if (cp) { + ++k; + set_control_char_or_die(cp, argnext, &mode); + continue; + } + + param = find_param(arg); + if (param & param_need_arg) { + ++k; + } + + switch (param) { +#ifdef HAVE_C_LINE + case param_line: + mode.c_line = xatoul_sfx(argnext, stty_suffixes); + require_set_attr = 1; + break; +#endif +#ifdef TIOCGWINSZ + case param_cols: + set_window_size(-1, xatoul_sfx(argnext, stty_suffixes)); + break; + case param_size: + display_window_size(0); + break; + case param_rows: + set_window_size(xatoul_sfx(argnext, stty_suffixes), -1); + break; +#endif + case param_speed: + display_speed(&mode, 0); + break; + case param_ispeed: + set_speed_or_die(input_speed, argnext, &mode); + speed_was_set = 1; + require_set_attr = 1; + break; + case param_ospeed: + set_speed_or_die(output_speed, argnext, &mode); + speed_was_set = 1; + require_set_attr = 1; + break; + default: + if (recover_mode(arg, &mode) == 1) + require_set_attr = 1; + else /* true: if (string_to_baud_or_die(arg) != (speed_t) -1) */ { + set_speed_or_die(both_speeds, arg, &mode); + speed_was_set = 1; + require_set_attr = 1; + } /* else - impossible (caught in the first pass): + bb_error_msg_and_die("invalid argument '%s'", arg); */ + } + } + + if (require_set_attr) { + struct termios new_mode; + + if (tcsetattr(STDIN_FILENO, TCSADRAIN, &mode)) + perror_on_device_and_die("%s"); + + /* POSIX (according to Zlotnick's book) tcsetattr returns zero if + it performs *any* of the requested operations. This means it + can report 'success' when it has actually failed to perform + some proper subset of the requested operations. To detect + this partial failure, get the current terminal attributes and + compare them to the requested ones */ + + /* Initialize to all zeroes so there is no risk memcmp will report a + spurious difference in an uninitialized portion of the structure */ + memset(&new_mode, 0, sizeof(new_mode)); + if (tcgetattr(STDIN_FILENO, &new_mode)) + perror_on_device_and_die("%s"); + + if (memcmp(&mode, &new_mode, sizeof(mode)) != 0) { +#ifdef CIBAUD + /* SunOS 4.1.3 (at least) has the problem that after this sequence, + tcgetattr (&m1); tcsetattr (&m1); tcgetattr (&m2); + sometimes (m1 != m2). The only difference is in the four bits + of the c_cflag field corresponding to the baud rate. To save + Sun users a little confusion, don't report an error if this + happens. But suppress the error only if we haven't tried to + set the baud rate explicitly -- otherwise we'd never give an + error for a true failure to set the baud rate */ + + new_mode.c_cflag &= (~CIBAUD); + if (speed_was_set || memcmp(&mode, &new_mode, sizeof(mode)) != 0) +#endif + perror_on_device_and_die("%s: cannot perform all requested operations"); + } + } + + return EXIT_SUCCESS; +} + +/* Save set_mode from #ifdef forest plague */ +#ifndef ONLCR +#define ONLCR 0 +#endif +#ifndef OCRNL +#define OCRNL 0 +#endif +#ifndef ONLRET +#define ONLRET 0 +#endif +#ifndef XCASE +#define XCASE 0 +#endif +#ifndef IXANY +#define IXANY 0 +#endif +#ifndef TABDLY +#define TABDLY 0 +#endif +#ifndef OXTABS +#define OXTABS 0 +#endif +#ifndef IUCLC +#define IUCLC 0 +#endif +#ifndef OLCUC +#define OLCUC 0 +#endif +#ifndef ECHOCTL +#define ECHOCTL 0 +#endif +#ifndef ECHOKE +#define ECHOKE 0 +#endif + +static void set_mode(const struct mode_info *info, int reversed, + struct termios *mode) +{ + tcflag_t *bitsp; + + bitsp = mode_type_flag(info->type, mode); + + if (bitsp) { + if (reversed) + *bitsp = *bitsp & ~((unsigned long)info->mask) & ~info->bits; + else + *bitsp = (*bitsp & ~((unsigned long)info->mask)) | info->bits; + return; + } + + /* Combination mode */ + if (info->name == evenp || info->name == parity) { + if (reversed) + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + else + mode->c_cflag = (mode->c_cflag & ~PARODD & ~CSIZE) | PARENB | CS7; + } else if (info->name == stty_oddp) { + if (reversed) + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + else + mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARODD | PARENB; + } else if (info->name == stty_nl) { + if (reversed) { + mode->c_iflag = (mode->c_iflag | ICRNL) & ~INLCR & ~IGNCR; + mode->c_oflag = (mode->c_oflag | ONLCR) & ~OCRNL & ~ONLRET; + } else { + mode->c_iflag = mode->c_iflag & ~ICRNL; + if (ONLCR) mode->c_oflag = mode->c_oflag & ~ONLCR; + } + } else if (info->name == stty_ek) { + mode->c_cc[VERASE] = CERASE; + mode->c_cc[VKILL] = CKILL; + } else if (info->name == stty_sane) { + sane_mode(mode); + } + else if (info->name == cbreak) { + if (reversed) + mode->c_lflag |= ICANON; + else + mode->c_lflag &= ~ICANON; + } else if (info->name == stty_pass8) { + if (reversed) { + mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB; + mode->c_iflag |= ISTRIP; + } else { + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + mode->c_iflag &= ~ISTRIP; + } + } else if (info->name == litout) { + if (reversed) { + mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB; + mode->c_iflag |= ISTRIP; + mode->c_oflag |= OPOST; + } else { + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + mode->c_iflag &= ~ISTRIP; + mode->c_oflag &= ~OPOST; + } + } else if (info->name == raw || info->name == cooked) { + if ((info->name[0] == 'r' && reversed) + || (info->name[0] == 'c' && !reversed)) { + /* Cooked mode */ + mode->c_iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON; + mode->c_oflag |= OPOST; + mode->c_lflag |= ISIG | ICANON; +#if VMIN == VEOF + mode->c_cc[VEOF] = CEOF; +#endif +#if VTIME == VEOL + mode->c_cc[VEOL] = CEOL; +#endif + } else { + /* Raw mode */ + mode->c_iflag = 0; + mode->c_oflag &= ~OPOST; + mode->c_lflag &= ~(ISIG | ICANON | XCASE); + mode->c_cc[VMIN] = 1; + mode->c_cc[VTIME] = 0; + } + } + else if (IXANY && info->name == decctlq) { + if (reversed) + mode->c_iflag |= IXANY; + else + mode->c_iflag &= ~IXANY; + } + else if (TABDLY && info->name == stty_tabs) { + if (reversed) + mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB3; + else + mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB0; + } + else if (OXTABS && info->name == stty_tabs) { + if (reversed) + mode->c_oflag |= OXTABS; + else + mode->c_oflag &= ~OXTABS; + } + else if (XCASE && IUCLC && OLCUC + && (info->name == stty_lcase || info->name == stty_LCASE)) { + if (reversed) { + mode->c_lflag &= ~XCASE; + mode->c_iflag &= ~IUCLC; + mode->c_oflag &= ~OLCUC; + } else { + mode->c_lflag |= XCASE; + mode->c_iflag |= IUCLC; + mode->c_oflag |= OLCUC; + } + } + else if (info->name == stty_crt) { + mode->c_lflag |= ECHOE | ECHOCTL | ECHOKE; + } + else if (info->name == stty_dec) { + mode->c_cc[VINTR] = 3; /* ^C */ + mode->c_cc[VERASE] = 127; /* DEL */ + mode->c_cc[VKILL] = 21; /* ^U */ + mode->c_lflag |= ECHOE | ECHOCTL | ECHOKE; + if (IXANY) mode->c_iflag &= ~IXANY; + } +} + +static void set_control_char_or_die(const struct control_info *info, + const char *arg, struct termios *mode) +{ + unsigned char value; + + if (info->name == stty_min || info->name == stty_time) + value = xatoul_range_sfx(arg, 0, 0xff, stty_suffixes); + else if (arg[0] == '\0' || arg[1] == '\0') + value = arg[0]; + else if (streq(arg, "^-") || streq(arg, "undef")) + value = _POSIX_VDISABLE; + else if (arg[0] == '^') { /* Ignore any trailing junk (^Cjunk) */ + value = arg[1] & 0x1f; /* Non-letters get weird results */ + if (arg[1] == '?') + value = 127; + } else + value = xatoul_range_sfx(arg, 0, 0xff, stty_suffixes); + mode->c_cc[info->offset] = value; +} + +static void display_changed(const struct termios *mode) +{ + int i; + tcflag_t *bitsp; + unsigned long mask; + int prev_type = control; + + display_speed(mode, 1); +#ifdef HAVE_C_LINE + wrapf("line = %d;\n", mode->c_line); +#else + wrapf("\n"); +#endif + + for (i = 0; control_info[i].name != stty_min; ++i) { + if (mode->c_cc[control_info[i].offset] == control_info[i].saneval) + continue; + /* If swtch is the same as susp, don't print both */ +#if VSWTCH == VSUSP + if (control_info[i].name == stty_swtch) + continue; +#endif + /* If eof uses the same slot as min, only print whichever applies */ +#if VEOF == VMIN + if ((mode->c_lflag & ICANON) == 0 + && (control_info[i].name == stty_eof + || control_info[i].name == stty_eol)) continue; +#endif + wrapf("%s = %s;", control_info[i].name, + visible(mode->c_cc[control_info[i].offset])); + } + if ((mode->c_lflag & ICANON) == 0) { + wrapf("min = %d; time = %d;", (int) mode->c_cc[VMIN], + (int) mode->c_cc[VTIME]); + } + if (current_col) wrapf("\n"); + + for (i = 0; i < NUM_mode_info; ++i) { + if (mode_info[i].flags & OMIT) + continue; + if (mode_info[i].type != prev_type) { + if (current_col) wrapf("\n"); + prev_type = mode_info[i].type; + } + + bitsp = mode_type_flag(mode_info[i].type, mode); + mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits; + if ((*bitsp & mask) == mode_info[i].bits) { + if (mode_info[i].flags & SANE_UNSET) { + wrapf("%s", mode_info[i].name); + } + } else if ((mode_info[i].flags & (SANE_SET | REV)) == (SANE_SET | REV)) { + wrapf("-%s", mode_info[i].name); + } + } + if (current_col) wrapf("\n"); +} + +static void display_all(const struct termios *mode) +{ + int i; + tcflag_t *bitsp; + unsigned long mask; + int prev_type = control; + + display_speed(mode, 1); + display_window_size(1); +#ifdef HAVE_C_LINE + wrapf("line = %d;\n", mode->c_line); +#else + wrapf("\n"); +#endif + + for (i = 0; control_info[i].name != stty_min; ++i) { + /* If swtch is the same as susp, don't print both */ +#if VSWTCH == VSUSP + if (control_info[i].name == stty_swtch) + continue; +#endif + /* If eof uses the same slot as min, only print whichever applies */ +#if VEOF == VMIN + if ((mode->c_lflag & ICANON) == 0 + && (control_info[i].name == stty_eof + || control_info[i].name == stty_eol)) continue; +#endif + wrapf("%s = %s;", control_info[i].name, + visible(mode->c_cc[control_info[i].offset])); + } +#if VEOF == VMIN + if ((mode->c_lflag & ICANON) == 0) +#endif + wrapf("min = %d; time = %d;", mode->c_cc[VMIN], mode->c_cc[VTIME]); + if (current_col) wrapf("\n"); + + for (i = 0; i < NUM_mode_info; ++i) { + if (mode_info[i].flags & OMIT) + continue; + if (mode_info[i].type != prev_type) { + wrapf("\n"); + prev_type = mode_info[i].type; + } + + bitsp = mode_type_flag(mode_info[i].type, mode); + mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits; + if ((*bitsp & mask) == mode_info[i].bits) + wrapf("%s", mode_info[i].name); + else if (mode_info[i].flags & REV) + wrapf("-%s", mode_info[i].name); + } + if (current_col) wrapf("\n"); +} + +static void display_speed(const struct termios *mode, int fancy) +{ + //01234567 8 9 + const char *fmt_str = "%lu %lu\n\0ispeed %lu baud; ospeed %lu baud;"; + unsigned long ispeed, ospeed; + + ospeed = ispeed = cfgetispeed(mode); + if (ispeed == 0 || ispeed == (ospeed = cfgetospeed(mode))) { + ispeed = ospeed; /* in case ispeed was 0 */ + //0123 4 5 6 7 8 9 + fmt_str = "%lu\n\0\0\0\0\0speed %lu baud;"; + } + if (fancy) fmt_str += 9; + wrapf(fmt_str, tty_baud_to_value(ispeed), tty_baud_to_value(ospeed)); +} + +static void display_recoverable(const struct termios *mode) +{ + int i; + printf("%lx:%lx:%lx:%lx", + (unsigned long) mode->c_iflag, (unsigned long) mode->c_oflag, + (unsigned long) mode->c_cflag, (unsigned long) mode->c_lflag); + for (i = 0; i < NCCS; ++i) + printf(":%x", (unsigned int) mode->c_cc[i]); + putchar('\n'); +} + +static int recover_mode(const char *arg, struct termios *mode) +{ + int i, n; + unsigned int chr; + unsigned long iflag, oflag, cflag, lflag; + + /* Scan into temporaries since it is too much trouble to figure out + the right format for 'tcflag_t' */ + if (sscanf(arg, "%lx:%lx:%lx:%lx%n", + &iflag, &oflag, &cflag, &lflag, &n) != 4) + return 0; + mode->c_iflag = iflag; + mode->c_oflag = oflag; + mode->c_cflag = cflag; + mode->c_lflag = lflag; + arg += n; + for (i = 0; i < NCCS; ++i) { + if (sscanf(arg, ":%x%n", &chr, &n) != 1) + return 0; + mode->c_cc[i] = chr; + arg += n; + } + + /* Fail if there are too many fields */ + if (*arg != '\0') + return 0; + + return 1; +} + +static void sane_mode(struct termios *mode) +{ + int i; + tcflag_t *bitsp; + + for (i = 0; i < NUM_control_info; ++i) { +#if VMIN == VEOF + if (control_info[i].name == stty_min) + break; +#endif + mode->c_cc[control_info[i].offset] = control_info[i].saneval; + } + + for (i = 0; i < NUM_mode_info; ++i) { + if (mode_info[i].flags & SANE_SET) { + bitsp = mode_type_flag(mode_info[i].type, mode); + *bitsp = (*bitsp & ~((unsigned long)mode_info[i].mask)) + | mode_info[i].bits; + } else if (mode_info[i].flags & SANE_UNSET) { + bitsp = mode_type_flag(mode_info[i].type, mode); + *bitsp = *bitsp & ~((unsigned long)mode_info[i].mask) + & ~mode_info[i].bits; + } + } +} diff --git a/coreutils/sum.c b/coreutils/sum.c new file mode 100644 index 000000000..93f4e22eb --- /dev/null +++ b/coreutils/sum.c @@ -0,0 +1,156 @@ +/* vi: set sw=4 ts=4: */ +/* + * sum -- checksum and count the blocks in a file + * Like BSD sum or SysV sum -r, except like SysV sum if -s option is given. + * + * Copyright (C) 86, 89, 91, 1995-2002, 2004 Free Software Foundation, Inc. + * Copyright (C) 2005 by Erik Andersen + * Copyright (C) 2005 by Mike Frysinger + * + * Written by Kayvan Aghaiepour and David MacKenzie + * Taken from coreutils and turned into a busybox applet by Mike Frysinger + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +/* 1 if any of the files read were the standard input */ +static int have_read_stdin; + +/* make a little more readable and avoid using strcmp for just 2 bytes */ +#define IS_STDIN(s) (s[0] == '-' && s[1] == '\0') + +/* Calculate and print the rotated checksum and the size in 1K blocks + of file FILE, or of the standard input if FILE is "-". + If PRINT_NAME is >1, print FILE next to the checksum and size. + The checksum varies depending on sizeof (int). + Return 1 if successful. */ +static int bsd_sum_file(const char *file, int print_name) +{ + FILE *fp; + int checksum = 0; /* The checksum mod 2^16. */ + uintmax_t total_bytes = 0; /* The number of bytes. */ + int ch; /* Each character read. */ + int ret = 0; + + if (IS_STDIN(file)) { + fp = stdin; + have_read_stdin++; + } else { + fp = fopen_or_warn(file, "r"); + if (fp == NULL) + goto out; + } + + while ((ch = getc(fp)) != EOF) { + ++total_bytes; + checksum = (checksum >> 1) + ((checksum & 1) << 15); + checksum += ch; + checksum &= 0xffff; /* Keep it within bounds. */ + } + + if (ferror(fp)) { + bb_perror_msg(file); + fclose_if_not_stdin(fp); + goto out; + } + + if (fclose_if_not_stdin(fp) == EOF) { + bb_perror_msg(file); + goto out; + } + ret++; + printf("%05d %5ju ", checksum, (total_bytes+1023)/1024); + if (print_name > 1) + puts(file); + else + puts(""); +out: + return ret; +} + +/* Calculate and print the checksum and the size in 512-byte blocks + of file FILE, or of the standard input if FILE is "-". + If PRINT_NAME is >0, print FILE next to the checksum and size. + Return 1 if successful. */ +#define MY_BUF_SIZE 8192 +static int sysv_sum_file(const char *file, int print_name) +{ + RESERVE_CONFIG_BUFFER(buf, MY_BUF_SIZE); + int fd; + uintmax_t total_bytes = 0; + + /* The sum of all the input bytes, modulo (UINT_MAX + 1). */ + unsigned int s = 0; + + if (IS_STDIN(file)) { + fd = 0; + have_read_stdin = 1; + } else { + fd = open(file, O_RDONLY); + if (fd == -1) + goto release_and_ret; + } + + while (1) { + size_t bytes_read = safe_read(fd, buf, MY_BUF_SIZE); + + if (bytes_read == 0) + break; + + if (bytes_read == -1) { +release_and_ret: + bb_perror_msg(file); + RELEASE_CONFIG_BUFFER(buf); + if (!IS_STDIN(file)) + close(fd); + return 0; + } + + total_bytes += bytes_read; + while (bytes_read--) + s += buf[bytes_read]; + } + + if (!IS_STDIN(file) && close(fd) == -1) + goto release_and_ret; + else + RELEASE_CONFIG_BUFFER(buf); + + { + int r = (s & 0xffff) + ((s & 0xffffffff) >> 16); + s = (r & 0xffff) + (r >> 16); + + printf("%d %ju ", s, (total_bytes+511)/512); + } + puts(print_name ? file : ""); + + return 1; +} + +int sum_main(int argc, char **argv) +{ + int flags; + int ok; + int (*sum_func)(const char *, int) = bsd_sum_file; + + /* give the bsd func priority over sysv func */ + flags = getopt32(argc, argv, "sr"); + if (flags & 1) + sum_func = sysv_sum_file; + if (flags & 2) + sum_func = bsd_sum_file; + + have_read_stdin = 0; + if ((argc - optind) == 0) + ok = sum_func("-", 0); + else + for (ok = 1; optind < argc; optind++) + ok &= sum_func(argv[optind], 1); + + if (have_read_stdin && fclose(stdin) == EOF) + bb_perror_msg_and_die("-"); + + exit(ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/coreutils/sync.c b/coreutils/sync.c new file mode 100644 index 000000000..59f0b504a --- /dev/null +++ b/coreutils/sync.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini sync implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +#include +#include +#include "busybox.h" + +int sync_main(int argc, char **argv) +{ + bb_warn_ignoring_args(argc - 1); + + sync(); + + return EXIT_SUCCESS; +} diff --git a/coreutils/tail.c b/coreutils/tail.c new file mode 100644 index 000000000..ed5ea1467 --- /dev/null +++ b/coreutils/tail.c @@ -0,0 +1,326 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini tail implementation for busybox + * + * Copyright (C) 2001 by Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant (need fancy for -c) */ +/* BB_AUDIT GNU compatible -c, -q, and -v options in 'fancy' configuration. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/tail.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Pretty much rewritten to fix numerous bugs and reduce realloc() calls. + * Bugs fixed (although I may have forgotten one or two... it was pretty bad) + * 1) mixing printf/write without fflush()ing stdout + * 2) no check that any open files are present + * 3) optstring had -q taking an arg + * 4) no error checking on write in some cases, and a warning even then + * 5) q and s interaction bug + * 6) no check for lseek error + * 7) lseek attempted when count==0 even if arg was +0 (from top) + */ + +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +static const struct suffix_mult tail_suffixes[] = { + { "b", 512 }, + { "k", 1024 }, + { "m", 1048576 }, + { NULL, 0 } +}; + +static int status; + +static void tail_xbb_full_write(const char *buf, size_t len) +{ + /* If we get a write error, there is really no sense in continuing. */ + if (full_write(STDOUT_FILENO, buf, len) < 0) + bb_perror_nomsg_and_die(); +} + +static void tail_xprint_header(const char *fmt, const char *filename) +{ +#if defined __GLIBC__ + if (dprintf(STDOUT_FILENO, fmt, filename) < 0) { + bb_perror_nomsg_and_die(); + } +#else + int hdr_len = strlen(fmt) + strlen(filename); + char *hdr = xzalloc(hdr_len); + sprintf(hdr, filename, filename); + tail_xbb_full_write(hdr, hdr_len); +#endif +} + +static ssize_t tail_read(int fd, char *buf, size_t count) +{ + ssize_t r; + off_t current,end; + struct stat sbuf; + + end = current = lseek(fd, 0, SEEK_CUR); + if (!fstat(fd, &sbuf)) + end = sbuf.st_size; + lseek(fd, end < current ? 0 : current, SEEK_SET); + if ((r = safe_read(fd, buf, count)) < 0) { + bb_perror_msg(bb_msg_read_error); + status = EXIT_FAILURE; + } + + return r; +} + +static const char tail_opts[] = + "fn:c:" +#if ENABLE_FEATURE_FANCY_TAIL + "qs:v" +#endif + ; + +static const char header_fmt[] = "\n==> %s <==\n"; + +int tail_main(int argc, char **argv) +{ + long count = 10; + unsigned sleep_period = 1; + int from_top = 0; + int follow = 0; + int header_threshhold = 1; + int count_bytes = 0; + + char *tailbuf; + size_t tailbufsize; + int taillen = 0; + int newline = 0; + + int *fds, nfiles, nread, nwrite, seen, i, opt; + char *s, *buf; + const char *fmt; + +#if !ENABLE_DEBUG_YANK_SUSv2 || ENABLE_FEATURE_FANCY_TAIL + /* Allow legacy syntax of an initial numeric option without -n. */ + if (argc >= 2 && (argv[1][0] == '+' || argv[1][0] == '-') + && isdigit(argv[1][1]) + ) { + optind = 2; + optarg = argv[1]; + goto GET_COUNT; + } +#endif + + while ((opt = getopt(argc, argv, tail_opts)) > 0) { + switch (opt) { + case 'f': + follow = 1; + break; + case 'c': + count_bytes = 1; + /* FALLS THROUGH */ + case 'n': +#if !ENABLE_DEBUG_YANK_SUSv2 || ENABLE_FEATURE_FANCY_TAIL + GET_COUNT: +#endif + count = xatol_sfx(optarg, tail_suffixes); + /* Note: Leading whitespace is an error trapped above. */ + if (*optarg == '+') { + from_top = 1; + } else { + from_top = 0; + } + if (count < 0) { + count = -count; + } + break; +#if ENABLE_FEATURE_FANCY_TAIL + case 'q': + header_threshhold = INT_MAX; + break; + case 's': + sleep_period = xatou(optarg); + break; + case 'v': + header_threshhold = 0; + break; +#endif + default: + bb_show_usage(); + } + } + + /* open all the files */ + fds = (int *)xmalloc(sizeof(int) * (argc - optind + 1)); + + argv += optind; + nfiles = i = 0; + + if ((argc -= optind) == 0) { + struct stat statbuf; + + if (!fstat(STDIN_FILENO, &statbuf) && S_ISFIFO(statbuf.st_mode)) { + follow = 0; + } + /* --argv; */ + *argv = (char *) bb_msg_standard_input; + goto DO_STDIN; + } + + do { + if ((argv[i][0] == '-') && !argv[i][1]) { + DO_STDIN: + fds[nfiles] = STDIN_FILENO; + } else if ((fds[nfiles] = open(argv[i], O_RDONLY)) < 0) { + bb_perror_msg("%s", argv[i]); + status = EXIT_FAILURE; + continue; + } + argv[nfiles] = argv[i]; + ++nfiles; + } while (++i < argc); + + if (!nfiles) { + bb_error_msg_and_die("no files"); + } + + tailbufsize = BUFSIZ; + + /* tail the files */ + if (from_top < count_bytes) { /* Each is 0 or 1, so true iff 0 < 1. */ + /* Hence, !from_top && count_bytes */ + if (tailbufsize < count) { + tailbufsize = count + BUFSIZ; + } + } + + buf = tailbuf = xmalloc(tailbufsize); + + fmt = header_fmt + 1; /* Skip header leading newline on first output. */ + i = 0; + do { + /* Be careful. It would be possible to optimize the count-bytes + * case if the file is seekable. If you do though, remember that + * starting file position may not be the beginning of the file. + * Beware of backing up too far. See example in wc.c. + */ + if ((!(count|from_top)) && (lseek(fds[i], 0, SEEK_END) >= 0)) { + continue; + } + + if (nfiles > header_threshhold) { + tail_xprint_header(fmt, argv[i]); + fmt = header_fmt; + } + + buf = tailbuf; + taillen = 0; + seen = 1; + newline = 0; + + while ((nread = tail_read(fds[i], buf, tailbufsize-taillen)) > 0) { + if (from_top) { + nwrite = nread; + if (seen < count) { + if (count_bytes) { + nwrite -= (count - seen); + seen = count; + } else { + s = buf; + do { + --nwrite; + if ((*s++ == '\n') && (++seen == count)) { + break; + } + } while (nwrite); + } + } + tail_xbb_full_write(buf + nread - nwrite, nwrite); + } else if (count) { + if (count_bytes) { + taillen += nread; + if (taillen > count) { + memmove(tailbuf, tailbuf + taillen - count, count); + taillen = count; + } + } else { + int k = nread; + int nbuf = 0; + + while (k) { + --k; + if (buf[k] == '\n') { + ++nbuf; + } + } + + if (newline + nbuf < count) { + newline += nbuf; + taillen += nread; + + } else { + int extra = 0; + if (buf[nread-1] != '\n') { + extra = 1; + } + + k = newline + nbuf + extra - count; + s = tailbuf; + while (k) { + if (*s == '\n') { + --k; + } + ++s; + } + + taillen += nread - (s - tailbuf); + memmove(tailbuf, s, taillen); + newline = count - extra; + } + if (tailbufsize < taillen + BUFSIZ) { + tailbufsize = taillen + BUFSIZ; + tailbuf = xrealloc(tailbuf, tailbufsize); + } + } + buf = tailbuf + taillen; + } + } + + if (!from_top) { + tail_xbb_full_write(tailbuf, taillen); + } + + taillen = 0; + } while (++i < nfiles); + + buf = xrealloc(tailbuf, BUFSIZ); + + fmt = NULL; + + while (follow) { + sleep(sleep_period); + i = 0; + do { + if (nfiles > header_threshhold) { + fmt = header_fmt; + } + while ((nread = tail_read(fds[i], buf, sizeof(buf))) > 0) { + if (fmt) { + tail_xprint_header(fmt, argv[i]); + fmt = NULL; + } + tail_xbb_full_write(buf, nread); + } + } while (++i < nfiles); + } + + return status; +} diff --git a/coreutils/tee.c b/coreutils/tee.c new file mode 100644 index 000000000..640a231ef --- /dev/null +++ b/coreutils/tee.c @@ -0,0 +1,100 @@ +/* vi: set sw=4 ts=4: */ +/* + * tee implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/tee.html */ + +#include "busybox.h" +#include + +int tee_main(int argc, char **argv) +{ + const char *mode = "w\0a"; + FILE **files; + FILE **fp; + char **names; + char **np; + int flags; + int retval = EXIT_SUCCESS; +#if ENABLE_FEATURE_TEE_USE_BLOCK_IO + ssize_t c; +# define buf bb_common_bufsiz1 +#else + int c; +#endif + flags = getopt32(argc, argv, "ia"); /* 'a' must be 2nd */ + argc -= optind; + argv += optind; + + mode += (flags & 2); /* Since 'a' is the 2nd option... */ + + if (flags & 1) { + signal(SIGINT, SIG_IGN); /* TODO - switch to sigaction. */ + } + /* gnu tee ignores SIGPIPE in case one of the output files is a pipe + * that doesn't consume all its input. Good idea... */ + signal(SIGPIPE, SIG_IGN); /* TODO - switch to sigaction. */ + + /* Allocate an array of FILE *'s, with one extra for a sentinal. */ + fp = files = xzalloc(sizeof(FILE *) * (argc + 2)); + np = names = argv - 1; + + files[0] = stdout; + goto GOT_NEW_FILE; + do { + *fp = fopen_or_warn(*argv, mode); + if (*fp == NULL) { + retval = EXIT_FAILURE; + continue; + } + *np = *argv++; + GOT_NEW_FILE: + setbuf(*fp++, NULL); /* tee must not buffer output. */ + np++; + } while (*argv); + /* names[0] will be filled later */ + +#if ENABLE_FEATURE_TEE_USE_BLOCK_IO + while ((c = safe_read(STDIN_FILENO, buf, BUFSIZ)) > 0) { + fp = files; + do + fwrite(buf, 1, c, *fp++); + while (*fp); + } + if (c < 0) { /* Make sure read errors are signaled. */ + retval = EXIT_FAILURE; + } +#else + setvbuf(stdout, NULL, _IONBF, 0); + while ((c = getchar()) != EOF) { + fp = files; + do + putc(c, *fp++); + while (*fp); + } +#endif + + /* Now we need to check for i/o errors on stdin and the various + * output files. Since we know that the first entry in the output + * file table is stdout, we can save one "if ferror" test by + * setting the first entry to stdin and checking stdout error + * status with fflush_stdout_and_exit()... although fflush()ing + * is unnecessary here. */ + np = names; + fp = files; + names[0] = (char *) bb_msg_standard_input; + files[0] = stdin; + do { /* Now check for input and output errors. */ + /* Checking ferror should be sufficient, but we may want to fclose. + * If we do, remember not to close stdin! */ + die_if_ferror(*fp++, *np++); + } while (*fp); + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/test.c b/coreutils/test.c new file mode 100644 index 000000000..ebb4f9086 --- /dev/null +++ b/coreutils/test.c @@ -0,0 +1,583 @@ +/* vi: set sw=4 ts=4: */ +/* + * test implementation for busybox + * + * Copyright (c) by a whole pile of folks: + * + * test(1); version 7-like -- author Erik Baalbergen + * modified by Eric Gisin to be used as built-in. + * modified by Arnold Robbins to add SVR3 compatibility + * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). + * modified by J.T. Conklin for NetBSD. + * modified by Herbert Xu to be used as built-in in ash. + * modified by Erik Andersen to be used + * in busybox. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Original copyright notice states: + * "This program is in the Public Domain." + */ + +#include "busybox.h" +#include +#include +#include +#include +#include + +/* test(1) accepts the following grammar: + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary | "!" primary + primary ::= unary-operator operand + | operand binary-operator operand + | operand + | "(" oexpr ")" + ; + unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| + "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; + + binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| + "-nt"|"-ot"|"-ef"; + operand ::= +*/ + +enum token { + EOI, + FILRD, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + FILNT, + FILOT, + FILEQ, + FILUID, + FILGID, + STREZ, + STRNZ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT, + BAND, + BOR, + LPAREN, + RPAREN, + OPERAND +}; + +enum token_types { + UNOP, + BINOP, + BUNOP, + BBINOP, + PAREN +}; + +static const struct t_op { + const char *op_text; + short op_num, op_type; +} ops[] = { + { + "-r", FILRD, UNOP}, { + "-w", FILWR, UNOP}, { + "-x", FILEX, UNOP}, { + "-e", FILEXIST, UNOP}, { + "-f", FILREG, UNOP}, { + "-d", FILDIR, UNOP}, { + "-c", FILCDEV, UNOP}, { + "-b", FILBDEV, UNOP}, { + "-p", FILFIFO, UNOP}, { + "-u", FILSUID, UNOP}, { + "-g", FILSGID, UNOP}, { + "-k", FILSTCK, UNOP}, { + "-s", FILGZ, UNOP}, { + "-t", FILTT, UNOP}, { + "-z", STREZ, UNOP}, { + "-n", STRNZ, UNOP}, { + "-h", FILSYM, UNOP}, /* for backwards compat */ + { + "-O", FILUID, UNOP}, { + "-G", FILGID, UNOP}, { + "-L", FILSYM, UNOP}, { + "-S", FILSOCK, UNOP}, { + "=", STREQ, BINOP}, { + "==", STREQ, BINOP}, { + "!=", STRNE, BINOP}, { + "<", STRLT, BINOP}, { + ">", STRGT, BINOP}, { + "-eq", INTEQ, BINOP}, { + "-ne", INTNE, BINOP}, { + "-ge", INTGE, BINOP}, { + "-gt", INTGT, BINOP}, { + "-le", INTLE, BINOP}, { + "-lt", INTLT, BINOP}, { + "-nt", FILNT, BINOP}, { + "-ot", FILOT, BINOP}, { + "-ef", FILEQ, BINOP}, { + "!", UNOT, BUNOP}, { + "-a", BAND, BBINOP}, { + "-o", BOR, BBINOP}, { + "(", LPAREN, PAREN}, { + ")", RPAREN, PAREN}, { + 0, 0, 0} +}; + +#ifdef CONFIG_FEATURE_TEST_64 +typedef int64_t arith_t; +#else +typedef int arith_t; +#endif + +static char **t_wp; +static struct t_op const *t_wp_op; +static gid_t *group_array; +static int ngroups; + +static enum token t_lex(char *s); +static arith_t oexpr(enum token n); +static arith_t aexpr(enum token n); +static arith_t nexpr(enum token n); +static int binop(void); +static arith_t primary(enum token n); +static int filstat(char *nm, enum token mode); +static arith_t getn(const char *s); +static int newerf(const char *f1, const char *f2); +static int olderf(const char *f1, const char *f2); +static int equalf(const char *f1, const char *f2); +static int test_eaccess(char *path, int mode); +static int is_a_group_member(gid_t gid); +static void initialize_group_array(void); + +static jmp_buf leaving; + +int bb_test(int argc, char **argv) +{ + int res; + + if (strcmp(argv[0], "[") == 0) { + if (strcmp(argv[--argc], "]")) { + bb_error_msg("missing ]"); + return 2; + } + argv[argc] = NULL; + } else if (strcmp(argv[0], "[[") == 0) { + if (strcmp(argv[--argc], "]]")) { + bb_error_msg("missing ]]"); + return 2; + } + argv[argc] = NULL; + } + + res = setjmp(leaving); + if (res) + return res; + + /* resetting ngroups is probably unnecessary. it will + * force a new call to getgroups(), which prevents using + * group data fetched during a previous call. but the + * only way the group data could be stale is if there's + * been an intervening call to setgroups(), and this + * isn't likely in the case of a shell. paranoia + * prevails... + */ + ngroups = 0; + + /* Implement special cases from POSIX.2, section 4.62.4 */ + switch (argc) { + case 1: + return 1; + case 2: + return *argv[1] == '\0'; + case 3: + if (argv[1][0] == '!' && argv[1][1] == '\0') { + return *argv[2] != '\0'; + } + break; + case 4: + if (argv[1][0] != '!' || argv[1][1] != '\0') { + if (t_lex(argv[2]), t_wp_op && t_wp_op->op_type == BINOP) { + t_wp = &argv[1]; + return binop() == 0; + } + } + break; + case 5: + if (argv[1][0] == '!' && argv[1][1] == '\0') { + if (t_lex(argv[3]), t_wp_op && t_wp_op->op_type == BINOP) { + t_wp = &argv[2]; + return binop() != 0; + } + } + break; + } + + t_wp = &argv[1]; + res = !oexpr(t_lex(*t_wp)); + + if (*t_wp != NULL && *++t_wp != NULL) { + bb_error_msg("%s: unknown operand", *t_wp); + return 2; + } + return res; +} + +static void syntax(const char *op, const char *msg) +{ + if (op && *op) { + bb_error_msg("%s: %s", op, msg); + } else { + bb_error_msg("%s", msg); + } + longjmp(leaving, 2); +} + +static arith_t oexpr(enum token n) +{ + arith_t res; + + res = aexpr(n); + if (t_lex(*++t_wp) == BOR) { + return oexpr(t_lex(*++t_wp)) || res; + } + t_wp--; + return res; +} + +static arith_t aexpr(enum token n) +{ + arith_t res; + + res = nexpr(n); + if (t_lex(*++t_wp) == BAND) + return aexpr(t_lex(*++t_wp)) && res; + t_wp--; + return res; +} + +static arith_t nexpr(enum token n) +{ + if (n == UNOT) + return !nexpr(t_lex(*++t_wp)); + return primary(n); +} + +static arith_t primary(enum token n) +{ + arith_t res; + + if (n == EOI) { + syntax(NULL, "argument expected"); + } + if (n == LPAREN) { + res = oexpr(t_lex(*++t_wp)); + if (t_lex(*++t_wp) != RPAREN) + syntax(NULL, "closing paren expected"); + return res; + } + if (t_wp_op && t_wp_op->op_type == UNOP) { + /* unary expression */ + if (*++t_wp == NULL) + syntax(t_wp_op->op_text, "argument expected"); + switch (n) { + case STREZ: + return strlen(*t_wp) == 0; + case STRNZ: + return strlen(*t_wp) != 0; + case FILTT: + return isatty(getn(*t_wp)); + default: + return filstat(*t_wp, n); + } + } + + if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { + return binop(); + } + + return strlen(*t_wp) > 0; +} + +static int binop(void) +{ + const char *opnd1, *opnd2; + struct t_op const *op; + + opnd1 = *t_wp; + (void) t_lex(*++t_wp); + op = t_wp_op; + + if ((opnd2 = *++t_wp) == (char *) 0) + syntax(op->op_text, "argument expected"); + + switch (op->op_num) { + case STREQ: + return strcmp(opnd1, opnd2) == 0; + case STRNE: + return strcmp(opnd1, opnd2) != 0; + case STRLT: + return strcmp(opnd1, opnd2) < 0; + case STRGT: + return strcmp(opnd1, opnd2) > 0; + case INTEQ: + return getn(opnd1) == getn(opnd2); + case INTNE: + return getn(opnd1) != getn(opnd2); + case INTGE: + return getn(opnd1) >= getn(opnd2); + case INTGT: + return getn(opnd1) > getn(opnd2); + case INTLE: + return getn(opnd1) <= getn(opnd2); + case INTLT: + return getn(opnd1) < getn(opnd2); + case FILNT: + return newerf(opnd1, opnd2); + case FILOT: + return olderf(opnd1, opnd2); + case FILEQ: + return equalf(opnd1, opnd2); + } + /* NOTREACHED */ + return 1; +} + +static int filstat(char *nm, enum token mode) +{ + struct stat s; + unsigned int i; + + if (mode == FILSYM) { +#ifdef S_IFLNK + if (lstat(nm, &s) == 0) { + i = S_IFLNK; + goto filetype; + } +#endif + return 0; + } + + if (stat(nm, &s) != 0) + return 0; + + switch (mode) { + case FILRD: + return test_eaccess(nm, R_OK) == 0; + case FILWR: + return test_eaccess(nm, W_OK) == 0; + case FILEX: + return test_eaccess(nm, X_OK) == 0; + case FILEXIST: + return 1; + case FILREG: + i = S_IFREG; + goto filetype; + case FILDIR: + i = S_IFDIR; + goto filetype; + case FILCDEV: + i = S_IFCHR; + goto filetype; + case FILBDEV: + i = S_IFBLK; + goto filetype; + case FILFIFO: +#ifdef S_IFIFO + i = S_IFIFO; + goto filetype; +#else + return 0; +#endif + case FILSOCK: +#ifdef S_IFSOCK + i = S_IFSOCK; + goto filetype; +#else + return 0; +#endif + case FILSUID: + i = S_ISUID; + goto filebit; + case FILSGID: + i = S_ISGID; + goto filebit; + case FILSTCK: + i = S_ISVTX; + goto filebit; + case FILGZ: + return s.st_size > 0L; + case FILUID: + return s.st_uid == geteuid(); + case FILGID: + return s.st_gid == getegid(); + default: + return 1; + } + + filetype: + return ((s.st_mode & S_IFMT) == i); + + filebit: + return ((s.st_mode & i) != 0); +} + +static enum token t_lex(char *s) +{ + struct t_op const *op = ops; + + if (s == 0) { + t_wp_op = (struct t_op *) 0; + return EOI; + } + while (op->op_text) { + if (strcmp(s, op->op_text) == 0) { + t_wp_op = op; + return op->op_num; + } + op++; + } + t_wp_op = (struct t_op *) 0; + return OPERAND; +} + +/* atoi with error detection */ +static arith_t getn(const char *s) +{ + char *p; +#ifdef CONFIG_FEATURE_TEST_64 + long long r; +#else + long r; +#endif + + errno = 0; +#ifdef CONFIG_FEATURE_TEST_64 + r = strtoll(s, &p, 10); +#else + r = strtol(s, &p, 10); +#endif + + if (errno != 0) + syntax(s, "out of range"); + + if (*(skip_whitespace(p))) + syntax(s, "bad number"); + + return r; +} + +static int newerf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime); +} + +static int olderf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime); +} + +static int equalf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && + b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); +} + +/* Do the same thing access(2) does, but use the effective uid and gid, + and don't make the mistake of telling root that any file is + executable. */ +static int test_eaccess(char *path, int mode) +{ + struct stat st; + unsigned int euid = geteuid(); + + if (stat(path, &st) < 0) + return -1; + + if (euid == 0) { + /* Root can read or write any file. */ + if (mode != X_OK) + return 0; + + /* Root can execute any file that has any one of the execute + bits set. */ + if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) + return 0; + } + + if (st.st_uid == euid) /* owner */ + mode <<= 6; + else if (is_a_group_member(st.st_gid)) + mode <<= 3; + + if (st.st_mode & mode) + return 0; + + return -1; +} + +static void initialize_group_array(void) +{ + ngroups = getgroups(0, NULL); + if (ngroups > 0) { + group_array = xmalloc(ngroups * sizeof(gid_t)); + getgroups(ngroups, group_array); + } +} + +/* Return non-zero if GID is one that we have in our groups list. */ +static int is_a_group_member(gid_t gid) +{ + int i; + + /* Short-circuit if possible, maybe saving a call to getgroups(). */ + if (gid == getgid() || gid == getegid()) + return 1; + + if (ngroups == 0) + initialize_group_array(); + + /* Search through the list looking for GID. */ + for (i = 0; i < ngroups; i++) + if (gid == group_array[i]) + return 1; + + return 0; +} + + +/* applet entry point */ + +int test_main(int argc, char **argv) +{ + exit(bb_test(argc, argv)); +} + diff --git a/coreutils/touch.c b/coreutils/touch.c new file mode 100644 index 000000000..e1af7d0dc --- /dev/null +++ b/coreutils/touch.c @@ -0,0 +1,63 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini touch implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 _NOT_ compliant -- options -a, -m, -r, -t not supported. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/touch.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Previous version called open() and then utime(). While this will be + * be necessary to implement -r and -t, it currently only makes things bigger. + * Also, exiting on a failure was a bug. All args should be processed. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +int touch_main(int argc, char **argv) +{ + int fd; + int flags; + int status = EXIT_SUCCESS; + + flags = getopt32(argc, argv, "c"); + + argv += optind; + + if (!*argv) { + bb_show_usage(); + } + + do { + if (utime(*argv, NULL)) { + if (errno == ENOENT) { /* no such file*/ + if (flags & 1) { /* Creation is disabled, so ignore. */ + continue; + } + /* Try to create the file. */ + fd = open(*argv, O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH + ); + if ((fd >= 0) && !close(fd)) { + continue; + } + } + status = EXIT_FAILURE; + bb_perror_msg("%s", *argv); + } + } while (*++argv); + + return status; +} diff --git a/coreutils/tr.c b/coreutils/tr.c new file mode 100644 index 000000000..237ade047 --- /dev/null +++ b/coreutils/tr.c @@ -0,0 +1,253 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini tr implementation for busybox + * + ** Copyright (c) 1987,1997, Prentice Hall All rights reserved. + * + * The name of Prentice Hall may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * Copyright (c) Michiel Huisjes + * + * This version of tr is adapted from Minix tr and was modified + * by Erik Andersen to be used in busybox. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +// Even with -funsigned-char, gcc still complains about char as an array index. + +#define GCC4_IS_STUPID int + +#define ASCII 0377 + +/* some "globals" shared across this file */ +static char com_fl, del_fl, sq_fl; +/* these last are pointers to static buffers declared in tr_main */ +static char *poutput, *pvector, *pinvec, *poutvec; + +static void convert(void) +{ + int read_chars = 0, in_index = 0, out_index = 0, c, coded, last = -1; + + for (;;) { + // If we're out of input, flush output and read more input. + + if (in_index == read_chars) { + if (out_index) { + if (write(1, (char *) poutput, out_index) != out_index) + bb_error_msg_and_die(bb_msg_write_error); + out_index = 0; + } + + if ((read_chars = read(0, bb_common_bufsiz1, BUFSIZ)) <= 0) { + if (write(1, (char *) poutput, out_index) != out_index) + bb_error_msg(bb_msg_write_error); + exit(0); + } + in_index = 0; + } + c = bb_common_bufsiz1[in_index++]; + coded = pvector[c]; + if (del_fl && pinvec[c]) + continue; + if (sq_fl && last == coded && (pinvec[c] || poutvec[coded])) + continue; + poutput[out_index++] = last = coded; + } + + /* NOTREACHED */ +} + +static void map(char *string1, unsigned int string1_len, + char *string2, unsigned int string2_len) +{ + char last = '0'; + unsigned int i, j; + + for (j = 0, i = 0; i < string1_len; i++) { + if (string2_len <= j) + pvector[(GCC4_IS_STUPID)string1[i]] = last; + else + pvector[(GCC4_IS_STUPID)string1[i]] = last = string2[j++]; + } +} + +/* supported constructs: + * Ranges, e.g., [0-9] ==> 0123456789 + * Escapes, e.g., \a ==> Control-G + * Character classes, e.g. [:upper:] ==> A ... Z + */ +static unsigned int expand(const char *arg, char *buffer) +{ + char *buffer_start = buffer; + int i, ac; + + while (*arg) { + if (*arg == '\\') { + arg++; + *buffer++ = bb_process_escape_sequence(&arg); + } else if (*(arg+1) == '-') { + ac = *(arg+2); + if(ac == 0) { + *buffer++ = *arg++; + continue; + } + i = *arg; + while (i <= ac) + *buffer++ = i++; + arg += 3; /* Skip the assumed a-z */ + } else if (*arg == '[') { + arg++; + i = *arg++; + if (ENABLE_FEATURE_TR_CLASSES && i == ':') { + if (strncmp(arg, "alpha", 5) == 0) { + for (i = 'A'; i <= 'Z'; i++) + *buffer++ = i; + for (i = 'a'; i <= 'z'; i++) + *buffer++ = i; + } + else if (strncmp(arg, "alnum", 5) == 0) { + for (i = '0'; i <= '9'; i++) + *buffer++ = i; + for (i = 'A'; i <= 'Z'; i++) + *buffer++ = i; + for (i = 'a'; i <= 'z'; i++) + *buffer++ = i; + } + else if (strncmp(arg, "digit", 5) == 0) + for (i = '0'; i <= '9'; i++) + *buffer++ = i; + else if (strncmp(arg, "lower", 5) == 0) + for (i = 'a'; i <= 'z'; i++) + *buffer++ = i; + else if (strncmp(arg, "upper", 5) == 0) + for (i = 'A'; i <= 'Z'; i++) + *buffer++ = i; + else if (strncmp(arg, "space", 5) == 0) { + const char s[] = "\t\n\v\f\r "; + strcat((char*)buffer, s); + buffer += sizeof(s) - 1; + } + else if (strncmp(arg, "blank", 5) == 0) { + *buffer++ = '\t'; + *buffer++ = ' '; + } + /* gcc gives a warning if braces aren't used here */ + else if (strncmp(arg, "punct", 5) == 0) { + for (i = 0; i <= ASCII; i++) + if (isprint(i) && (!isalnum(i)) && (!isspace(i))) + *buffer++ = i; + } + else if (strncmp(arg, "cntrl", 5) == 0) { + for (i = 0; i <= ASCII; i++) + if (iscntrl(i)) + *buffer++ = i; + } + else { + *buffer++ = '['; + *buffer++ = ':'; + continue; + } + break; + } + if (ENABLE_FEATURE_TR_EQUIV && i == '=') { + *buffer++ = *arg; + /* skip the closing =] */ + arg += 3; + continue; + } + if (*arg++ != '-') { + *buffer++ = '['; + arg -= 2; + continue; + } + ac = *arg++; + while (i <= ac) + *buffer++ = i++; + arg++; /* Skip the assumed ']' */ + } else + *buffer++ = *arg++; + } + + return (buffer - buffer_start); +} + +static int complement(char *buffer, int buffer_len) +{ + short i, j, ix; + char conv[ASCII + 2]; + + ix = 0; + for (i = 0; i <= ASCII; i++) { + for (j = 0; j < buffer_len; j++) + if (buffer[j] == i) + break; + if (j == buffer_len) + conv[ix++] = i & ASCII; + } + memcpy(buffer, conv, ix); + return ix; +} + +int tr_main(int argc, char **argv) +{ + unsigned char *ptr; + int output_length=0, input_length; + int idx = 1; + int i; + RESERVE_CONFIG_BUFFER(output, BUFSIZ); + RESERVE_CONFIG_BUFFER(vector, ASCII+1); + RESERVE_CONFIG_BUFFER(invec, ASCII+1); + RESERVE_CONFIG_BUFFER(outvec, ASCII+1); + + /* ... but make them available globally */ + poutput = output; + pvector = vector; + pinvec = invec; + poutvec = outvec; + + if (argc > 1 && argv[idx][0] == '-') { + for (ptr = (unsigned char *) &argv[idx][1]; *ptr; ptr++) { + switch (*ptr) { + case 'c': + com_fl = TRUE; + break; + case 'd': + del_fl = TRUE; + break; + case 's': + sq_fl = TRUE; + break; + default: + bb_show_usage(); + } + } + idx++; + } + for (i = 0; i <= ASCII; i++) { + vector[i] = i; + invec[i] = outvec[i] = FALSE; + } + + if (argv[idx] != NULL) { + input_length = expand(argv[idx++], bb_common_bufsiz1); + if (com_fl) + input_length = complement(bb_common_bufsiz1, input_length); + if (argv[idx] != NULL) { + if (*argv[idx] == '\0') + bb_error_msg_and_die("STRING2 cannot be empty"); + output_length = expand(argv[idx], output); + map(bb_common_bufsiz1, input_length, output, output_length); + } + for (i = 0; i < input_length; i++) + invec[(GCC4_IS_STUPID)bb_common_bufsiz1[i]] = TRUE; + for (i = 0; i < output_length; i++) + outvec[(GCC4_IS_STUPID)output[i]] = TRUE; + } + convert(); + return 0; +} diff --git a/coreutils/true.c b/coreutils/true.c new file mode 100644 index 000000000..388e897c0 --- /dev/null +++ b/coreutils/true.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini true implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/true.html */ + +#include +#include "busybox.h" + +int true_main(int argc, char **argv) +{ + return EXIT_SUCCESS; +} diff --git a/coreutils/tty.c b/coreutils/tty.c new file mode 100644 index 000000000..b2ab862ec --- /dev/null +++ b/coreutils/tty.c @@ -0,0 +1,45 @@ +/* vi: set sw=4 ts=4: */ +/* + * tty implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/tty.html */ + +#include +#include +#include +#include "busybox.h" + +int tty_main(int argc, char **argv) +{ + const char *s; + int silent; /* Note: No longer relevant in SUSv3. */ + int retval; + + xfunc_error_retval = 2; /* SUSv3 requires > 1 for error. */ + + silent = getopt32(argc, argv, "s"); + + /* gnu tty outputs a warning that it is ignoring all args. */ + bb_warn_ignoring_args(argc - optind); + + retval = 0; + + if ((s = ttyname(0)) == NULL) { + /* According to SUSv3, ttyname can on fail with EBADF or ENOTTY. + * We know the file descriptor is good, so failure means not a tty. */ + s = "not a tty"; + retval = 1; + } + + if (!silent) { + puts(s); + } + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/uname.c b/coreutils/uname.c new file mode 100644 index 000000000..5a3eafe92 --- /dev/null +++ b/coreutils/uname.c @@ -0,0 +1,107 @@ +/* vi: set sw=4 ts=4: */ +/* uname -- print system information + * Copyright (C) 1989-1999 Free Software Foundation, Inc. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/uname.html */ + +/* Option Example + + -s, --sysname SunOS + -n, --nodename rocky8 + -r, --release 4.0 + -v, --version + -m, --machine sun + -a, --all SunOS rocky8 4.0 sun + + The default behavior is equivalent to `-s'. + + David MacKenzie */ + +/* Busyboxed by Erik Andersen */ + +/* Further size reductions by Glenn McGrath and Manuel Novoa III. */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Now does proper error checking on i/o. Plus some further space savings. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +typedef struct { + struct utsname name; + char processor[8]; /* for "unknown" */ +} uname_info_t; + +static const char options[] = "snrvmpa"; +static const unsigned short int utsname_offset[] = { + offsetof(uname_info_t,name.sysname), + offsetof(uname_info_t,name.nodename), + offsetof(uname_info_t,name.release), + offsetof(uname_info_t,name.version), + offsetof(uname_info_t,name.machine), + offsetof(uname_info_t,processor) +}; + +int uname_main(int argc, char **argv) +{ + uname_info_t uname_info; +#if defined(__sparc__) && defined(__linux__) + char *fake_sparc = getenv("FAKE_SPARC"); +#endif + const unsigned short int *delta; + char toprint; + + toprint = getopt32(argc, argv, options); + + if (argc != optind) { + bb_show_usage(); + } + + if (toprint & (1 << 6)) { + toprint = 0x3f; + } + + if (toprint == 0) { + toprint = 1; /* sysname */ + } + + if (uname(&uname_info.name) == -1) { + bb_error_msg_and_die("cannot get system name"); + } + +#if defined(__sparc__) && defined(__linux__) + if ((fake_sparc != NULL) + && ((fake_sparc[0] == 'y') + || (fake_sparc[0] == 'Y'))) { + strcpy(uname_info.name.machine, "sparc"); + } +#endif + + strcpy(uname_info.processor, "unknown"); + + delta = utsname_offset; + do { + if (toprint & 1) { + printf(((char *)(&uname_info)) + *delta); + if (toprint > 1) { + putchar(' '); + } + } + ++delta; + } while (toprint >>= 1); + putchar('\n'); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/uniq.c b/coreutils/uniq.c new file mode 100644 index 000000000..100f2be00 --- /dev/null +++ b/coreutils/uniq.c @@ -0,0 +1,103 @@ +/* vi: set sw=4 ts=4: */ +/* + * uniq implementation for busybox + * + * Copyright (C) 2005 Manuel Novoa III + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/uniq.html */ + +#include "busybox.h" + +static const char uniq_opts[] = "f:s:" "cdu\0\1\2\4"; + +static FILE *xgetoptfile_uniq_s(char **argv, int read0write2) +{ + const char *n; + + if ((n = *argv) != NULL) { + if ((*n != '-') || n[1]) { + return xfopen(n, "r\0w" + read0write2); + } + } + return (read0write2) ? stdout : stdin; +} + +int uniq_main(int argc, char **argv) +{ + FILE *in, *out; + unsigned long dups, skip_fields, skip_chars, i, uniq_flags; + const char *s0, *e0, *s1, *e1, *input_filename; + int opt; + + uniq_flags = skip_fields = skip_chars = 0; + + while ((opt = getopt(argc, argv, uniq_opts)) > 0) { + if ((opt == 'f') || (opt == 's')) { + unsigned long t = xatoul(optarg); + if (opt == 'f') { + skip_fields = t; + } else { + skip_chars = t; + } + } else if ((s0 = strchr(uniq_opts, opt)) != NULL) { + uniq_flags |= s0[4]; + } else { + bb_show_usage(); + } + } + + input_filename = *(argv += optind); + + in = xgetoptfile_uniq_s(argv, 0); + if (*argv) { + ++argv; + } + out = xgetoptfile_uniq_s(argv, 2); + if (*argv && argv[1]) { + bb_show_usage(); + } + + s1 = e1 = NULL; /* prime the pump */ + + do { + s0 = s1; + e0 = e1; + dups = 0; + + /* gnu uniq ignores newlines */ + while ((s1 = xmalloc_getline(in)) != NULL) { + e1 = s1; + for (i = skip_fields; i; i--) { + e1 = skip_whitespace(e1); + while (*e1 && !isspace(*e1)) { + ++e1; + } + } + for (i = skip_chars; *e1 && i; i--) { + ++e1; + } + + if (!s0 || strcmp(e0, e1)) { + break; + } + + ++dups; /* Note: Testing for overflow seems excessive. */ + } + + if (s0) { + if (!(uniq_flags & (2 << !!dups))) { + fprintf(out, "\0%d " + (uniq_flags & 1), dups + 1); + fprintf(out, "%s\n", s0); + } + free((void *)s0); + } + } while (s1); + + die_if_ferror(in, input_filename); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/usleep.c b/coreutils/usleep.c new file mode 100644 index 000000000..de473a7b2 --- /dev/null +++ b/coreutils/usleep.c @@ -0,0 +1,28 @@ +/* vi: set sw=4 ts=4: */ +/* + * usleep implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Apparently a busybox extension. */ + +#include +#include +#include +#include "busybox.h" + +int usleep_main(int argc, char **argv) +{ + if (argc != 2) { + bb_show_usage(); + } + + if (usleep(xatou(argv[1]))) { + bb_perror_nomsg_and_die(); + } + + return EXIT_SUCCESS; +} diff --git a/coreutils/uudecode.c b/coreutils/uudecode.c new file mode 100644 index 000000000..8b7de74ff --- /dev/null +++ b/coreutils/uudecode.c @@ -0,0 +1,181 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 2003, Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Based on specification from + * http://www.opengroup.org/onlinepubs/007904975/utilities/uuencode.html + * + * Bugs: the spec doesn't mention anything about "`\n`\n" prior to the + * "end" line + */ + + +#include "busybox.h" + +static int read_stduu(FILE *src_stream, FILE *dst_stream) +{ + char *line; + + while ((line = xmalloc_getline(src_stream)) != NULL) { + int length; + char *line_ptr = line; + + if (strcmp(line, "end") == 0) { + return EXIT_SUCCESS; + } + length = ((*line_ptr - 0x20) & 0x3f)* 4 / 3; + + if (length <= 0) { + /* Ignore the "`\n" line, why is it even in the encode file ? */ + continue; + } + if (length > 60) { + bb_error_msg_and_die("line too long"); + } + + line_ptr++; + /* Tolerate an overly long line to accomodate a possible exta '`' */ + if (strlen(line_ptr) < (size_t)length) { + bb_error_msg_and_die("short file"); + } + + while (length > 0) { + /* Merge four 6 bit chars to three 8 bit chars */ + fputc(((line_ptr[0] - 0x20) & 077) << 2 | ((line_ptr[1] - 0x20) & 077) >> 4, dst_stream); + line_ptr++; + length--; + if (length == 0) { + break; + } + + fputc(((line_ptr[0] - 0x20) & 077) << 4 | ((line_ptr[1] - 0x20) & 077) >> 2, dst_stream); + line_ptr++; + length--; + if (length == 0) { + break; + } + + fputc(((line_ptr[0] - 0x20) & 077) << 6 | ((line_ptr[1] - 0x20) & 077), dst_stream); + line_ptr += 2; + length -= 2; + } + free(line); + } + bb_error_msg_and_die("short file"); +} + +static int read_base64(FILE *src_stream, FILE *dst_stream) +{ + static const char base64_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n"; + char term_count = 0; + + while (1) { + char translated[4]; + int count = 0; + + while (count < 4) { + char *table_ptr; + int ch; + + /* Get next _valid_ character */ + do { + ch = fgetc(src_stream); + if (ch == EOF) { + bb_error_msg_and_die("short file"); + } + } while ((table_ptr = strchr(base64_table, ch)) == NULL); + + /* Convert encoded charcter to decimal */ + ch = table_ptr - base64_table; + + if (*table_ptr == '=') { + if (term_count == 0) { + translated[count] = 0; + break; + } + term_count++; + } + else if (*table_ptr == '\n') { + /* Check for terminating line */ + if (term_count == 5) { + return EXIT_SUCCESS; + } + term_count = 1; + continue; + } else { + translated[count] = ch; + count++; + term_count = 0; + } + } + + /* Merge 6 bit chars to 8 bit */ + fputc(translated[0] << 2 | translated[1] >> 4, dst_stream); + if (count > 2) { + fputc(translated[1] << 4 | translated[2] >> 2, dst_stream); + } + if (count > 3) { + fputc(translated[2] << 6 | translated[3], dst_stream); + } + } +} + +int uudecode_main(int argc, char **argv) +{ + FILE *src_stream; + char *outname = NULL; + char *line; + + getopt32(argc, argv, "o:", &outname); + + if (optind == argc) { + src_stream = stdin; + } else if (optind + 1 == argc) { + src_stream = xfopen(argv[optind], "r"); + } else { + bb_show_usage(); + } + + /* Search for the start of the encoding */ + while ((line = xmalloc_getline(src_stream)) != NULL) { + int (*decode_fn_ptr)(FILE * src, FILE * dst); + char *line_ptr; + FILE *dst_stream; + int mode; + int ret; + + if (strncmp(line, "begin-base64 ", 13) == 0) { + line_ptr = line + 13; + decode_fn_ptr = read_base64; + } else if (strncmp(line, "begin ", 6) == 0) { + line_ptr = line + 6; + decode_fn_ptr = read_stduu; + } else { + free(line); + continue; + } + + mode = strtoul(line_ptr, NULL, 8); + if (outname == NULL) { + outname = strchr(line_ptr, ' '); + if ((outname == NULL) || (*outname == '\0')) { + break; + } + outname++; + } + if (strcmp(outname, "-") == 0) { + dst_stream = stdout; + } else { + dst_stream = xfopen(outname, "w"); + chmod(outname, mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + } + free(line); + ret = decode_fn_ptr(src_stream, dst_stream); + fclose_if_not_stdin(src_stream); + return ret; + } + bb_error_msg_and_die("no 'begin' line"); +} diff --git a/coreutils/uuencode.c b/coreutils/uuencode.c new file mode 100644 index 000000000..e8f8d541c --- /dev/null +++ b/coreutils/uuencode.c @@ -0,0 +1,75 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2000 by Glenn McGrath + * + * based on the function base64_encode from http.c in wget v1.6 + * Copyright (C) 1995, 1996, 1997, 1998, 2000 Free Software Foundation, Inc. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + + +#define SRC_BUF_SIZE 45 // This *MUST* be a multiple of 3 +#define DST_BUF_SIZE 4 * ((SRC_BUF_SIZE + 2) / 3) +int uuencode_main(int argc, char **argv) +{ + const size_t src_buf_size = SRC_BUF_SIZE; + const size_t dst_buf_size = DST_BUF_SIZE; + size_t write_size = dst_buf_size; + struct stat stat_buf; + FILE *src_stream = stdin; + const char *tbl; + size_t size; + mode_t mode; + RESERVE_CONFIG_BUFFER(src_buf, SRC_BUF_SIZE + 1); + RESERVE_CONFIG_BUFFER(dst_buf, DST_BUF_SIZE + 1); + + tbl = bb_uuenc_tbl_std; + if (getopt32(argc, argv, "m") & 1) { + tbl = bb_uuenc_tbl_base64; + } + + switch (argc - optind) { + case 2: + src_stream = xfopen(argv[optind], "r"); + xstat(argv[optind], &stat_buf); + mode = stat_buf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); + if (src_stream == stdout) { + puts("NULL"); + } + break; + case 1: + mode = 0666 & ~umask(0666); + break; + default: + bb_show_usage(); + } + + printf("begin%s %o %s", tbl == bb_uuenc_tbl_std ? "" : "-base64", mode, argv[argc - 1]); + + while ((size = fread(src_buf, 1, src_buf_size, src_stream)) > 0) { + if (size != src_buf_size) { + /* write_size is always 60 until the last line */ + write_size = (4 * ((size + 2) / 3)); + /* pad with 0s so we can just encode extra bits */ + memset(&src_buf[size], 0, src_buf_size - size); + } + /* Encode the buffer we just read in */ + bb_uuencode((unsigned char*)src_buf, dst_buf, size, tbl); + + putchar('\n'); + if (tbl == bb_uuenc_tbl_std) { + putchar(tbl[size]); + } + if (fwrite(dst_buf, 1, write_size, stdout) != write_size) { + bb_perror_msg_and_die(bb_msg_write_error); + } + } + printf(tbl == bb_uuenc_tbl_std ? "\n`\nend\n" : "\n====\n"); + + die_if_ferror(src_stream, "source"); /* TODO - Fix this! */ + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/watch.c b/coreutils/watch.c new file mode 100644 index 000000000..81856c867 --- /dev/null +++ b/coreutils/watch.c @@ -0,0 +1,79 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini watch implementation for busybox + * + * Copyright (C) 2001 by Michael Habermann + * Copyrigjt (C) Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A */ +/* BB_AUDIT GNU defects -- only option -n is supported. */ + +#include "busybox.h" + +// procps 2.0.18: +// watch [-d] [-n seconds] +// [--differences[=cumulative]] [--interval=seconds] command +// +// procps-3.2.3: +// watch [-dt] [-n seconds] +// [--differences[=cumulative]] [--interval=seconds] [--no-title] command +// +// (procps 3.x and procps 2.x are forks, not newer/older versions of the same) + +int watch_main(int argc, char **argv) +{ + unsigned opt; + unsigned period = 2; + unsigned cmdlen = 1; // 1 for terminal NUL + char *header = NULL; + char *cmd; + char *tmp; + char **p; + + opt_complementary = "-1"; // at least one param please + opt = getopt32(argc, argv, "+dtn:", &tmp); + //if (opt & 0x1) // -d (ignore) + //if (opt & 0x2) // -t + if (opt & 0x4) period = xatou(tmp); + argv += optind; + + p = argv; + while (*p) + cmdlen += strlen(*p++) + 1; + tmp = cmd = xmalloc(cmdlen); + while (*argv) { + tmp += sprintf(tmp, " %s", *argv); + argv++; + } + cmd++; // skip initial space + + while (1) { + printf("\033[H\033[J"); + if (!(opt & 0x2)) { // no -t + int width, len; + char *thyme; + time_t t; + + get_terminal_width_height(STDOUT_FILENO, &width, 0); + header = xrealloc(header, width--); + // '%-*s' pads header with spaces to the full width + snprintf(header, width, "Every %ds: %-*s", period, width, cmd); + time(&t); + thyme = ctime(&t); + len = strlen(thyme); + if (len < width) + strcpy(header + width - len, thyme); + puts(header); + } + fflush(stdout); + // TODO: 'real' watch pipes cmd's output to itself + // and does not allow it to overflow the screen + // (taking into account linewrap!) + system(cmd); + sleep(period); + } + return 0; // gcc thinks we can reach this :) +} diff --git a/coreutils/wc.c b/coreutils/wc.c new file mode 100644 index 000000000..973929ebe --- /dev/null +++ b/coreutils/wc.c @@ -0,0 +1,204 @@ +/* vi: set sw=4 ts=4: */ +/* + * wc implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 _NOT_ compliant -- option -m is not currently supported. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/wc.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Rewritten to fix a number of problems and do some size optimizations. + * Problems in the previous busybox implementation (besides bloat) included: + * 1) broken 'wc -c' optimization (read note below) + * 2) broken handling of '-' args + * 3) no checking of ferror on EOF returns + * 4) isprint() wasn't considered when word counting. + * + * TODO: + * + * When locale support is enabled, count multibyte chars in the '-m' case. + * + * NOTES: + * + * The previous busybox wc attempted an optimization using stat for the + * case of counting chars only. I omitted that because it was broken. + * It didn't take into account the possibility of input coming from a + * pipe, or input from a file with file pointer not at the beginning. + * + * To implement such a speed optimization correctly, not only do you + * need the size, but also the file position. Note also that the + * file position may be past the end of file. Consider the example + * (adapted from example in gnu wc.c) + * + * echo hello > /tmp/testfile && + * (dd ibs=1k skip=1 count=0 &> /dev/null ; wc -c) < /tmp/testfile + * + * for which 'wc -c' should output '0'. + */ + +#include "busybox.h" + +#ifdef CONFIG_LOCALE_SUPPORT +#define isspace_given_isprint(c) isspace(c) +#else +#undef isspace +#undef isprint +#define isspace(c) ((((c) == ' ') || (((unsigned int)((c) - 9)) <= (13 - 9)))) +#define isprint(c) (((unsigned int)((c) - 0x20)) <= (0x7e - 0x20)) +#define isspace_given_isprint(c) ((c) == ' ') +#endif + +#if ENABLE_FEATURE_WC_LARGE +#define COUNT_T unsigned long long +#define COUNT_FMT "llu" +#else +#define COUNT_T unsigned +#define COUNT_FMT "u" +#endif + +enum { + WC_LINES = 0, + WC_WORDS = 1, + WC_CHARS = 2, + WC_LENGTH = 3 +}; + +int wc_main(int argc, char **argv) +{ + FILE *fp; + const char *s, *arg; + const char *start_fmt = "%9"COUNT_FMT; + const char *fname_fmt = " %s\n"; + COUNT_T *pcounts; + COUNT_T counts[4]; + COUNT_T totals[4]; + unsigned linepos; + unsigned u; + int num_files = 0; + int c; + char status = EXIT_SUCCESS; + char in_word; + unsigned print_type; + + print_type = getopt32(argc, argv, "lwcL"); + + if (print_type == 0) { + print_type = (1 << WC_LINES) | (1 << WC_WORDS) | (1 << WC_CHARS); + } + + argv += optind; + if (!argv[0]) { + *--argv = (char *) bb_msg_standard_input; + fname_fmt = "\n"; + if (!((print_type-1) & print_type)) /* exactly one option? */ + start_fmt = "%"COUNT_FMT; + } + + memset(totals, 0, sizeof(totals)); + + pcounts = counts; + + while ((arg = *argv++) != 0) { + ++num_files; + fp = fopen_or_warn_stdin(arg); + if (!fp) { + status = EXIT_FAILURE; + continue; + } + + memset(counts, 0, sizeof(counts)); + linepos = 0; + in_word = 0; + + do { + /* Our -w doesn't match GNU wc exactly... oh well */ + + ++counts[WC_CHARS]; + c = getc(fp); + if (isprint(c)) { + ++linepos; + if (!isspace_given_isprint(c)) { + in_word = 1; + continue; + } + } else if (((unsigned int)(c - 9)) <= 4) { + /* \t 9 + * \n 10 + * \v 11 + * \f 12 + * \r 13 + */ + if (c == '\t') { + linepos = (linepos | 7) + 1; + } else { /* '\n', '\r', '\f', or '\v' */ + DO_EOF: + if (linepos > counts[WC_LENGTH]) { + counts[WC_LENGTH] = linepos; + } + if (c == '\n') { + ++counts[WC_LINES]; + } + if (c != '\v') { + linepos = 0; + } + } + } else if (c == EOF) { + if (ferror(fp)) { + bb_perror_msg("%s", arg); + status = EXIT_FAILURE; + } + --counts[WC_CHARS]; + goto DO_EOF; /* Treat an EOF as '\r'. */ + } else { + continue; + } + + counts[WC_WORDS] += in_word; + in_word = 0; + if (c == EOF) { + break; + } + } while (1); + + if (totals[WC_LENGTH] < counts[WC_LENGTH]) { + totals[WC_LENGTH] = counts[WC_LENGTH]; + } + totals[WC_LENGTH] -= counts[WC_LENGTH]; + + fclose_if_not_stdin(fp); + + OUTPUT: + /* coreutils wc tries hard to print pretty columns + * (saves results for all files, find max col len etc...) + * we won't try that hard, it will bloat us too much */ + s = start_fmt; + u = 0; + do { + if (print_type & (1 << u)) { + printf(s, pcounts[u]); + s = " %9"COUNT_FMT; /* Ok... restore the leading space. */ + } + totals[u] += pcounts[u]; + } while (++u < 4); + printf(fname_fmt, arg); + } + + /* If more than one file was processed, we want the totals. To save some + * space, we set the pcounts ptr to the totals array. This has the side + * effect of trashing the totals array after outputting it, but that's + * irrelavent since we no longer need it. */ + if (num_files > 1) { + num_files = 0; /* Make sure we don't get here again. */ + arg = "total"; + pcounts = totals; + --argv; + goto OUTPUT; + } + + fflush_stdout_and_exit(status); +} diff --git a/coreutils/who.c b/coreutils/who.c new file mode 100644 index 000000000..4cd42652b --- /dev/null +++ b/coreutils/who.c @@ -0,0 +1,67 @@ +/* vi: set sw=4 ts=4: */ +/*---------------------------------------------------------------------- + * Mini who is used to display user name, login time, + * idle time and host name. + * + * Author: Da Chen + * + * This is a free document; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation: + * http://www.gnu.org/copyleft/gpl.html + * + * Copyright (c) 2002 AYR Networks, Inc. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + *---------------------------------------------------------------------- + */ + +#include "busybox.h" +#include +#include + +static const char * idle_string (time_t t) +{ + static char str[6]; + + time_t s = time(NULL) - t; + + if (s < 60) + return "."; + if (s < (24 * 60 * 60)) { + sprintf(str, "%02d:%02d", + (int) (s / (60 * 60)), + (int) ((s % (60 * 60)) / 60)); + return str; + } + return "old"; +} + +int who_main(int argc, char **argv) +{ + struct utmp *ut; + struct stat st; + char *name; + + if (argc > 1) { + bb_show_usage(); + } + + setutent(); + printf("USER TTY IDLE TIME HOST\n"); + while ((ut = getutent()) != NULL) { + if (ut->ut_user[0] && ut->ut_type == USER_PROCESS) { + time_t thyme = ut->ut_tv.tv_sec; + + /* ut->ut_line is device name of tty - "/dev/" */ + name = concat_path_file("/dev", ut->ut_line); + printf("%-10s %-8s %-8s %-12.12s %s\n", ut->ut_user, ut->ut_line, + (stat(name, &st)) ? "?" : idle_string(st.st_atime), + ctime(&thyme) + 4, ut->ut_host); + if (ENABLE_FEATURE_CLEAN_UP) free(name); + } + } + if (ENABLE_FEATURE_CLEAN_UP) endutent(); + return 0; +} diff --git a/coreutils/whoami.c b/coreutils/whoami.c new file mode 100644 index 000000000..df714f22c --- /dev/null +++ b/coreutils/whoami.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini whoami implementation for busybox + * + * Copyright (C) 2000 Edward Betts . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +#include +#include +#include +#include "busybox.h" + +int whoami_main(int argc, char **argv) +{ + if (argc > 1) + bb_show_usage(); + + puts(bb_getpwuid(NULL, geteuid(), -1)); + /* exits on error */ + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/yes.c b/coreutils/yes.c new file mode 100644 index 000000000..894506a89 --- /dev/null +++ b/coreutils/yes.c @@ -0,0 +1,41 @@ +/* vi: set sw=4 ts=4: */ +/* + * yes implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Size reductions and removed redundant applet name prefix from error messages. + */ + +#include "busybox.h" + +int yes_main(int argc, char **argv) +{ + static const char fmt_str[] = " %s"; + const char *fmt; + char **first_arg; + + *argv = "y"; + if (argc != 1) { + ++argv; + } + + first_arg = argv; + do { + fmt = fmt_str + 1; + do { + printf(fmt, *argv); + fmt = fmt_str; + } while (*++argv); + argv = first_arg; + } while (putchar('\n') != EOF); + + bb_perror_nomsg_and_die(); +} diff --git a/debianutils/Config.in b/debianutils/Config.in new file mode 100644 index 000000000..3d85999ff --- /dev/null +++ b/debianutils/Config.in @@ -0,0 +1,88 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Debian Utilities" + +config MKTEMP + bool "mktemp" + default n + help + mktemp is used to create unique temporary files + +config PIPE_PROGRESS + bool "pipe_progress" + default n + help + Display a dot to indicate pipe activity. + +config READLINK + bool "readlink" + default n + help + This program reads a symbolic link and returns the name + of the file it points to + +config FEATURE_READLINK_FOLLOW + bool "Enable canonicalization by following all symlinks (-f)" + default n + depends on READLINK + help + Enable the readlink option (-f). + +config RUN_PARTS + bool "run-parts" + default n + help + run-parts is a utility designed to run all the scripts in a directory. + + It is useful to set up a directory like cron.daily, where you need to + execute all the scripts in that directory. + + In this implementation of run-parts some features (such as report mode) + are not implemented. + + Unless you know that run-parts is used in some of your scripts + you can safely say N here. + +config FEATURE_RUN_PARTS_LONG_OPTIONS + bool "Enable long options" + default n + depends on RUN_PARTS && GETOPT_LONG + help + Support long options for the run-parts applet. + +config START_STOP_DAEMON + bool "start-stop-daemon" + default y + help + start-stop-daemon is used to control the creation and + termination of system-level processes, usually the ones + started during the startup of the system. + +config FEATURE_START_STOP_DAEMON_FANCY + bool "Support additional arguments" + default y + depends on START_STOP_DAEMON + help + Support additional arguments. + -o|--oknodo ignored since we exit with 0 anyway + -v|--verbose + +config FEATURE_START_STOP_DAEMON_LONG_OPTIONS + bool "Enable long options" + default n + depends on START_STOP_DAEMON && GETOPT_LONG + help + Support long options for the start-stop-daemon applet. + +config WHICH + bool "which" + default n + help + which is used to find programs in your PATH and + print out their pathnames. + +endmenu + diff --git a/debianutils/Kbuild b/debianutils/Kbuild new file mode 100644 index 000000000..99df6a536 --- /dev/null +++ b/debianutils/Kbuild @@ -0,0 +1,13 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_MKTEMP) += mktemp.o +lib-$(CONFIG_PIPE_PROGRESS) += pipe_progress.o +lib-$(CONFIG_READLINK) += readlink.o +lib-$(CONFIG_RUN_PARTS) += run_parts.o +lib-$(CONFIG_START_STOP_DAEMON) += start_stop_daemon.o +lib-$(CONFIG_WHICH) += which.o diff --git a/debianutils/mktemp.c b/debianutils/mktemp.c new file mode 100644 index 000000000..d47d5a0bf --- /dev/null +++ b/debianutils/mktemp.c @@ -0,0 +1,48 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini mktemp implementation for busybox + * + * + * Copyright (C) 2000 by Daniel Jacobowitz + * Written by Daniel Jacobowitz + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include +#include +#include +#include +#include + +int mktemp_main(int argc, char **argv) +{ + unsigned long flags = getopt32(argc, argv, "dqt"); + char *chp; + + if (optind + 1 != argc) + bb_show_usage(); + + chp = argv[optind]; + + if (flags & 4) { + char *dir = getenv("TMPDIR"); + if (dir && *dir != '\0') + chp = concat_path_file(dir, chp); + else + chp = concat_path_file("/tmp/", chp); + } + + if (flags & 1) { + if (mkdtemp(chp) == NULL) + return EXIT_FAILURE; + } else { + if (mkstemp(chp) < 0) + return EXIT_FAILURE; + } + + puts(chp); + + return EXIT_SUCCESS; +} diff --git a/debianutils/pipe_progress.c b/debianutils/pipe_progress.c new file mode 100644 index 000000000..75d26e20f --- /dev/null +++ b/debianutils/pipe_progress.c @@ -0,0 +1,42 @@ +/* vi: set sw=4 ts=4: */ +/* + * Monitor a pipe with a simple progress display. + * + * Copyright (C) 2003 by Rob Landley , Joey Hess + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include +#include + +#define PIPE_PROGRESS_SIZE 4096 + +/* Read a block of data from stdin, write it to stdout. + * Activity is indicated by a '.' to stderr + */ +int pipe_progress_main(int argc, char **argv) +{ + RESERVE_CONFIG_BUFFER(buf, PIPE_PROGRESS_SIZE); + time_t t = time(NULL); + size_t len; + + while ((len = fread(buf, 1, PIPE_PROGRESS_SIZE, stdin)) > 0) { + time_t new_time = time(NULL); + if (new_time != t) { + t = new_time; + fputc('.', stderr); + } + fwrite(buf, len, 1, stdout); + } + + fputc('\n', stderr); + + if (ENABLE_FEATURE_CLEAN_UP) + RELEASE_CONFIG_BUFFER(buf); + + return 0; +} diff --git a/debianutils/readlink.c b/debianutils/readlink.c new file mode 100644 index 000000000..400259080 --- /dev/null +++ b/debianutils/readlink.c @@ -0,0 +1,51 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini readlink implementation for busybox + * + * Copyright (C) 2000,2001 Matt Kraai + * + * Licensed under GPL v2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include +#include + +int readlink_main(int argc, char **argv) +{ + char *buf; + char *fname; + + USE_FEATURE_READLINK_FOLLOW( + unsigned opt; + /* We need exactly one non-option argument. */ + opt_complementary = "=1"; + opt = getopt32(argc, argv, "f"); + fname = argv[optind]; + ) + SKIP_FEATURE_READLINK_FOLLOW( + const unsigned opt = 0; + if (argc != 2) bb_show_usage(); + fname = argv[1]; + ) + + /* compat: coreutils readlink reports errors silently via exit code */ + logmode = LOGMODE_NONE; + + if (opt) { + buf = realpath(fname, bb_common_bufsiz1); + } else { + buf = xreadlink(fname); + } + + if (!buf) + return EXIT_FAILURE; + puts(buf); + + if (ENABLE_FEATURE_CLEAN_UP && buf != bb_common_bufsiz1) + free(buf); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/debianutils/run_parts.c b/debianutils/run_parts.c new file mode 100644 index 000000000..2b3b18854 --- /dev/null +++ b/debianutils/run_parts.c @@ -0,0 +1,191 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini run-parts implementation for busybox + * + * + * Copyright (C) 2001 by Emanuele Aina + * + * Based on the Debian run-parts program, version 1.15 + * Copyright (C) 1996 Jeff Noxon , + * Copyright (C) 1996-1999 Guy Maor + * + * + * Licensed under GPL v2 or later, see file LICENSE in this tarball for details. + */ + +/* This is my first attempt to write a program in C (well, this is my first + * attempt to write a program! :-) . */ + +/* This piece of code is heavily based on the original version of run-parts, + * taken from debian-utils. I've only removed the long options and a the + * report mode. As the original run-parts support only long options, I've + * broken compatibility because the BusyBox policy doesn't allow them. + * The supported options are: + * -t test. Print the name of the files to be executed, without + * execute them. + * -a ARG argument. Pass ARG as an argument the program executed. It can + * be repeated to pass multiple arguments. + * -u MASK umask. Set the umask of the program executed to MASK. */ + +/* TODO + * done - convert calls to error in perror... and remove error() + * done - convert malloc/realloc to their x... counterparts + * done - remove catch_sigchld + * done - use bb's concat_path_file() + * done - declare run_parts_main() as extern and any other function as static? + */ + +#include "busybox.h" +#include + +static const struct option runparts_long_options[] = { + { "test", 0, NULL, 't' }, + { "umask", 1, NULL, 'u' }, + { "arg", 1, NULL, 'a' }, + { 0, 0, 0, 0 } +}; + +/* valid_name */ +/* True or false? Is this a valid filename (upper/lower alpha, digits, + * underscores, and hyphens only?) + */ +static int valid_name(const struct dirent *d) +{ + const char *c = d->d_name; + + while (*c) { + if (!isalnum(*c) && (*c != '_') && (*c != '-')) { + return 0; + } + ++c; + } + return 1; +} + +/* test mode = 1 is the same as official run_parts + * test_mode = 2 means to fail silently on missing directories + */ +static int run_parts(char **args, const unsigned char test_mode) +{ + struct dirent **namelist = 0; + struct stat st; + char *filename; + char *arg0 = args[0]; + int entries; + int i; + int exitstatus = 0; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &i; + (void) &exitstatus; +#endif + /* scandir() isn't POSIX, but it makes things easy. */ + entries = scandir(arg0, &namelist, valid_name, alphasort); + + if (entries == -1) { + if (test_mode & 2) { + return 2; + } + bb_perror_msg_and_die("cannot open '%s'", arg0); + } + + for (i = 0; i < entries; i++) { + filename = concat_path_file(arg0, namelist[i]->d_name); + + xstat(filename, &st); + if (S_ISREG(st.st_mode) && !access(filename, X_OK)) { + if (test_mode) { + puts(filename); + } else { + /* exec_errno is common vfork variable */ + volatile int exec_errno = 0; + int result; + int pid; + + if ((pid = vfork()) < 0) { + bb_perror_msg_and_die("failed to fork"); + } else if (!pid) { + args[0] = filename; + execve(filename, args, environ); + exec_errno = errno; + _exit(1); + } + + waitpid(pid, &result, 0); + if (exec_errno) { + errno = exec_errno; + bb_perror_msg("failed to exec %s", filename); + exitstatus = 1; + } + if (WIFEXITED(result) && WEXITSTATUS(result)) { + bb_perror_msg("%s exited with return code %d", filename, WEXITSTATUS(result)); + exitstatus = 1; + } else if (WIFSIGNALED(result)) { + bb_perror_msg("%s exited because of uncaught signal %d", filename, WTERMSIG(result)); + exitstatus = 1; + } + } + } else if (!S_ISDIR(st.st_mode)) { + bb_error_msg("component %s is not an executable plain file", filename); + exitstatus = 1; + } + + free(namelist[i]); + free(filename); + } + free(namelist); + + return exitstatus; +} + + +/* run_parts_main */ +/* Process options */ +int run_parts_main(int argc, char **argv) +{ + char **args = xmalloc(2 * sizeof(char *)); + unsigned char test_mode = 0; + unsigned short argcount = 1; + int opt; + + umask(022); + + while ((opt = getopt_long(argc, argv, "tu:a:", + runparts_long_options, NULL)) > 0) + { + switch (opt) { + /* Enable test mode */ + case 't': + test_mode++; + break; + /* Set the umask of the programs executed */ + case 'u': + /* Check and set the umask of the program executed. As stated in the original + * run-parts, the octal conversion in libc is not foolproof; it will take the + * 8 and 9 digits under some circumstances. We'll just have to live with it. + */ + umask(xstrtoul_range(optarg, 8, 0, 07777)); + break; + /* Pass an argument to the programs */ + case 'a': + /* Add an argument to the commands that we will call. + * Called once for every argument. */ + args = xrealloc(args, (argcount + 2) * (sizeof(char *))); + args[argcount++] = optarg; + break; + default: + bb_show_usage(); + } + } + + /* We require exactly one argument: the directory name */ + if (optind != (argc - 1)) { + bb_show_usage(); + } + + args[0] = argv[optind]; + args[argcount] = 0; + + return run_parts(args, test_mode); +} diff --git a/debianutils/start_stop_daemon.c b/debianutils/start_stop_daemon.c new file mode 100644 index 000000000..f52352995 --- /dev/null +++ b/debianutils/start_stop_daemon.c @@ -0,0 +1,319 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini start-stop-daemon implementation(s) for busybox + * + * Written by Marek Michalkiewicz , + * Adapted for busybox David Kimdon + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include + +static int signal_nr = 15; +static int user_id = -1; +static int quiet; +static char *userspec; +static char *chuid; +static char *cmdname; +static char *execname; +static char *pidfile; + +struct pid_list { + struct pid_list *next; + pid_t pid; +}; + +static struct pid_list *found = NULL; + +static inline void push(pid_t pid) +{ + struct pid_list *p; + + p = xmalloc(sizeof(*p)); + p->next = found; + p->pid = pid; + found = p; +} + +static int pid_is_exec(pid_t pid, const char *name) +{ + char buf[sizeof("/proc//exe") + sizeof(int)*3]; + char *execbuf; + int equal; + + sprintf(buf, "/proc/%d/exe", pid); + execbuf = xstrdup(name); + readlink(buf, execbuf, strlen(name)+1); + + equal = ! strcmp(execbuf, name); + if (ENABLE_FEATURE_CLEAN_UP) + free(execbuf); + return equal; +} + +static int pid_is_user(int pid, int uid) +{ + struct stat sb; + char buf[sizeof("/proc/") + sizeof(int)*3]; + + sprintf(buf, "/proc/%d", pid); + if (stat(buf, &sb) != 0) + return 0; + return (sb.st_uid == uid); +} + +static int pid_is_cmd(pid_t pid, const char *name) +{ + char buf[sizeof("/proc//stat") + sizeof(int)*3]; + FILE *f; + int c; + + sprintf(buf, "/proc/%d/stat", pid); + f = fopen(buf, "r"); + if (!f) + return 0; + while ((c = getc(f)) != EOF && c != '(') + ; + if (c != '(') { + fclose(f); + return 0; + } + /* this hopefully handles command names containing ')' */ + while ((c = getc(f)) != EOF && c == *name) + name++; + fclose(f); + return (c == ')' && *name == '\0'); +} + + +static void check(int pid) +{ + if (execname && !pid_is_exec(pid, execname)) { + return; + } + if (userspec && !pid_is_user(pid, user_id)) { + return; + } + if (cmdname && !pid_is_cmd(pid, cmdname)) { + return; + } + push(pid); +} + + +static void do_pidfile(void) +{ + FILE *f; + pid_t pid; + + f = fopen(pidfile, "r"); + if (f) { + if (fscanf(f, "%d", &pid) == 1) + check(pid); + fclose(f); + } else if (errno != ENOENT) + bb_perror_msg_and_die("open pidfile %s", pidfile); +} + +static void do_procinit(void) +{ + DIR *procdir; + struct dirent *entry; + int foundany, pid; + + if (pidfile) { + do_pidfile(); + return; + } + + procdir = xopendir("/proc"); + + foundany = 0; + while ((entry = readdir(procdir)) != NULL) { + if (sscanf(entry->d_name, "%d", &pid) != 1) + continue; + foundany++; + check(pid); + } + closedir(procdir); + if (!foundany) + bb_error_msg_and_die ("nothing in /proc - not mounted?"); +} + + +static int do_stop(void) +{ + char *what; + struct pid_list *p; + int killed = 0; + + do_procinit(); + + if (cmdname) + what = xstrdup(cmdname); + else if (execname) + what = xstrdup(execname); + else if (pidfile) + what = xasprintf("process in pidfile '%s'", pidfile); + else if (userspec) + what = xasprintf("process(es) owned by '%s'", userspec); + else + bb_error_msg_and_die("internal error, please report"); + + if (!found) { + if (!quiet) + printf("no %s found; none killed\n", what); + if (ENABLE_FEATURE_CLEAN_UP) + free(what); + return -1; + } + for (p = found; p; p = p->next) { + if (kill(p->pid, signal_nr) == 0) { + p->pid = -p->pid; + killed++; + } else { + bb_perror_msg("warning: failed to kill %d", p->pid); + } + } + if (!quiet && killed) { + printf("stopped %s (pid", what); + for (p = found; p; p = p->next) + if(p->pid < 0) + printf(" %d", -p->pid); + puts(")"); + } + if (ENABLE_FEATURE_CLEAN_UP) + free(what); + return killed; +} + +#if ENABLE_FEATURE_START_STOP_DAEMON_LONG_OPTIONS +static const struct option ssd_long_options[] = { + { "stop", 0, NULL, 'K' }, + { "start", 0, NULL, 'S' }, + { "background", 0, NULL, 'b' }, + { "quiet", 0, NULL, 'q' }, + { "make-pidfile", 0, NULL, 'm' }, +#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY + { "oknodo", 0, NULL, 'o' }, + { "verbose", 0, NULL, 'v' }, + { "nicelevel", 1, NULL, 'N' }, +#endif + { "startas", 1, NULL, 'a' }, + { "name", 1, NULL, 'n' }, + { "signal", 1, NULL, 's' }, + { "user", 1, NULL, 'u' }, + { "chuid", 1, NULL, 'c' }, + { "exec", 1, NULL, 'x' }, + { "pidfile", 1, NULL, 'p' }, +#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY + { "retry", 1, NULL, 'R' }, +#endif + { 0, 0, 0, 0 } +}; +#endif + +#define SSD_CTX_STOP 0x1 +#define SSD_CTX_START 0x2 +#define SSD_OPT_BACKGROUND 0x4 +#define SSD_OPT_QUIET 0x8 +#define SSD_OPT_MAKEPID 0x10 +#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY +#define SSD_OPT_OKNODO 0x20 +#define SSD_OPT_VERBOSE 0x40 +#define SSD_OPT_NICELEVEL 0x80 +#endif + +int start_stop_daemon_main(int argc, char **argv) +{ + unsigned opt; + char *signame = NULL; + char *startas = NULL; +#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY +// char *retry_arg = NULL; +// int retries = -1; + char *opt_N; +#endif +#if ENABLE_FEATURE_START_STOP_DAEMON_LONG_OPTIONS + applet_long_options = ssd_long_options; +#endif + + /* Check required one context option was given */ + opt_complementary = "K:S:?:K--S:S--K:m?p:K?xpun:S?xa"; + opt = getopt32(argc, argv, "KSbqm" +// USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:R:") + USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:") + "a:n:s:u:c:x:p:" + USE_FEATURE_START_STOP_DAEMON_FANCY(,&opt_N) +// USE_FEATURE_START_STOP_DAEMON_FANCY(,&retry_arg) + ,&startas, &cmdname, &signame, &userspec, &chuid, &execname, &pidfile); + + quiet = (opt & SSD_OPT_QUIET) + USE_FEATURE_START_STOP_DAEMON_FANCY(&& !(opt & SSD_OPT_VERBOSE)); + + if (signame) { + signal_nr = get_signum(signame); + if (signal_nr < 0) bb_show_usage(); + } + + if (!startas) + startas = execname; + +// USE_FEATURE_START_STOP_DAEMON_FANCY( +// if (retry_arg) +// retries = xatoi_u(retry_arg); +// ) + argc -= optind; + argv += optind; + + if (userspec && sscanf(userspec, "%d", &user_id) != 1) + user_id = bb_xgetpwnam(userspec); + + if (opt & SSD_CTX_STOP) { + int i = do_stop(); + return + USE_FEATURE_START_STOP_DAEMON_FANCY((opt & SSD_OPT_OKNODO) + ? 0 :) !!(i<=0); + } + + do_procinit(); + + if (found) { + if (!quiet) + printf("%s already running\n%d\n", execname, found->pid); + USE_FEATURE_START_STOP_DAEMON_FANCY(return !(opt & SSD_OPT_OKNODO);) + SKIP_FEATURE_START_STOP_DAEMON_FANCY(return EXIT_FAILURE;) + } + *--argv = startas; + if (opt & SSD_OPT_BACKGROUND) { + xdaemon(0, 0); + setsid(); + } + if (opt & SSD_OPT_MAKEPID) { + /* user wants _us_ to make the pidfile */ + FILE *pidf = xfopen(pidfile, "w"); + + pid_t pidt = getpid(); + fprintf(pidf, "%d\n", pidt); + fclose(pidf); + } + if (chuid) { + if (sscanf(chuid, "%d", &user_id) != 1) + user_id = bb_xgetpwnam(chuid); + xsetuid(user_id); + } +#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY + if (opt & SSD_OPT_NICELEVEL) { + /* Set process priority */ + int prio = getpriority(PRIO_PROCESS, 0) + xatoi_range(opt_N, INT_MIN/2, INT_MAX/2); + if (setpriority(PRIO_PROCESS, 0, prio) < 0) { + bb_perror_msg_and_die("setpriority(%d)", prio); + } + } +#endif + execv(startas, argv); + bb_perror_msg_and_die("cannot start %s", startas); +} diff --git a/debianutils/which.c b/debianutils/which.c new file mode 100644 index 000000000..a715a2664 --- /dev/null +++ b/debianutils/which.c @@ -0,0 +1,43 @@ +/* vi: set sw=4 ts=4: */ +/* + * Which implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * Copyright (C) 2006 Gabriel Somlo + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Based on which from debianutils + */ + +#include "busybox.h" + +int which_main(int argc, char **argv) +{ + int status = EXIT_SUCCESS; + char *p; + + if (argc <= 1 || argv[1][0] == '-') { + bb_show_usage(); + } + + while (--argc > 0) { + argv++; + if (strchr(*argv, '/')) { + if (execable_file(*argv)) { + puts(*argv); + continue; + } + } else { + p = find_execable(*argv); + if (p) { + puts(p); + free(p); + continue; + } + } + status = EXIT_FAILURE; + } + + fflush_stdout_and_exit(status); +} diff --git a/docs/autodocifier.pl b/docs/autodocifier.pl new file mode 100755 index 000000000..3aa838eb4 --- /dev/null +++ b/docs/autodocifier.pl @@ -0,0 +1,304 @@ +#!/usr/bin/perl -w + +use strict; +use Getopt::Long; + +# collect lines continued with a '\' into an array +sub continuation { + my $fh = shift; + my @line; + + while (<$fh>) { + my $s = $_; + $s =~ s/\\\s*$//; + #$s =~ s/#.*$//; + push @line, $s; + last unless (/\\\s*$/); + } + return @line; +} + +# regex && eval away unwanted strings from documentation +sub beautify { + my $text = shift; + for (;;) { + my $text2 = $text; + $text =~ s/SKIP_\w+\(.*?"\s*\)//sxg; + $text =~ s/USE_\w+\(\s*?(.*?)"\s*\)/$1"/sxg; + $text =~ s/USAGE_\w+\(\s*?(.*?)"\s*\)/$1"/sxg; + last if ( $text2 eq $text ); + } + $text =~ s/"\s*"//sg; + my @line = split("\n", $text); + $text = join('', + map { + s/^\s*"//; + s/"\s*$//; + s/%/%%/g; + s/\$/\\\$/g; + eval qq[ sprintf(qq{$_}) ] + } @line + ); + return $text; +} + +# generate POD for an applet +sub pod_for_usage { + my $name = shift; + my $usage = shift; + + # Sigh. Fixup the known odd-name applets. + $name =~ s/dpkg_deb/dpkg-deb/g; + $name =~ s/fsck_minix/fsck.minix/g; + $name =~ s/mkfs_minix/mkfs.minix/g; + $name =~ s/run_parts/run-parts/g; + $name =~ s/start_stop_daemon/start-stop-daemon/g; + + # make options bold + my $trivial = $usage->{trivial}; + if (!defined $usage->{trivial}) { + $trivial = ""; + } else { + $trivial =~ s/(?/sxg; + } + my @f0 = + map { $_ !~ /^\s/ && s/(?/g; $_ } + split("\n", (defined $usage->{full} ? $usage->{full} : "")); + + # add "\n" prior to certain lines to make indented + # lines look right + my @f1; + my $len = @f0; + for (my $i = 0; $i < $len; $i++) { + push @f1, $f0[$i]; + if (($i+1) != $len && $f0[$i] !~ /^\s/ && $f0[$i+1] =~ /^\s/) { + next if ($f0[$i] =~ /^$/); + push(@f1, "") unless ($f0[$i+1] =~ /^\s*$/s); + } + } + my $full = join("\n", @f1); + + # prepare notes if they exist + my $notes = (defined $usage->{notes}) + ? "$usage->{notes}\n\n" + : ""; + + # prepare examples if they exist + my $example = (defined $usage->{example}) + ? + "Example:\n\n" . + join ("\n", + map { "\t$_" } + split("\n", $usage->{example})) . "\n\n" + : ""; + + # Pad the name so that the applet name gets a line + # by itself in BusyBox.txt + my $spaces = 10 - length($name); + if ($spaces > 0) { + $name .= " " x $spaces; + } + + return + "=item B<$name>". + "\n\n$name $trivial\n\n". + "$full\n\n" . + "$notes" . + "$example" . + "\n\n" + ; +} + +# the keys are applet names, and +# the values will contain hashrefs of the form: +# +# { +# trivial => "...", +# full => "...", +# notes => "...", +# example => "...", +# } +my %docs; + + +# get command-line options + +my %opt; + +GetOptions( + \%opt, + "help|h", + "pod|p", + "verbose|v", +); + +if (defined $opt{help}) { + print + "$0 [OPTION]... [FILE]...\n", + "\t--help\n", + "\t--pod\n", + "\t--verbose\n", + ; + exit 1; +} + + +# collect documenation into %docs + +foreach (@ARGV) { + open(USAGE, $_) || die("$0: $_: $!"); + my $fh = *USAGE; + my ($applet, $type, @line); + while (<$fh>) { + if (/^#define (\w+)_(\w+)_usage/) { + $applet = $1; + $type = $2; + @line = continuation($fh); + my $doc = $docs{$applet} ||= { }; + my $text = join("\n", @line); + $doc->{$type} = beautify($text); + } + } +} + + +# generate structured documentation + +my $generator = \&pod_for_usage; + +my @names = sort keys %docs; +my $line = "\t[, [[, "; +for (my $i = 0; $i < $#names; $i++) { + if (length ($line.$names[$i]) >= 65) { + print "$line\n\t"; + $line = ""; + } + $line .= "$names[$i], "; +} +print $line . $names[-1]; + +print "\n\n=head1 COMMAND DESCRIPTIONS\n"; +print "\n=over 4\n\n"; + +foreach my $applet (@names) { + print $generator->($applet, $docs{$applet}); +} + +exit 0; + +__END__ + +=head1 NAME + +autodocifier.pl - generate docs for busybox based on usage.h + +=head1 SYNOPSIS + +autodocifier.pl [OPTION]... [FILE]... + +Example: + + ( cat docs/busybox_header.pod; \ + docs/autodocifier.pl usage.h; \ + cat docs/busybox_footer.pod ) > docs/busybox.pod + +=head1 DESCRIPTION + +The purpose of this script is to automagically generate +documentation for busybox using its usage.h as the original source +for content. It used to be that same content has to be duplicated +in 3 places in slightly different formats -- F, +F. This was tedious and error-prone, so it was +decided that F would contain all the text in a +machine-readable form, and scripts could be used to transform this +text into other forms if necessary. + +F is one such script. It is based on a script by +Erik Andersen which was in turn based on a +script by Mark Whitley + +=head1 OPTIONS + +=over 4 + +=item B<--help> + +This displays the help message. + +=item B<--pod> + +Generate POD (this is the default) + +=item B<--verbose> + +Be verbose (not implemented) + +=back + +=head1 FORMAT + +The following is an example of some data this script might parse. + + #define length_trivial_usage \ + "STRING" + #define length_full_usage \ + "Prints out the length of the specified STRING." + #define length_example_usage \ + "$ length Hello\n" \ + "5\n" + +Each entry is a cpp macro that defines a string. The macros are +named systematically in the form: + + $name_$type_usage + +$name is the name of the applet. $type can be "trivial", "full", "notes", +or "example". Every documentation macro must end with "_usage". + +The definition of the types is as follows: + +=over 4 + +=item B + +This should be a brief, one-line description of parameters that +the command expects. This will be displayed when B<-h> is issued to +a command. I + +=item B + +This should contain descriptions of each option. This will also +be displayed along with the trivial help if CONFIG_FEATURE_TRIVIAL_HELP +is disabled. I + +=item B + +This is documentation that is intended to go in the POD or SGML, but +not be printed when a B<-h> is given to a command. To see an example +of notes being used, see init_notes_usage in F. I + +=item B + +This should be an example of how the command is actually used. +This will not be printed when a B<-h> is given to a command -- it +will only be included in the POD or SGML documentation. I + +=back + +=head1 FILES + +F + +=head1 COPYRIGHT + +Copyright (c) 2001 John BEPPU. All rights reserved. This program is +free software; you can redistribute it and/or modify it under the same +terms as Perl itself. + +=head1 AUTHOR + +John BEPPU + +=cut + +# $Id: autodocifier.pl,v 1.26 2004/04/06 15:26:25 andersen Exp $ diff --git a/docs/busybox.net/FAQ.html b/docs/busybox.net/FAQ.html new file mode 100644 index 000000000..c751f7521 --- /dev/null +++ b/docs/busybox.net/FAQ.html @@ -0,0 +1,1108 @@ + + +

Frequently Asked Questions

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

General questions

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

Troubleshooting

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

Programming questions

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

General questions

+ +
+

+

How can I get started using BusyBox?

+

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

+

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

+

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

+

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

+

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

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

+

How do I configure busybox?

+

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

+ +

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

+ +

The most important busybox configurators are:

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

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

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

  • +
+ +

Some other configuration options are:

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

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

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

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

  • +
+ +

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

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

+
+

+

How do I build a BusyBox-based system?

+

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

+

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

+

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

+

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

+ +
+

+

Which Linux kernel versions are supported?

+

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

+
+

+

Which architectures does BusyBox run on?

+

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

+

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

+
+

+

Which C libraries are supported?

+

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

+

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

+

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

+ +
+

+

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

+

+ +

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

+ +
+

+

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

+

+ we maintain such a list on this site! +

+ +
+

+

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

+

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


+

+

I need help with BusyBox! What should I do?

+

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

+ +

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

+ +

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

+ +

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

+ +
+

+

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

+

+ +

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

+ +

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

+ + + + +

Troubleshooting

+ +
+

+

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

+

+ +

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

+ +

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

+ +

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

+ +
+

+

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

+ +

Variants of this one get asked a lot.

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +
+

+

Busybox init isn't working!

+

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

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

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

+ +

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

+ +
+

+

I can't configure busybox on my system.

+

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

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

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

+ +
+

+

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

+

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

+ +

Development

+ +

What are the goals of busybox?

+ +

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

+ +

What is the design of busybox?

+ +

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

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

+ +

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

+ + + +

The applet directories

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

libbb

+ +

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

+ +

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

+ +
+

+

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

+

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

+ +

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

+ +

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

+

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

+

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

+

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

+
+ + + +

Adding an applet to busybox

+ +

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

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

What standards does busybox adhere to?

+ +

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

+ +

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

+ +

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

+ +

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

+ +

Portability.

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

Programming tips and tricks.

+ +

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

+ +

Encrypted Passwords

+ +

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

+
+  $type$salt$encrypted_password
+
+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

Fork and vfork

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

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

+ +

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

+ +

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

+ +

Short reads and writes

+ +

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

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

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+
+

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+
+ +

Including kernel headers

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

Who are the BusyBox developers?

+ +

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

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

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

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

BusyBox: The Swiss Army Knife of Embedded Linux

+ +

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

+ +

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

+ +

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

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

Morris Dancing

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

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

+

or

+

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

+ +

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

+ +

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

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

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

+ +

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

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

Download

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

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

+ + + diff --git a/docs/busybox.net/footer.html b/docs/busybox.net/footer.html new file mode 100644 index 000000000..5f2335a2b --- /dev/null +++ b/docs/busybox.net/footer.html @@ -0,0 +1,47 @@ + + + + + + + +
+ + + + + + + + + + + +
+ + Copyright © 1999-2005 Erik Andersen +
+ Mail all comments, insults, suggestions and bribes to +
+ Denis Vlasenko vda.linux@googlemail.com
+
+
+ This site created with the vi editor + + This site is kindly hosted by OSL +
+ + + + diff --git a/docs/busybox.net/header.html b/docs/busybox.net/header.html new file mode 100644 index 000000000..081f18c7f --- /dev/null +++ b/docs/busybox.net/header.html @@ -0,0 +1,96 @@ + + + + + + BusyBox + + + + + + + + + + + + + + + + + + + + + +R&>bG zN3ek*3FgYv_}uvT^)0Qr@dpp4j6SmM#h(xIo>9y87Z(?gY8g4JW!5O0S?&bM6N0b` zhgzK4zT(-Z4-@3!=pECbM5vN2%%@|zjd^Lz2cLe>ZOoh9PQUc$KIj7H@5@R!!wPJB z&6XoW7;C0Xx@*oHY*W!YFNOh{m^6{+c=FU^Z(@W3MUfJ>!Z0nrOr8B%M)^6A;Xx@w zXav>HAR}6CO?Q?iNHsyL5IKH7Nea^V_=;TWGd@0L@R1`!Zf)B(QtfkZWrL0%{TaJcW$kMdd1t)`BvgBk-n<5w3)CN7**Pj974w(aLk&`hhC7)QB}nl)-9^5m>pqugz{iQ`^8z}G{e!MJ_ImHhC)Zl+vNvy-I2ym= z2Chc8286VyXN`<(vEs7MoH>&e6tsKd;N1=^E)F6|Z?-F{Mso-Tngk^n^UDr8uqf9C z9HecgM@4j%l?F)z`I z?-=vJ>#x7w^@IDncH8*UZ-2kqBG%siOv1#8qlXR|+BkG#Qp3QfP4lu)y0?ELe6EG#U}_utol9~|0@{&z=_OO}A5&8S&ZZAP|WNy$+a z2bprtfB`+5YCnE#nxx0WZ;n|A=Lpa9Cd$nRUAgY>+I8de@AW@5ckTwitj5NPsMkY= z$cYbGISo7`%s(N)zqzQWTsZCa?zH%pR2+7v@%3`=+GgBLy)!CIEhD@MG`-7AWyaLb zFkM2Cfl{EgsJ7Fjd#aD7QDRGi{3I+RQ$98>R)X)%?z_Yi*!kUX5fJFdy9wsVmX}c9$(-#8-5!xGEyleRjfbP)l zu(n7VO-s|pKrA*Lld}p{x+~D$pgoOua;o6#1lf}ai_js|tXj2c#fnXydpPH`jf?Zk zf;GG0my_e-;_sgoJHOA*9-p`r5SP(hWZbV-;2wc5_A(y4sSNk-wkM`+8af+wY@Qp52Ciww`$EA$C; zs;w<5a`D`>X~p(ayH;#kv1e7P=cZJIwo8^@){PrC+|GoB`6q-XBxKq4f8Z`uo41P5 z9PYQ#WNFjVf_DDGGLp|HDNY}!OSmf+vu@1K)% z1A08;ecg1V~$x$(DsELOz2P*Lpn$_9HEc^6TNMJ&oF5}h+6;<<_YR3saa z=Hx`a63j)4WLlYmSR0^S(wz8hSOI*%^vI!_6GJ-?Suvg;o>0MCMSMIev{O-05u$a| zvGcoD2*wq162~fTSGw>-jj^dNIc_&##r)5l3Cl?c_4l`(-{-}<5ZiANW3gO7(`MO) z(s=9mH*Q&c9d zC7(q_OM_)vdeo$)=pce?YnAZ{1Mu={qG9M zd$nS-tk_+}c4=vHZbfP9`lCm)=b-C$=YZ0E;-dih{PWL)9~=)4_jfgs+_-kgMDNhF zK)PmnM06)I_^Nf#23?ZjyuAZVTUMrhKu)@AP2~f%*D0TavRcXxQ7xE-BA12{tO!y8 z46KlMZSma-pcr3Mv*NJ@kD)O0lNL6u+Qg4rShWclUD|QUIm5SUpUH9a3&jfaHwVb_ zW+X|Mtkk;#QbLQ5_vxXi1)FGVMtVpk=YH$XDM-lg;me-2p`> zk*lmgA9aV~p`k<)jCX1ZsBPk`3@NhqU=d_>ZPSWfE9h?aARlf*b;ePGdc&&N4W4m+ zH*z4uB(Ow0Lycju6y?pKMG5{1Sws44y9@tkq)eYWg$V_f?Hx%vaGQYrirO(qlgDUn*bcI= zFrdpE0%Y#UAWzeZwUg3epp%KDle8&{o)1rFmOpSXG{Gfx#ft5#U)a7~SbA=HpK}rv zX^u?%T>L!!+RwCGGMhPIU~<@$8FXH znSPeG&O*CwBWDHK44C!c3jq22My_-DAmOr{Gdw8QBq_m6q%5aYI|1{c^^Nfvp9SIvZEqM zXwU?*u=T=JAauXZ`Q+668Gn9$9P@++Cw9VWQ-!0PG?_#| ziInVaZ`uC?E(9n+#^+n-k5_#Ru#=>W(-cXvs}+KSvmblI#~JR^VBunQN+GP$&6n7kmv$zj$*-b1a~xbZs_8QJ^F3Ihw%%6ZmyzJyzV z=?q0g_IQW!WoZtBcYDhaLP`uo$E@hhA)|xLfXd`k5eBRC^2+#rLBdSagy^J526BrR z56c5;LtVz6w>Y(S?e^^!yUw4V+rNMR)L7=mJ)2hX6b)N+FD{;Ow|SS?iE)&d|re_mACfBuYHGvZR?Y;B=Oo+W6T3d`uH z=65cPYkyYiP^s5ptTp~&L3{!O7rRvW#3`zu)^_w`s2pVb!MYn?*D~#YtKMvB``p3L@Def#R|7U!>w1;{J?=P$n8f5!az zW9MUCxpE~oHa3-~2+H0@mcNTXyJ>BOGCA^GHbiz9+6Q#8&Ry7RcxTj- zHSYYpQx=-7Q)RV4iNIKjA5BooDbWK;zxd@d-_@f$`Q^}jfmhBcVZJYBQ1!PN!Zcddv; z=eJ^R?3J;x^RLXEJ6~4+u^i{f;^|naqD(ajMI@V}Xj}T3Cz+d{@i$L8(~twCp}U*e z1i830d_3dh10UT5ko-{S>uVU>M~^Psn!S~8rsJX_$2$xjZ(Z0^I1ZR~eBV>gX#7-! zZxk1~JD@PosjvyHpC~e4+Lio#DX$b5mC0c^b7ZaRnbTBTgXSHunlISLTZXuk0H;e{5{rZNE5gfWM2`)D*krJg&%P}-e9h=d}7NsOK97#QLZS-9L`Ozp?a(c<^Y*@(w+1YcZ+5jVm zyac)1VLU>2*wz8p2egzup=hUl%1*RK zvP^R~GLuwBTs7Tn9o2~dWxItT9z&GgyMrw4A-eOY7VwkdPJ!JnDDl{N+>XsXcIApd z+;CYw#bT`bFbhkWG5Vd!k<$cSxSP%~g-ezj-vnyuhMth-CkVYZTW(xN^P>eCZ2R2$ z=*T-iALJ8Dq)A*;yatjyntfCmAeI`e(&G%u{6b1{D}!6r8*QE|uJR8LmO_vS?Iz9= ztkGjj;g7E_wBP_ z9G^P1dco>b7DRZA5U*@Hw&%(*smq%-*~iNAvs7<^hZy;p45CR&FNp8%Mq7=ey1BWz z4Ly@YfC&j<3ETn+eMT$)e>U`yi=iKviu|RSUFkXoT#bam!H*DAtYrg@)+`!MV@KYaWp&rRcTA1H3cf0H+ z@99R7v9uhTWI5E$5+G?Hladk=n*9^}`B4`MgDJ`c+iU$lK62oTpAYgLfLxJFgT)Wix|_Ipf78s zMX@kMs7}HIFz!Utk9VB=#D{Fct@FKy?wfmR^=icP>a`FgO!h92gaCnW?lH>m3LwU^ zO51N(wSk4kepBoQ``h5Cec+GZ?N~fTlGHxz!k!^5JS`Vrb~^((CN(7ZLxlWfflwgi z2Jh>m3)of28Vef-I&aIXRod3DB$yvs#DEnzam?v@R_z1;|2nH&c6#efxP0Bv5wS z_}if^JqrT`Bm*2EP07w$bJlF5)1W!6nm6(LxL;+hn#iD_%VXzS5F&2!Hf>OZ60gwU z03=Gwrd5>T?b|qP07*PMc5D@Zg4=C-`#4KKD3SCw+}=Qvu!?+R@)<@sLYxb6jog?Z zA90~>p$X0LMIf2LQ#3~(Sa}yfKDd?An?oOzPj64r%=dOQp8`0;7ucjx2AQ+iCLIw+_NZrouv zuDipM7SD$Q*B-hmZ>rNE$|A|wD-3F^+rWs+s;^wbF!pvFB6|ZD@*^qw z`Po~ZQ49UG8**flr5jH#QyW-JoqTXE1gpH~d$%aIh}7r~4a5yudtJyprb-T*=RS;B(H z`L&z-S+?WiC+mz0Hwe%8hY@5FA0+V&0F6T%&ol_bP+lAO!E?)J0NvKJE za)c@_`BG4<8PnWGkf{(&Qd7P|&`<>P_SNStEVi%RehMlCK~5kncAcLaJAWVFGb>?D zDbg^SNN(U0``AtNKC386_{JOdw=H@4V0*hiPa%{|1uXv@zRwP=U_#EBB-x~C2y37h zS13a%$>zaDNSIk!4M#?QG3YLUyl)AoKe(--AnC~w;TJ@Vt*sXTvIWX)g(SDK;0)V3 zb^MU`Ha_$E#;)DEjd|(4gbTw0p*1GHLb`G5Ldvl$Z9F>_A&Z!%R(V1wypOr%zAIu9 z*Ir${di$x>+u;>Y%{?!2JiliTf;o2XzAO7QjxdZHR@vW%{D@Qdarc0Cc!enA;uzyH zzvVQ%-4iT{k~e>s9F8oq+!`Bsb~R7RY(U-imrE#G?K1D?#>On$s?lHk{C73nbI(dv z8=#y6MUo^HX|w*qvh`{zd0|;g%gFW1)=O|Bx`$28AH4my&wucG*AKdl8MCEjYER{R ztjUlyrch8qnS2j_p%_b=%qAyvE3vy4EV#N_xf_e^78Wo&=g+HzxJO;~#1i2KkYuff z?hwt;Bkp6zvS_1`0ZArAyn_Dvc53V@KhF&tZhLzA;UcHL5hI!!a?HY$hQ@}Z#-xUc zNfVQB4NLGhk@T+hZpgB=opG&UMjCu2=Ev@UNJLKo{%cq#&I9|?27AvF{T)4UY{41vxEP%;g zO`{Evr_^|xY4QA?xmV`SB|@^K6zv()BF7CI2$CdOx$(4pgPc#@?&9ZpJI*gFF3$6| z+EwuMZ+F4fMP_Fp$bD>S6LZ}7PR5~0Lno5t#EI}e&}l+K5vomtZGYIv(Z}z`nWlr5 zD3aI3k|RfoERP;Nx_%i-_J!6(DBdm5BQ!ZoiL%w&;=R|qjp;V#(`k?8w)T|iSsL*P zN0DU{y1_zL^R)6%bIaIc=u`Or>7czqDzxA@_S6+R?)eC1K%5JJsi~>+Nm9C&D-7aT z2J!|e+V;XRmcH9hc4D}xmBn*-Jlj{buTt5OB>nk*ZfO!5a@3OZ3x_sPkSIHlWT+IJ zqELWLuua`Fqha*Q-~VGy|8TuiEE%#`eIgY_nSFHG`eiM+N#$;pVuRSG`(bPsZk=`H zv%hwG=_N#V%u%1fv@*^rtm(wHk1>HB3k!P&2Bww!w{M!ODRHg%Y$|b=#oS$lczzdR zdhYovSLPGl*wok!UlpxvS6MVyTnQIQEbwtKFP9{t5#IA!IYKhTiB z^;R+Jww!RmPh2^QYU8sXft@R{ec{5<^+%~kV8o&4*5p1N3`bnn%1heIaV;sdUI3;ddf*Ino;HT1@pSw$Pqo@xDIKossq8^1Lx1`&( zeQ71h*xMTz@={nN8?v@|iZVSnwDU{~sWa`IE%5+;#}{FE6hx_xIe8YTJ0=u7K<%KC*aJu{+Hq zJ4Iy3_4`q?$q?4ZC%Ls{{nqtIr_NeGY$`w&1{Th0dFRXL|GMR5%KFy8K%P`=?Nrf3 zVO9i`2kknwK>mj@rxxI{Yu7Oe=efLSEene@+LmL|ys*Z^rfPcJlM1VSMHn7aA7R51 z9fxWImT_@$yyHTMdv4G;BFe@7NX0>x%n9>1H}y8;7-o(a{LO`6=x$tCX#{6cZIO#7 zI0EF|2<>}Dt>OPRvDkeHw{C3q?=L=jG<*Mg=En;yFp@2~rSL$gKCr(a{^g9m=fXSn~vHF7`X2qiK-@_$4LV}6w9^s%R5wP9a!N~b+94ZPKm^H>vk(ss_~&NKuIms_1?FNB!z{<3(%C}7;D~*m_d8C{ zuw7eS)2gbjN`I1N*-p}3kDvGd=iABDCs`X4gM$DtHT~Cr`;Y&~3yId(zxecVyPVy4 z^vNbAh3%>UySBR)Ht(!<)2wO1 zTq|B$|Ki|#Vg5`)M0WV-_^^T?cnu)?D`?E{&UOopT!9E?sk_doq*@eHod<97AYZ~y22 z_^q_xyz^j}3h+Ug)xLRi_ml{-;1?FG=qa9FoJXY#PF{Q_HR+SDcCFBy=m60Vf_RBV z1wByTvf{6p_K_dH!h>&R{gQ`o1>S2QO~|&#>LG$}fiz!m^Kiy16+8=thLejSLj0rW z-gy>B4;KLA()Fc7am*!qLF2MXQZm>+>M(Or%xq==B+%@P>^*G{GlQ~k*fAQB-~Q&e zzabI-?tv5r-eG6m16pPvS)#WH5|(lb#t=5a5SsCkWO;JA(grLxz)Yth!A>__vO?kY zbU*IMLSmAy8`pHMytm|o>591I(70h&H%;@EXX>V>GC*d)3L@X&Tb`!n0ce4X0HZ9! z=cLo;k>eiJz63~>|G9o$>J2xRFIOZaY|xetauj?A=1I$jHkK#u%z)DQtcvw+|C(S1qTQ?5Y!w8^fgICoxIefJHTW2 z6pJ?V71zDE{NiCHnaibzBj0k@^VgmKcE~Fer2@v)AcqP?$~fah#M&Gb2a25xNI6>E z%uHrRK6*BP3;x5iMtUHZ8yHZ&4FLonrP4y>c)BZz2Qcw8G}YlD9ZyS-N{`_2tU_QN z@B&0km-yJo4V2P0z5~Swc@6Z{)pVMr!jht-tQ{8+xynjYU}iPNPs^iz1dG8&Hu-3U zLPb)$s+65?SIEe`K~}o?Xw>BvyJ(MqoEc@+<~kmShhHw;Sb9y#!yB|X@Ps-bWQW3C z90DuPcFxZB9zKJM2Pf?1%XbDOA1hJIgVzTx_k+`2RjmReZz|n*fu|7i(c0;n_&w9jgdH|`7H;&J>q%?d(1Ji5YAv-qOD(joFJb%}xDiz^;b0S-?d z7*^Vae0#nCAfr)`(O{jsQgLnDZW`mg0rEeO9*zl=(2gp`*`RqL8A(4!Bs)!QQJNOFhqVLZ zM&ioOc3nTMr4=D%8@>|rAh3MkraE!iLh2I9Y(qKaulw)kMK|UJNxYxW7aD=xOda>2 zKLF>mF&Z0I8*Z>Dd*e$XB=B6i@g>t3h!9sYbpbI$9P77Na6X#91&{l|uV4O}jFhAz z(F1O~G}ZV}C;~lvSFCqQKn$O2ryE_aoa#En2@D)ar>7y%_%uK+0#xWo+g92z6lSSl z|8vP!IKpexO^dbcLc1{EZZjt_&#G&-%wBPWoGi#Y-X)pOXY=#(u-ldPycbMV4db9U zK=wmNE+DSC4kvO)iS$yKmz3(m3Zw0nVumgd>tQQQ1g%`!pL#^4_)-Nf^cY`T6op(| zg)eL}kr#^j?uFWXR9ZunGBxFF07Ixp=*WRn(6Tg8vZo>3(_(1^vhkLSjw` zAs2+{vVcmHMgRaH07*naR40rDSCfncJD<4UY!8?xl#CMLL_VB- z_N+4h%CQ{7SnQ2x_g@(s3kzU%bz_hM5ynRHYC9|BB!DE6(3a%m?Uj#SSS4!1T?LYI zw`optp=sV#FA8NQyhDqqNic8ZF?GYtg7On1OJgImP*!Z z9DyWsWl~ed8$M&82c~ITLDh5{%%C+I%mHN^k_>OSo|LuQ1p{Wg4IJm;Z#;YW@ZpR3 z351p9#6fS2jWF_%O_FqdmM&~I_wK3$Wqs!?v$Hdlp(}aPImr;om9N_I5>3n6+NrSZ z0g$RGhfx8#s~!rS6&wVKf}*O=N(yZ!gBXzsp_9&~jHGx!3?RR>E)O0-;fYmdI7r(PO7L!6 zD*UBdSUxzt#eyJX=?zuuU)_+eO7v=KC8j4K!nSW)0%n?w1eOi2YFQ9LhS~h4X$FpC z>#lNgG*==RmSFLw;WZE)ayY3V-jAD=7tYnd#6YCmP><|-e#Ru)e1q8^BhiDkKRnmw z7$JVbj4o@5OE>D9JF;@FuIe|8vUm7`_>h4Pvyv;9_8w;ABDC-Z*H%f(HL3KKZm9tK zmIyXGPHMZgo3&GF%s4rDYmEQ_NL8?2g$YWFVJ0i3N6$}7?+FPxH9Y08hE?VJb8Et? zo_B#7kknmp5r={;*m)qI$&_y3U8K?ARl2-rm-ZP5^7A~*HBg)djQFb_~ONj z^2GLVYuo5W!wP}i65mHjwQ%xKx&@#y1Tsih7bO}p4I~3I@T!2ZQ3WHjBJR5BO5aaG<3xx?H!@FNR$#i>^$aBU z_W@+J)lBuG&*ClK+OV)Nrc#oE#6aWV<&&Ui}$?EOJRrowY<(ZI-wtt&XSAaVYa%2A?ykOP{nny0rJywFeLx zpPr^wlh`Un7H`vW;bILy)@aCqCmyuOQf#rRWa8A6OlSeluz0}aEbX#I&rxLiyh|v# z+jPpBk|rM~;AUO|sVnVBxz_pKgIEVZu+;>RjAgO~MmPm_moi&H?^5;PSK9D`=$C_* zFJ82_hljUYsa`Ci!g+TYK#pCxen=z*N<8{O^{Yb{Khb%QvIF=>t^#1SqBjtvU~4LRlD zK^1updICp?v1GsrrXJA9nBs_=;T1O$2f7HVImAF)?Mk&KtKx1~YBnI_EO0d1GMk@h zSMLnRwvFC+KX1J?10b)DBC;78Vit%VWw^73k26f{eFLO#0 zmP!{HTiD;zT<$boD^a|2HGl{tZ>`3=(tih^$6?~TJ4GDfO;(`-ovy^yv?mSAIiC_T zo=+)T!+r|7L5!$%f(ZISH8M#7Wf{ou2aswuUAKV`Izk0kTAjlLTDj0<@CZWkx*)D) zFJ9t>U?SiIlOIE{IGLlt zggY>hyZkbD8Wyvw>zty^Ejfh{K0wKc#w(aj7haL{Ln6H4 z#jzD2xgXt6?3cHBc}wpYIR+iMFtl_y1gXBENV%kTAaV^NbtCm8p&U|Q0LjkaxZ%U~ zyE%|!5~9nlPu8E~%;7i^uGC0OiZ()M_%x7bYYK*%5e>OmBA}LR&~~bLZZO?*ZaIp7 zM3OBjBH8$!j4I#}MX~)DFPmIm>t)yzFaGNG#LcYnm4YTSt$|mj)8 zb@3^;h((*R;XBRkRIh-%2Ot?Fa%1tAa2~Hq*?~)UIlg^ed>;j6CNrdF9x|D;y{A!o zdLTy})#Q*Ar<_vPW_nxluwY~wD8^U2PMTqJSDH^s%Dika!&9x=GOFe9z8`vyw9@c< zHV7V=31My^gobkTp$-EpF1Cdg1bX3??bsH+Oqz7gUByU`tZaH^x7nZ_7jXwQn}sr$ zInB=Ic|`^x-QSNkx3^o{sa7vqsCfDz+6&B%vqh5e;SF$;DO}kn012n_Lq_h9k3$_g zlbO!mc;3zpkdOl--$PjWu=7 z4ft>b;oJo5>8T=KX(+(xFG}u~qcoDi1)K;Y1HfqdcG88tQ9zpD9zZL&Wqj3W;UP02 zT;^0L%#w#~BU+K0p#f$y*4l0v+r0wv7k|0%_JZm(1&$nCs@IDyKru79h%Lz9NUj0cm^kV zHRQQ-?xd$}>OyYLhxNwaiQ9-`@Q3+(fE$iLTxjYkGy}#kZcBEXw_3C|EtmUd37uOm z1I(t^YAS=n>k0U18d79?5Xr@{LtY=E>W7=sAPE##c2-uTRb_~9 zsw~is0&?VR{b3vADnY>FY1Ujg@QV-r*K?MmEA7g+cuxqz7BGoLtJPIi?Z?yh@m=1% z>DVCS@bGZjw5#VPqCks@A3{$}3)}@!z$y1~#^o+Llkp70YWhHS%m#|431+sGbVExv zn;<0D-8D_{(I^;|XghBhg}iu?mHl}D*`BEKUYyi+FIJ<4=i|jOUTpJ;N{)w_vz<+1 zd80mgdq?2By)(oI%dwN0OY4ckxl~=4Xvne2k*rMHjVb1KO46-dT8*oe7+3}jm?a~2 zCJnIeHLokV+zwX*9`1`T-N*mL12_wpggv@e7a$TZ)8u2KISX0z|t7 z^$0X+wJFW5Mw6}gUQ;Z1-mOH93Q)}F4F)%}2oY!7&FV0XO{|ANzIE;2FTB0<_S;ft zpvhU;Tp^T2fspGiR*D&XK_oLflRG1mBYPjsPDrM!ltx%`p%uj*cWGDRGQ$d;7)*GG z{sJwRmaCh)EUB_SVEVotuT4+$&eybUSHpxZOb`^Kjo5|deqGaZB`Al0DIT3$%_#ZR zh7`7f05%DRiMgM^a%H^~r-;R}un#iNf}*C7B$zk`-r&cTLbTjY!TpJ~?)*`AEB!y` zudckk@X6cZ%~dyUY;NvsuB>p}W@YDgQ4r-G7T%7UsZXkIi`fR#iKPt($cbwx5g#HJ z0a9$vs!$B_2gy85$9ZW3yi1W;--f+4g_YC1rc~u}EI3bHN3?3dI51%4%N7di6FN+I z1D@p+hZV{bj2`JMS=9+iN8(@b8lLGI5>{&k3A#0pl~-3)U=1n77RX8?+AJI5N2Zp0 z$;h|<&%arKaDVcpR1smhR{-P(XKce?DT-j%WuFb8oTMS&89DoKHao>u<*Duz<2~uS zEztpFZIGFXk_^59(ts?}5$asb4NCRO^v#&>!%pymFTv45oNW=D+v62B|PEzWyO!7)jvRyEmsbrgcHP~HYEFl8#g|vlaVW(lTD7^(DDrJNMs|vIZXH2-iP}IOPXj^9K!Zxfvv=- zk85LKhe1`4)nX4%P?Ay0T1{?t)|0&CCYiKtU!oJ6b8e0en*z~|3#g{1vwcTrC_{Ei zpbhk(D45!5NZGJ~2nIBT&mLseZXfX{T`kF(Sm{KQ#uhFA`h>~difmuqHd3jX9);Q$ z=Rf}2w?Fv=P>O8B&}?pYIuL9!vc99DZ8@3A+nJS}OM4$a%$lrBsSKh-E_}<>js%R1 z3E7nlhe&DzY5Fd&i_(_Q@n74YJ_m|{371+0eF2P%a=$eo$w^^;0*a2*69UgtXu8=d z{#+H>(aTCpq{o~iBs%MbxgcgUpdX1M9~V$oo;{ndJmZoTBAGH$TfK@9U!VW=QUW3=ILIJY-+h*;likq!UUPq?Grtwh|%B`c?Qxmc1bsgq@-m z9iQ682r#)AGksnQn>6v?3|qpsMrPIGu;jLN(Tf5Z2e3?<@#mHnu%kk{_RJ)Fn~RQ8 zc0Xz=Wszqcn<;Y0be^}tHpG5XQ;__`ELM#$zXdfSXqO3KPPu0PW5Cu@xZ_OhdQwWD;V|_qEHaI= zN5z=sK+T1rW>~Lg>`wxlb z$O$C+y&;9A6mH;=e^n?UE(Sy7Ci_x}Ar1~3@*G=mGxgPoBma7va;Ax^zH7G)mKUos+X85b_di z4ohCwLrO~RVKQ?v*mfK^9l#INv1_I2k~E64V-#K?YzEK@58F`eX$rT^C=VeEu(Zw4 zUMJNt>3+js;b%)4e55;t_pa%*-`v{ca%RR=hC?Kme^K;n(oG5-mVPe*0@(eN!&qlS}!iRwfQzJ6K1}hPYr>AY-go%d3Xs0Rv(ya3uWABZLycSCD>WrT%HqcvM@ixIbBL@NM0UrCIQ zM?l4czJr6suLIHYpCBcHp@qufo# z+U)yu(v}G<<2IWaC9i8UQvs!Dv+^)a>BSGH#e?JH z<(dAT%+!4I`R{#WIGuw?DzScY^vjdX(WSGK%t|Iykk`W%ER&O+ucFoTMkxmyJB?>L zZnBFEZ~b(Qg@!bPP;;SsVjLubb-mUa=9^I=ANlGmv-^OXahJN9|J=Q^!Eg!oX@Th z$+=_v8hPd2r6biHSs$uPQSrJcN$SZj5@s4ml2&44034khhR}Xs63!azIlcst#EUS- zi8%0*Vu9_S+vo6pY_e4pfn%vj1yA%MFOSZ%P23b9x!}yzn9L1=mdDsd=7y6t#M^Xb z8xl@aZLYui z&6r<<=0oVBCrMcR39yk#Lo=Y_w4?%PR;eUmg?K57kkC^bq+qIjpFk2oWpg!AnDM}Z zD~?}**HV1}t)$5~$8{AU+htiVb^r0>$M^3af4UgizTB&#^}piui*TPjNkl(ir(iRe z#?cT_lWe^hl1`SRr_GH=>8j}kwrUJb z1|(c3jwbwe4Wu*Ec1$p$g%KV33XiOQVP)Ky--7pPN@Sw}#CcetMgu`1q&oU||Nebo z2ox6&;3<9&=K243mamL0jE$ZgJbt>qzRq}?KxSmDfzIS4_IX#T*X(kt!cW&#w+WvTU(4YAzQVYGGs}BVN>s%#(|uho>0`C3VDon%5||x67%- zk>VwX60O(aW^kNpqs>HhGMyG(>1uJ_c0vBCkKm>Ykwjj%6WgLZ*+5PXtT3^u-=hz zhGLQR?(4tX4K#V_i)hOdcW0U@SzBUl$F3!lH95Xt6N_C_pY$|iKB?~mzp4j0ud+aJ zG4aNXh4e$2%tE+&QaL}m=D$7?$>=g1i$CqMY6v$Gb~%!YMD8Eo-#Y$>-c?q6bnY%6 zk8%*cGq>KkKvFKoIGhw1Z`X&8zN(y4?FEwfk*F?U9Ig9Au@>T&^kpoD$fj#G=5~WB z6eEmGqhTtWp!Q)eue0V*zpN z-k&CjJnw$C1?dKo#RD~FTofkC=*@e4!2g-GSt#&S{m+v!z8wd2p-AJI1eBERxQI1l zao=HoPh$cvRaT^{3Q2%SAhQL}B89<7UjjtF*Iwr?h+;nb>e;jX{kD-ZROol_+7IU` z{vqexep(zz_uP|%^(PST`jCu_$V~)8+@OD!aP72{W`$2iHONd_6+oHPg^eVnUsGwE zID;H^st`h16D1QJo~bk>39V(N12W80K#)aEYPvGszWC;qXD?nnYq$459gHj<^c~Oq zDOr}UIam9^$+3lcv2!qZaMURhJ@#O*J?*`r_v#~i361d?OAAsC5KC-o%;exXttACi zfFf&|X(2eq5rC1zvPC3my;UEx$27Ri7wMr{_Jx>9QIwM6X8CWvGCN=1-+%mg@zcJa z^|Y}69iFS79RtZRaPr__or@S0luAQhe-EO~#A!HN5E@Pa*GNioQr7iwR3-}p)860z6p>5n&R_Rpo!R$w z?%pGkheMl32M1jJ6OJY-HCdlL`@9gkfy_+s48Zkj_U-T2o{tt_gpUU1z!phdk_$kg2!ItXrn5Llpn_<$wD79~zr) zcmCJC#l9;=AbHa1Y^sH)x9R$@XA(N{X`3-0a}_}#+x@FB-c{ap%H?hoVpYU4!{KR^ z)qCz*XE&+qsdiCac6+EIwwvV4wMOq;XA#iN8-~HnGDg9OMiR$~#8zti&ef~$|7ly7 z-*eu*wtU<-R~$Or++5jIZEEcElX8O;WE(BFV3At1+^i-NGS2x{2^n6R5)#@%9Zt@^*dY>}j+~ZFf%}~A26%fqw{|M&kp8BM>mU*=@7)*$}O*-49*%xv+A}~XG0}#( zrq{^2g@$kv)Z^+m?iFSOOVt`~G+WRJ>=6}@(a)=*D+{aumG^QLvh(fP&l4zc+vrqXDd6(kEHp&UB7+u%klkoA~EZ7?Mz@ z>2MXd%VhA2%%&j42VNp=y z+FJzDlHY+=z;6T2V(jwWtN-DL{!jgG&-?vbi=(5Rxsy&NBZXPevIroJNOZgs%}a8D zX)ompx;i`c;(pxLZIhWaKc;itktu~61*DtTl1YONCAe0}DTGO0v6Rx=2s4J2J6m&{*Wq$g8< zVenU(O?>|J;9zhua%bkJ?I`|%=lOEqXy@zKL=V)+m?`-63@N>gp3Q;qquOy#Wv5HFN7zRg!662k| zxhs8JKmN>Yug|sPzEK$Nk-bZo&i2M(KsaAuN~ejNhuzLfCa01ojP$n;4(9sqy^)L# zmr4Kt0X0cPK~!GOH=UW|(Yd3O&feMCrM(XmMxMRhS=TF2FU}jBgl@yOjI&C>yyEs} zf3_{Z|GYcXcjf4)vo~_Kx0k3G`K9AA^Y7clw|I{LdhM^SOGjZ*;ShIXQb;Zjq7m^FXm2O-1jgQg{Bg|JuiYM)40k z(333Db_SzOxnj8-joyEJKec`5`Nx0rL;rvKk)4?V zpZELWPmUf%QrnTOt!3!M?|1TN a_Icc5TG{{```OfpcoM*I`UNnQ~2le&dp2MMO0$kY-1Thr^_h zqlbFSlYXXNKP-}n#(;wDtblWCT3LsIa#chvAk3(YesaOPl5S2iC4F;g zzL$4{h4tREj);u*YdtH|!N;a@Q~37yi*?DQc2k#fjjMHA(b@TZZnd_a#$z-bo^nxp zR7Hi_@>>j8p`4O##I00(qQO+^RN0R#mJ9O5X9jsO5407*naRCt`t znhQYF_qqNt#alI6inR^`m0J^>K>dq&8ey2F9UYU(<;dEThH*%sUFDAk17bN81#ya6 zJj|3)LZ*vUg@Hi>#LKjhDR`OCLc~#~nTSk+4WyWyluOU^{(`l4X}7lLP4Y{kwS0Kq z_kG^?8*p;^&SUQE_YS|bc++?Pg}z-pwrSHo#Om7GbXNG{&;H%q9|q~u1n(W*w6xZ_ zw${VLBb|kdhp~J5Xa0+qR?q6*_udag_ZSkpZ(ps)QjeNjw`x3U{A*aOnwsovqx1CH zpJALnebyt7&)WCc_lh_B!^4|ad(?WYu1T-)@L<(;*Lu|S*KA*1Q&Y1Slt$<7mp=8q zEUtB)HEa5;?&*&#T>L#G9@-s_Uc7qsYUDIK+dsXAkuIG`SJ&*#-pj=58Y64E$Nhiw z($ZPe5lKnYFU*?$_~Rdbw|JY-6&|%vY9u=ylr{c%u+nS%{cDhD52%<88EaTIEF;Ug z`~E-O!O^>FK7Ivlci=~9oy;EMLoPR}OY07+cwHH?su6rdEh z=;Ci=bx;5Q_x=yF7dtzHa1mi%xNxDA6#dn|ChKz_d++e!O^bDm7N9_fU~N@t~0zA5eWw#J`Ti^r$hdsD4*l7sS-fS^ei z9{)C;H20$qpFRBg(xrc1dgRDRb~Y5j#h+j(!~W^f;7#|S)JUwXwGJ?6Ty%4c>ZRv%D-vo2+hwr`j`qCv!j+|Yxcx2=V z;Ewd8Inch;5w$OJ^8KXsSgPwU7rOKWXo!fSis(q`Hh)OPFgT z9Fg?rwF@7g9NtTdsqhwWI(v4>k&(0gBO}bEe+fYnTy`{qx`EQ^&w_YVl8lNNfVCO; zL;pT-8C`S4E^IF?olr9(DI#L+pP}Zjm+t%Z*8v`0 zyzlXSAY5{GN&gZc0`Ca;`j=#9FX=zBWP;H@J0sm6nCa=!*$fKH2rd>YJ=|zCrn8_V ziZY!3#Au`fV7i?kogMAH$peg~rOrvE1bN}gg)3JgB5nZn#*JeDU3=`>eWk+ECu64K+3a-yWkt?--{+C%Yo;B)L6qL8#y(y!u^eelwmZ23uLH6Y!u-H zZA>?M;ICK=QEMPU-9@KI`x}iJ;ebFjW)P*3*yufj2sV-x z0lmAsU#Bngk_WnGx-&UtDIg&w5U(KF$BrTC*NE}W+i%|f)kjZV{SwIe|Nj3ry$-Jb zx_I#uDG4%r2_+gu*`MvAV8z-oE|9_AA@3Tq&K&I^Y{P(~A=&x`8;){?!$p&Qq-HT`Y zL3tO1D8-TNyQoU07NZ-Z86C4h7tPpBbOwTwYGj0?H_VH07GdJTBpRF zEjl9|Hxh3mGBOy(a8R*)7)Itb)hoUspKh!eOKVxsvAf%O`-&B%6qIh{yp$l5*1|P` zd2H?V>$l&06OhMl-~Qb0vG^y8!1$?p>|e4Mko_YN(Z3N7e{>HybRUEPVn#Z!GQvqV zA3(LWg=d8O_&9id!rcMM+?b$Z?VkSFW3}J|V>grpq%-6M;_B_C5f>sNXf#4dQuA@r z@%r^QZ>+uj+-D)@{$$aLk;O~?3uQ@(zPsdZHj!rgk2Ll(6$vHDr;V^k7KDU4$akC3 zcwb2$A0MJ(P<hA5(ar@rw+nIb*dowK_fz%`8sLRLSb|m}S z+Uq}m>j@{PdGi+j@u>;QMT<(|ic7PZqGXzy{CtE43|gkoKbkrl62hZd#y%rchJbAI z@o@(dglr@Im~68gDR<=FNS)6L2PAeOlp~T&oG8W8?M(Nee{f@z1SDDIv1`|^?Z1BQ z+Vv+1Y~HeE!CMx-#z0PAbm8pc#YY%(B))#=I1+t?5TpGY`x^Ub%up>-rpX|~>7H>< z$TpIYr{eUPVDuqILMHw;d|&7sKz4VtZgqFxTCw8R6>1!Z4V5OrguwSW z3Uh7Ju?R5UxDMKD9~?Tp6J(EUD=f_E-Lh~RigM9(kFzwl?>hP>8~#Yi?xP12(bO&( z84mSAs88@Z#D__CxR1LJ!`SW9&0<~v&*<(>E!AcWp@PLg>}IiUQFO1kwVhIq)=8o! zr^2M2k{Uc>?Tte>964XUWf_y?N2atE);FEr@_VPbk33Q{LL;!B65ZI}covY^(IXil z8Q5z2NK3|fLP*N+wvY@`uPwvplW?E7cKh6eBzfK2s9u?_>h8udl2v;PN^ZZ!xOpkl z=43$;Nf#m@;~~b)uirpDA`Ts&7&Si5f39YSpB zm>PY3A(Z0~($dG#%sv@DkkZE|cLLJg-9ZXUccunF*#=Zn9NC7*ICPMj-M6|)$kNgk zWa<}aTf+lJY8~$(#O5qAt+w4s1SVG7<;1cr5oasmXz=n zEXp?~57wbl9Cg+v-AKCe@{MCJKeyxKlfSuhAXdsN-!SvOL;iZowvAqHIoBe7vMBxR z#fuDLbTp)lhKG{@Vb3*Y^yMMlA?Z|xAk1(WV_R+r(UFw#@uVX-yWNQsX{L9%Oh089 z^ZG(U$VDOJ-W4p|Zr!><9@I^X8)QVcR2_Zc&dHN^?(9gG7_w3lx)QcLH0{$9amtja zsIsy{NwaERZ|px3-S3Yy_rr5&&@`f{(b#BXbPfa9))x{!QH;55Z46{C0$lO#Bqc%O z5+6^{;qJM)sFF5gULM&ys~d#QteWk$=GcUF8@%OCzpM z(U*6f-*Egh)8@q~gM*1>WeL~zuACoogj&4czrPVh2_^gb{4@L+8T0a}9?>jqdAV(r z=C(Ev(LG6RT!++4WyUm>ul0^>6TD={Y6<6RjFTe2a$Im?T z#xv_GD@zK-*fMpCn8)LlC$yix`netG>XgC6#IijJ5lccgp`r6=e{}2=P!Zc>9{@AZ z&`fgM+VIGwXLmgL(98JU0Ev%}cR)Ix@HQ6@{EL{}czi2(RqInrci^qqyZ83b?>_m$ zoo5ht)>Rf1jE!YV)jR+m>f!NDl%L;n_5IK7X-*pk-3ABC%DS%qnKqvzFmYyqgpPTX z>gbgysyvYTP-zijTMYFJL_#rAxH}&1@eHK90}_!Bw*1_p{P>XgeS`@yYx*y~{LXXl z?s(%*Z@lr$oyvl-PWITCEHhK=>njlW@^~%2ywLOI=NEpS(dmicZBdC3)80az(-@td z-OreK9tFH7FD5T9H!mc_k>1?g-MP%QjZrI?Scoq_{yrTgJ2yT*AAc5KR8%y7Q+)jV zm^|a&y%(Q+@|9=a0N=WG=Q_v6*z8QUMkCWu`0{4@3dFpQ^XEN1uYO@KNAoP(R+yOB zuxC%t%NOsW7PB3j#Sw-w1h>45n7o(}pO6>_WiFHC`1qn+hhX^=YTgfbMn~N8V`7Tt zuiuo{*tq7&cR&6+_)hMq90MQj9CnM!s%qguLa@8VLF zw`R?nRquZMm%qPpa^2;EP7a5QV53O{S>~bTzAe52Utix7CqU?_A5H^gdwb}YF*-RN z+PJMLF>%nXtit?{i+MQ5^!1%}WPIhy7y$O=#)Qm|kHHNJq7RTnw8idx24^=t=jVTl zk$DHi`Ri9b`y~4JncwVK=gMV+k2A*M)^UZxA%RY%QkhjM>j@sOqodr@bNEELXGN&K zxw(Q_EWRurDjcL9cZ+N`-@WUx$=}#_b_9&kfLs|86VsNLw|jR?ZcKdc?%iZM@w?Fu zx%re&-0_Ctns`eZy5z51^X!LD{O#`$@PNX^*2o~CiKBpu9En7$6Y9)nDKJ}7$~^%$ zJnT8_>6y^nUeVmue)`;8P=PVur3hFq`3^q1z5eV+sKCGMxSYyngN?&6ab*D<6T&je=WB)D zTCG_J$P~RESuXeN8Xg`_NN8_vZ*ESo9sdeOC#SbJZj6eG^74v|6rFhUVjhM>bb4Rk zN^~~zy&HW~1R0@SkxLOIjL!$7OHtA8TvCw$T_z}9PObm&qqjc(7&ESptr+97k!6lr ztqzd;A%=!VNB#UpN0pW=tGGw6PfLS_o}OI^UCjw}OR(GR8@@_N#PN+Aw?#!YBu2TF zrQK+3H1@&QSE6xZ@}S`S-MNf%Nav{{s!PZP+*9B>=)%N72J-N;Z@u#FuFI}6u}lL4 z977p$xE8s@+k2EBH|iG`x8TtQ-hO8UYHM`|#PjUx>H?!@MHe6wpkhUWeZ!Z1d~v@l zw{6=N)tZ>-R#s*|MwDplkPKuPDPCDbRzil7e+mMkSBhL*P8GQv%=d9Qc<|t*gDwYW zKk>w_UGE;aT*)OknaNbg5f1t7_w)1i9t+cIbwn5!=j}($Ebiz4BM8&Fx}aoxMSDAS zb$dd)-L~QFuY+`YXv(&zLYm`lW#!K`Hs+xOL*P2VE1F+4Kc>iKK2vl>`KOAIZI@H0 zPJy#%{hCW}z4h0h{`8g0$#wj5#bZpEII;j!oyo7RuFh{LHa6C~Ds@4eUqFCB06uhc zN=k~pYdE2cKr0ZQcqD+*-roCl*+x7yI5=1s6@{xC`-SHKNr-6e80r#A??hKX!&5H1 zi%y+_gzIPj^{r<<-sQ@XaXIX<*w`wq4oxvC6w2fLfM_z2e%dhH%xW1BeZ|!sC&&*$ zm(W$wg(M^46&0l820U!RU*$jdVjdPEFqS3GN!_vF&Ak{hs7hq^)XGyuxVg+n*&U=J z%s+S#9ys@-znt84U_ok~iO*4tbHJR38AJ|K(Yky_}6 z0)&dE09iEu+1YRX^)EZ(G%_|^MQ$>2EE1(QR_V(J%h`?#I{=O3=Epx;l=@549!~3c==0fZ8fN=+&qI4U&@5QO&DP=}1AVfE6kR zr7;aNA!9QjyU^VZ$chc8KmS?U7vgwuPELbYRD+w_U|GR4JMOMriDF!{9+0Olo!Ux0 z?6T^kS56)%>Ff;Zlt=-|F$@Xhau}cAC>+o`P8&BGrc*&ZAa!&w7f=!*g}x(YcsQ*K z6`9uTfP|9GRFc#*XdAoj^n)0kUTm%3;MI^wbvZbv@}%ppR<2yLX8n4Q9=ycF!T8zF z9Y{s@c1m?Zc=IR}^BX0Xj(c>$sGnJ-RwKJTVkQVGXgH-ukFkOX1!brn>4tb|Y2Zt{ zUzG`VvhnuQ?G?cfgnX*rt3DVhk8*>Sfv&qAU9+ZWJw${jUfR0#;s4lraQrD;p?Wd-lmC;V$p@(=pfOU}BboBJ_PLPh6fWzdD!;C*Bq}>BN zolu@|I^p!`)ArNt8y*Phl+)U36N$W{ya=<|^}x?h%|BJN>e3}N&C^dmed*z?@BM8@ za-B*Whx)4u8yh1B^yb5C)EexAVll6}gUS$ydSazG(UB4aHTC*o{jejt7}Bm!NttlE z{B(l7-1p4r{x2LGz8I}u%a$z*4i-fbW>i9^VE(B~t5!k9g9o=h{P3$Y9-jMeyDHg& zGk#|Ckl8!-Mr>GEnAQLTq{W(tLrOKWoHDFO9d?ju>!D){4;Of2iXNLz*F>Nep`?Sd z9Af$oJ$iEi?0&(63<$B&%S+^Cvjuy3k(P;W!|$v*xb@W2mo7cMb?fhD%$Pdk`31?v z+MzQ;C`7HFh8)4Ixu6LfPD z5?abzV!e|ULX+BT9v%I+$tdaN)#@b@ZSX?xxM2qldUETfr~hZ`!G~Xcc=n8`Q>Xs+ z%(%gdBa=BR3l_~w5t9Wnk&=QVGg+Y+>Be)9xF@AUoT7*E01}7kAoyb!rCc9W9z=FN zCn2cwMnTDD7^=5YE3xRpq}tT4pNSKY^~7m|t2Q(=HMo_{+5e-5fA{LcPyg=K88co5 zRplEEmR8tkEIP?sn3PrwC^eI7U(9d(Y9BEDq3hK!$ zsXS15ZgWX&n93~GNi0&GPAZLibTW{wt)R?l_pGotG(^@99W39fVF6lLc8p7}NvbP9q)=;V;X{BFnqV%Y%Aa!j2vQ;JK1m zAWF4asw}lZnq^gGsunzO;Giaf*whf^mD79LZclWJbc?e2Jx|35J|Vql z2DJg0Sy`Q#DwW^$VX96;dC`i1>gv#k5-_<9 z+^2{BJQUnG`Jth!@9{BWBJJXou$!9?Y~EY~x*JthVX?9BS&jiAWviuGS<*~EdQSp! z8Ra@h6e$WM%D~7-*rn%x&$y@K)Sq>vp#b&6lwI^hXlOMZm#e{vCh6$mQTXyE8k+c! z^R&*Opw6DKo0oSTsD#ZZNJWhIFlAVnL?Rh8WJ!T2HK?<)S~8{H*Cqj3SlHT-*nnCL z3=Fh+ZivK|-u(V=Kgsrw>}f%1@MWg_^r4~XhYo#D5Bav3r$>>7QqT<4cQE-zId&Fo zK5*y2ft&oB1F5FC)YR000Y#OTc95_zDHGV_GP5$p*3Qf@?+0y@Oid1=D03RzB1NJ= zTd1$+oH94J^FN*X+X>DWQ_`A|Zn!lpC52c*dEySH!{TzRhY z+|6_6&J8FO#at#h5{pEq1)vs~IP9{eYIT+xYA#={@*X`inVM{EWfBfUx7%!bUTAZm zo5=RcJ;)8s&Dd*j2a=xB%HxF+BYpIVXU*`i*mI6vW2$q#e0j$X*Gi%z5fvp0g+fA~ zut>NZj)WtWvDs2Kg)jh!Diwy8%4}76drty#Bcz0jdqv{>EwTl+L&`aG+#-2@0^*FR z&;QA{IjwnkxEfOin6J9kKJzX?d=6+U|{pE z-%g$J>i>KY$S8?Jy`zV*HljRJnuql=zUvOx0|$~_U2i74-c-~j*AZX|F)E6Y>VlGj zVu{5vE*Tq>O2)$IoTgGqRq$~GY+R+v%#^Bpt!C|+ul;1e5nDDEwkDDXHF)*zfmP<% z_UzdM%1Gb;{N0R)pSI1Ru4&fmvGNbo0y~`2oDw8cth=*o2lAT=!emt7z|nz$Vr06Y zm;I+Ff6L`sr1t=TO~IfpJP>)>c3UqP0|V#Gsrc#K|5&vrCBY6QL-ox;K^ksx<(+kxcc4X*lU*x;nG7_=I37%< z3&t>;(J#n0S0agJhM*)2NAoel)aeX5l}?3h8?t0Fbyj9)W)Ds!Rc9tqlXC|d4{9xJ zYO3$Gp>HZ|WlfkqbJ9dRpMUF+J2Bl41a! z17m@5e0*Fvt{5LLmJ|cBc$|30!gNw*X2Zr|MVXmYk(rq-Sbm2l0qIn~j2K(%6PwUF zkg_+YNfa3w85pP!jeF-tS9?XdReE$CGQ19mm6g=TuF0tk3l1*aq(HSfjV8Se=GO1LS3EV8PO6xtD#&HTpQBou(Ld%@qoL(FD4N)&P z4!yTZCGc}~-LYfc<;u&Iu9YQ~H_xFA2a=g-Ohu?r98FfF;zbqMARn+e+=(&sNnqj# zg$8E(v2fT1yoQ(>nJ-Qr0wsUu*MFqu2pWs6gNd!6Y;6E!dwZ{!C?~i+GO)9tvy-2? z08L!UFjiIqtss@jGR>5u3Pn|wf~(|HR4w2u`AV*YQ_Sac6%PG4Ts9j+5DySqBnFGr zkfp)6$;y&xG%|tASLQ1e`c8W=qf_v*jQ|89Vi4T}P7&PQYtNjtv2#Zsb-hW9fUIQd zkS6j#GUnbn#&{G}3Wusysi_N6<5Csle8-HT3!hYE6s1HnHEDrIs%b~d%GAhYGEJ66 zzI-xDF2i6fbYKn^4mLE{_cVCrh;nku_Uy?#SGjrfO%x&K@c=PW7h~`xGwlq>@z^RQ z7h?0{`1}Q_3RI(fBHfbv8MkmGY^gQVie90<5n#2*5?H~QsnKLww1P>jYEJWrvZ0lP z#G=uVxW~RnL~_C{dlZ$>&e6xvi%}263||SFx@ufWO7ejj$CtGJE?0rlkX!#f;*tVSgB$j7%p4|q?OCLa;0KisWeEGN~MIa5Gv(rwgFR+axJ7x zOVyNgAR^D!Oc^LUb+O*ONibqAwlYo$LVz|z5~E0D>lFpstCf{Esr1g3C`u^L1H@T} z-3D%hDP4@mcmZuXG_1j=jKyjTSHzAhu?XcBj*w#!8iW?1P-igUjgVFh84SouxfXYJ z_NdMb2_^%1oFEw!DvU}@#G$q!G7`IGugD%atW3U{TtWe_;ZqrM>&^|-(KcHZTT)dF z!yYd{gA{Y&hYaJWL>ucZ8P!UloDdfw2aY2Y4#9RvR=A`TE4eH))2hK->}*kan`b^4 z(aCA&#${1aNH^*+Dhg``j2y=@5p$RbSddI6NPei`)B98Rp#<3VMsnyibNIcw!O-)V{M=TJuwD7F^{U)X4iyM~}dU+*A zfwa{t%FD|w(h*)Zk*9jWqp1_7O>U^DLm;)#tg5(5NwpV?$xYAD4OfXo8wOKX8aN!G zP_5<|22#FhV4I#adaZ$4` zj(P(M&_OOapUdah!Kugj<4R;2om{0*(r5ayT4k&R&!ALVbQXjG#|0t!1%yJX%6nSs zhJ*Ef*;?B`vATmS$E8%ab#RA~YubvT`HE3D%RIhSU2 zaw<(`E-hwsQsH91VV=1r<*3q|FCWzkX((dc&=Z$~zaa&M(2{-z5HO@Ro7FfpsLjGG zfxs6dQ6SJ{wpg`V^W-3%p4wPAWe^i*&mQu~2I!bWFPb8o#i%|co7l#yl;iNnaY>kj z%LdFi9jZ+7I?NimR?d_qJO`mrLO&gj+FDwCw78+oancshER-SVYMENC(Wqrs4Nec< zLz5Xe6wccQ2PxrAm^Mv0O=xj6c^OV-;@GMZbTanZD)b7RyO^wIV)8*Qlb@v<59)+@h0E^Qh@Wi!}{qj$5Y2Dp)==snN~EqHTqPgS7VA z0NFsTO$Xej=4v4}LTXpVcr3L_?0Bq1VNr6pCI@8z$22O(mq+<=NJrQrht07lEwuB% zYJe}4a%dILk~*A7O_P#Mn4KE2RUlZtd@@tf5x?9x1lSD~I?I-k;TeuQA=AvX`<)I}FHYCIWHcP+(81SS5q68hO zNh=UMXy(kFFHU>lkD5M-*QRXT)>@yFV{h-Z2f~0N$$}#1WGM|w4u?-B8OK*z(7zVY zm{QRp90@qt0Resi^g}$_Xoc7y*$|S=HVF+pf+)Y*h)HstoE>4bgTUg*rwhtBVu#7`YQ9}g{C zc7Az9dvCj)oSa6FDAMN3$9AKP;}R4(Iekuomx#F3aX2Be4IMemFN84X1jxwd1R$MK z>=kSdgoKd?2w*<+lRcP^uVCiY^H+~gz7Rhf!CpB$UVB1sZ*NnB$k951)&+d6g^>76 z9dhVmGS#KV$xU)4D$ybzrI*|~)F?rsBiJ&vj3Z@Z2ZDy=`!Cb5N5}m^_jCD!$Ny0m4^ugmHE9A-Nw>!4$D{k?}xEi-{&6 z8&udNNXW4cJdeA3K9{a?X3&Z3OAXe2iW; ztQ|Fg$;9WzIeu@!&t$SF}WQ31vr0Me&JkzGSbGt!M&R~Jx6zq){ABkgExG>cX%#89E&V0{RX zsRLv(4x6cnfB?=IJTqWO0H2w8=+GB`@97Dg%~DiULl;O z4xo;r#LVVsEtq;b29krh2l+U_*Jx0e0cdLu8|$rrclGMky=19cWQ96tNA#WqET@O1VJ4XA`xjKDs@`3&Y;uLk$}bu)7-Eg$2nUj>+G?%cy4)U z5)hv|HuD?(>x%ax_Y|4zCJOrsjLYNZuixhEsTzE8|}m= zj0Q7_sEw6tLC?l!6TpErvgs2utANLwIdl1=fA|*D&22nh zsINeVokgS3$kynNiQ~bx zW=|U+>-++6*kT~-r~=g>l)(ylg8kE8`Cg0gOJZlR-Lu&y(gy?x0_vb8B!qb6HL?!< z`KxDM`ynO#B#zr`p3Q-tf&gq=CdLV=i+z1VADVakhgR@@T;0&yY;Wetq&V`S05w(( z{3tZ^%=h2NUlTvtVzc*pVztJqt;)2j4jnqa^9OL62gHvbsu$T+^kJga*B6caf1?b) z5c6!d<;%^>moI<+J6vi0>G!uLJe?40Ygco1rpjpI+5P{QADH*gbv}-2~~Ovpi~J$ zganW-9YT@b%jdbzd%yRdd*Anu@45ecv-h*t%$nJ=_MYE7v)5Yt>ig9Xz&%ZMusVQ@ zj0~Xh>i}F`0+^n<+gN(qSU>b}@U(wuU~ltK1FZAnY7+1iKtWD^i~J_Vty{M!DJiI^ z>1n8M-==1{Pj{F80V^lR16FqShdg5Z54l9R+1UkT1w|f9NJ~p|^2;m9NrJ?rq$U4! zLPkkRNqw7|nTCd0@)7$Z$^U1%`UIe*Ad@1SyH3UhxJFBMotEsX3&0K_16;fQ=X?BL zBD;3|2Kmif6u-2p_W;+(u3x)x{pL;b8`mlSH2G6Y`$*y@`TZxlmTq)hlCR@$(R1fi zF{tXj{5-@XDAFRK8|8;=B`?yK|OD976OL>j##;=p?`pv&T z0ogU$>o@K{l8}6&dy~uZwW?dy5Z%-Gob9U#0QL1>#|>PdM=3$Vd=jRz;+6g z?pm`CID6E^ofrr~$DZO#2aQDA?Nn*as*!P810h}Box)uwOU}0QUNy%hw@-BQ^dl?` z{CvEO^3!E{|F+n+6;5z8)y=QbE91hB7a7I6PN+PXR!QvJgG1b%2Hg__>URxBJ?Vb( zklLce?B*n#g4V@p)|mEXU->3g-Y*iI7|~zzO&IS!$y@3p`JWf~2(i2AyE`(ZXu+os z*<0^0?@U!SxN%sm{&D>)Sz^|*v$!yGPYlaTC!6-`q2UZ|c_2b_Ny4}nmb)@4zdw;< zGp_Rb3RWenVzYI!#KJc`){V!Dk)6;fzMW?{oN+M`MGoITPYnp2xFIMgQkWimBlVBJ zhVUN}^Yj1W$Xf%}Ou~55uOm8R6~XK`8#IdZo-% zzw#i;iiA`M*4ocflw{9sNU)GvIgk&1Z-Ae#&3=tBdT%yzT#El?A@Z}?Vc%i;MQB&a z=fDB~sh1t^{I?!u-`EXYHWbpD&&)0r81=Dj+?U!)_hIm>=($V#6+7UFozuEBV}hne!8#xSplDLe;)HK809 z!T@iF>ATao&3AIX;Ap`{KY9xeqIi_>Wh#s99xqw`<_*S&G5q#hh>o&iH%$ia=HmB{ z4-~xSfLQC)8uxDG25pe8jt?(Zq%{V@1~;lIkUml`dTbGde}^wL&K#xPOPzBL`rta% z?Y-5QbA3MNzkar&OvmsLlwT=*OAsU!w zE8Er*^6hGhlus8F_y2+QKetceI>^e`Jc4RizKm-S6SR26hOd(#Vl+vlerb4nMq+k( zFn{MHcfj2BcB5+gtZq4bw{b82M&DEI02G}>D4NB3!i ziLWegRZ-YaP;M}9DC)fa(7@lsM8EOaFt$l5w+{9@Zgp~%z0#2QV;hB@U|*4Olbf0r zGFVC_7@s8nI5(FuZE>T%xjL`uRe0oADH_wCIg?v8COsC@f5uA{=VK9sQdbj zI&~T1;kyX?P({WhX5M?$9^HP4wzBsT-~(}`v9CuWe#wV3v|);G=j+3Is;&T2bs92h z^4qT#Ys`hlSQ6I`1G_Cf=OA86F%C{Fm0nmo@GHFW@up%Vsy4bdpqrHaWxYwAbZDdl zY4J{oWf0^WPKh@0_h&KXYhYgLwkxs+NLl}L{Qu<16O97^Hy!!b`qbv?a<XzYbVbk8m_oGXtlLbF$j+Xlb|7aATvvb~ z5osS(Ww~5=n1%~XDms?{CdCal)Td)i7dQj&4w-NPZSAxH! z#A3#%Ijc>#$c#87sikKAptvqB2#rc+GbfK@YzcFgI4TLDd6w4N95uMk-}-L12Z6ic zLrfyk@F4OUdCSTv#JOrI5`fynYTm#cN=D%tajyD=Cyr6T9K$?ut`=4Pj{ymX+V$~u zPvmIqO`cD%P%~>Ju`e z*#mLB$+j43;`SHa4v8uUgHK}n!f%qrh05M^2Wf909#xY+?@QMD%{qzkP;Cy0$m?>z znZNG@%;rL0m zEM!n&Y02i|pxdWWE*>tm~+LWFyDsX_VHte&+Re)&?`p_ON5f_xpN8 zp3zG_8@28x(Uv^PJfX_Md+tFWtF6%boWMYzAkb+`Ikny(6af-zIi4e|h*Ax9%tW_$ znGxRX68BC*bk}?@R#YlgHdZ@BQ^boBW9ODO{O?zO#`JX?mcn$mW9hg8ZATLzli^RW z=lb!>yO#~!Aj_EcQ_qEAn@htyR-mJ$Y9=}`Ce<|n7f*715f_cVaq@0UuqQH%xot>2 z3?l3Yy8{&C2OZC`!!OH;Hak7)grWuX)=cRf48@(1aVoK^9WF+rs3g{x0WG6n5?`fRhPP25Pfm0sC{Cv%U^!maNBIQm?pT>U4*R z$Z6(9#$nyyg-oeAtg?&*Zr4|Bt2Awx|DM&^6Tvkqvp3IFTfVj~l<=IuB0DdbR5i3~ zC)yun9yDKX*vgk(s$BOiM5p-7ZiA?B! zO3oY7`RPc(1iM}Y>IR*QQiRTom=0KQmz_LGnWOdOCF@}K4vbD6RMF#??l7zkwmzq%NnQ+^+1#rSY4}>ez zfrJMad*-}g&lc|T@DL=`g*3FIopeRCuEF&r8$ou>d~V|@krO>RX{);3B{3`uBoq;&Jc0E7iBVI{p?X^6-CA~=QV&=i zYHiP1T}h>K8hu|@me%Eb+x);6m2r%IfmS;DgPhipgV_+RwnsKPbt8hZh1~3wm^YQo zIsETmxICC@|2~I57|ghR=96*?Js|}8iX+d5_C0S0>$tVg>@F z7iSnPB7(D|L4y_}&4W-TQRu=Ipnj4gBb>9SPCrgn(!8TX)W;)P2N^5xXYwNjj)=Qr| zT9iM!mM4zW;xOd95xbEoz?MEZwAAM$DZu%+5m`!|wGqtZMdo&dsfFgH+qnWlLz(|h zo=}|?L|7Ed6W6$#LUWP!Nk9+fCNG9zbF*4;4h}qs&6}c`lOY?P|DW>p&%Qf%H}xix zAm%v;@y;8;|n z(L~FUJH<%G>X_^np=rt31f<%0P^|nw$XzZBt2|Os#p?>74l5@w7PK<%r=G4V7?GPK z>VV5tsy3jZhFRrygi1{`T3F+P^J}3gfx6D{aM|GZEDb?{Wiy4;EwNYS^R4AeqKupn zi+w4#42xz1*Njg6%=AH(fZO|a{PJF=;3klAZ`fFH=|CKCVZJ%>*#CiwPOod49mkTh zywwIuprZ~hwi~H?p(EQPHYEYgO_o`U4KYopU4@2xY3_|!+&y4FZwGB&PN(kd;A15v zA*AK%ZUiAEHuZwqsjn*aC|OApH;?kd*#SFV)=l(btys({>NzCB_kcFO2WuUVHL*+B zC>?IEwuv6o1VN$woU@+L%yd7e)EaS>97$p;XY0_Q?A~YitcJaY^TtqrpQ+C)zqWo8 z$(B<7=H53FxU!uVVozSlCx4J!45bHYRz;T>DI0e!7TV>=hDq#okEv%*i7?Ie%qsaJ zic(+7zcW|n{51G|YKIpGH^hA(enC058(G>5)2y#{(VsIiWNJ|>ILDLQyE<6yAl3ZT zXckWNS|Pvv*qIOXHgwdGF3%(G&Cg^L&$u)Nd8_51H5DunT%dX_zo0@Et)t%H_;}#p zHfiCw(XVm~aC7{B%g$TEhaav0Kg4*K*Fp|Ikz%bjsi=y0+$fYuch{t^069#<-_PPg zi;6sef?j>v_zCmlCmsc^%Qm zFULQ89=(e*#%`Z-a#>UeQX({S4?lo=dvASP+ZIP^JU&iHmRRB33J#iMS8u1BH*mHe z=jWN5&SAZgyw6$L1_T|p=n8UUlo* zUePi5RuzT=L4{pppg-$(ynBFSpZGf?zO$8Ob(1Ya-o1T(8VxyX?@dK@o8>5>^ds)l z{@Y(m-poS(mcV9>+vzwEIv8IWiH~C{%3;y<4-V~6Ox6)~jjT@`d}||fAT;WUSWH`T zD5*}zN63@>@8t=uG1s5Rlmv1e7V>UPL|v>&r;vbj#l%E&KH1RmqEDxKNyzuS=V`WG zzBXaNT9RM?#k)mAyk%QYx8m)RD2aw_=#D3rW7nX#y`zAllHKlr&ujUtGFMcxEAEl2 zRs_$Af~&bZ@q75qhBv{(*rIa`o0!hrVjj9uUfAXo;XqsWF(;f#9(jz`3a~HdhGd2i zZnQspiJF`g>6uOBZca)!Y_4+=>D>ep*pBI4Zj0{=-%MNOuT`u7mo0d~Vi|d~)$ET0-}r<(qWjQy+~}xR*nq zPwbJ^j&?w=q?GKJN2h(uI-`#QJA|!dQKJ@MW$kw|3s&F0-_u9cf6+G{b9w5XLf&3p zryO?vlYB!yfOBP0$@O1oyI65MiL%5TS%dlOyrr>fH(3nKl)gAwk`NPOhO*}B3F|{vWs8`Qt|ZH% ziwWoz02R`-FSQaFaZAjy8Ex{SVGUoFa;W^?dv3>XYW6Vz{^1`P_1`TSjSX=A#vi{r zn4Q4m~xbWJF)AWjjRn^fvt;zFZAk#ECu&=Xd`w^jxQE*lkK zNw9&8s&DT2`ZPMD7b&!^~+fE zVNNa2S2I=CN~@@ii-aTiI)fFenKI75IMo|@DkN^G8@ZwO-s-DYD`M&<`{1;N?l7IT z24Y%`L#Ijmmr7`GmzS18-N9YMXvUEzF)CAev68zG>9GTWqi->6Ji`Re_~fC0-Hx&S z$Mp&0HKHwg?#bwv2tRKXyP?d+a9T{9o2rBho_Aay5)=qw^^&!^&UXyuoJ3wj1f;fm9brNiqkyO`lercZ0 z_ziDpJJ6<7Yg6bkwOPJg;PPN5BNHepq`|>&>A=rszqK#;a(Hw&xvBH?!_xYJ(60>$ z){cIEhKVza%IWW6N}8e#)>s}PP#Ui1x%)O;Xi+LG9!SUN4rduGU_aQ9N(*`ELsKO0 zcgOGe{m9Xy^((-WN!r@AH*CR=>>j*F4$z6b(d+3pO&^h*I7We`)xNL|X^4O)4rkNdOsKwpB&kqwQ7$x zttCqpMZ7L!qqib}>!#WS>ept2vh@!G6*lu*Pp2&sEWT~rF^cDorPDK)Qpjt$8|{~w ztpKc6rmS{~S@CRi6fA@DFoTN>c!v#Tx@WB;Jhg8JpJ|6VA-1k;k zrAJNeT2hw0^ghB-uQJA#)H#(Rl3U7FehbFfDA-(2ASSPcgUU7Q(NYNhSI(aULFl#htKXy(_@sQ?2^!$3(XklFYM)Ro&Ht z&%!J{RPse4zenk{IGt--uINyC@kcuws_Xf0Xy@+se{x?ly9_gxMtn>zXwY?>hWq|p zG*Xs$vc#X9W?pXe(z7~FQSN;;E}E@&1a(yY>>K46 z%P&)0eQJYJjp;Ln99IDD*#aAr$r?9fqb>IiO< zKI8b&Lq^~$%&gzq0NnUzl>e1icEe|8P;CXma7sWQ3`5PHvSS5rwxxxT?vPAy90me> zI47Nn=+r*Z0J(?eA&k7goy%5aMV~yLO+GhG-~LgeuH}i;>V#tIgsR^@5GtK9c}~z# z@-#0}&ho(d*C}kV@e5O|CoQ_)HEdRhrBm4k^EbRko0u!{jDba$9NW3s4$j}fl(|Jk zn*FqRWQ-)If4AeXsu9OYH@jTs1G9V{^<4pO_fcYx=-Ar@wfsM@1`B|=#$KqKaKUjq zymjBBm+B*(c^q(vc>dUl6Gu3{065jDFa#B*a3uQM610Qg`t0rrssp!p@>DZP;EmjB zf0Vpcv>7&NoM0|xQ#Fq}H4@XdYu?LxUapv0WKJ6#e9OV^FfcRk2)E_BmA93p6E3!m zW@9)CVAVukS}%-<{FFU)B-O@gxBkl{=o z9;y=$hQBVh7cB=pX0z&y6_F%KRY33?XKyF#gd@h95@t)x1kdA#5ZjZ&%mOBe;rxW& z7W!=B1bLY=>`dYhz;FMG*1yXy8ykwe)D23yH6@K5(thJ)TY(q_N-mA&>721Rrpu+H z@^*MDb51pP2g44q#isiDZtnE&(qcBUNn!OWNh9_+F^UWNJbjenZJH~?j z8gXgyB1+FaBB#qxUJDOo4(NoEO&30SNx^*FqB{JKMRod>IlIKOf52f#Y^H0Jl~g}b z4F~m#=o9ObFKAKFQhwgn7@6cC1U}pf7!8c*bN~vYOE!Zy(C=!B@ZztkNsrOlvOcu# zYq6D|*+#M4krtpAHJCUS5l&d`k12&-6QqLlY-D6#b8cR>*0(W^6qc>3{*rdxy$rG6 zS{oC5Qm9ahJ;EZZqD^$6FHklLdL?5!*u}NaZE{v|#hqfN)kg@TTu?#1QcSCym2+yw z!~gwj_3t?RnzV?E3d|w~XKLwlfbY^W$#(8IER~#J+tPn)BMir4c(+fa`sZ{sJl z&!#tH+V9n6wGRB+wFSOQmn+G(C0cru5N4WL@-F1nmVKS=5+l8MOj)LnUgYczHG^^< zjDF_iB}3xsAt$IZozp3@Ieh579V_##P)TJeuo-i}DUkC?uAGRndeV9E z{76(8?<_QL6%V(QO+{ht2+mhN7S{~vp3+Q|R_ literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/busybox3.jpg b/docs/busybox.net/images/busybox3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0fab84cf9d0c4236d9b2167653a9cc1486a35447 GIT binary patch literal 3292 zcmb7FX*3&Zw@#?3b4pDWT0^BIlq!nqN6(2838JQ?rK+ihqA|5%x<{*3r9O$FH69_- zP*FlBLQAzDF~=A~3>6wv4WUxQb=JLib)7$Vec$u`*?T>EKWn|u-tT&M26n~(hwb3D zZ~zbp1la5zz|J;6;bJh(I}~^0_+5PHt>c$(;f~wbA{}>z02cw$l9E!A64FvqQZh2q zd-uy7*uQVzex)O_hvdM@YN}vm6_w+fdfLa;p&BYG5R+5T(*{OHMrzvU&YK!q=$$b# z{7VEVBO|ka-+sjd2NVrYsGKnTkFoOs0Fnk|0hYvp>HskiP#gr@`2U3$*|L(*`=xb15> z(;I4q`eZ}j4p1uwU)4`E2J0pXb7Ank^*K&nlV$bILRfRReIyQsV2ZnRo30>wBJjiP z)>%tIN>|Lc*pnP3I02oMMp;K%6#G=)WYP(azz@F$B6%jkhmNqS`KjusAW))k+c7*f z*=M(|GI)-x^!&pZDg;3aH29jP_Le^ZPHg=izj)Z+gqS?f8ku*ui+4MuCDlS&*J#oq z<*notZDN~e`lkc1iKV05%gzN6sI%AmpPE>(zAzKj{bdhZk&?uJ_p_(;jghf((X#qw zj@hT;9@Mm4tAsoXPAP!fmL=XL&4s)}EhpoQTwu*w!@;)}LjuRPZYsMS6ok-W-;ky@ zO;vb>v(sTA=$E6oQ^w1vq7o}qR!~NO@e@B&xM`ytt86U?)%iiQOWwM(QcE?L?}qFb zg7pw>j1su+_i5d)YXK?y zI{)Xac-~t22csW}EttG~(~ zqAyKn2sHH3Yc$lAu4q;J6u(XBv8hkWE{AN&dLepw-Cp$8)nvcd`C9VBGj*?GD}o&Q zuzQ;H`DNhh=3^;GcyKfM06n~?qst3+mi`R!I;3qt)GR zO};gv_|uP?pdWR1e=xLc<1A0W*>2+F?1_i2l|r z(v@zEbrk4tW3~$@253vAW`TU#@(XSVQDDn##=5_CB+wvEDVunWLuSiC;e&e#bb@ED zBDB3I?K^xLl98jLnrDKqFJJ4%}TpnK8})A+mB5 z%&#s-G9dxfNFN8wwT0aO61(*4DQTc{bkTq9%mYpmdC`8u1XGcm$Becn`N=V~CDit0 z+w?tRU%_nL<6s-UMPsMhTbOrh_HPh>I5<=2Y}Ctiv*ZR5&X_HM zU4eLnm*0Yduaa2;p#_`|$%$;{L@WKyJEklJq^dGxy`RfQ5GY+0G<$3Cnvz=|n+}CObU_ENBRg1+uN}3bD z<+q5Xk&6D%dfk%pm)v#3CcDbm$FB-?vaJot-!Ii1FccJy=BqDde@IY0XZ-VD&s_?s zTkM4L%kqeP-$Bl-Qcn!0L3Cu;M(^8$SlRHo`rl}Df(1kG+A!?YhI{er;C9d>R61lL z7h*GSDsLkU?2%Q4M)qhN=Y!yH5|^0i-t2+I6)eqD${|%=MLh<$UWDey*uv6-3?`G3zO!Ey2yw-9Ii6 zwV_naE|UL&3VrHZ|HoxvqhRU_xGmD==%0B@Bmuz_+GE)@F=97`IKA8sig__tf3c&1 z*xR0Ko@f*eaecVD!X(YGdsZ>&IGyy_7b})GDV59X+9cs?Q=ZP!2@! zT>7Q_m9L^om?&Yz*L&=HQPH~ngI8UyTQ@XC3%1SH?o%d=)>lGNX+yWe4&X(W${TE8 zsA{W5+sjdh+weXE;iCSg-baHGs?ruoa9lj(Z!i{j*B|5nQHnqK-wC1g;NshkoAfq%eE4iR7yTkVeLZ#zFP@p}v)yCW)w^n*3pk^QtSUVr5e~q7Ka;hzFl3|ykGy_NY z4!bO1b0(7q2aO-(5yASE0{`|fo!rf|Nl}(+exU&Kvb(aP+{0Itrl#Lf(EC>Q(PAjV zpU!JA61b)jB%tH^vhcm_>%~#)j~TCrc6adj-yD&G&FjYDTwP4>BWUZ*v2;oX$LrZ- z7z^@lQt=*MXfNt>N}8C|%)vA|)J}{P-4U;(pLeq_IE7&oUp@N4OLcD_#|AZsU|do%r>l#C~n;pVShy=VBpJ2$|rkUIe9-|}^4uC@O3Zm#xue*KLEWg6;hDwhh&LAKszd}IBf2mII<9>} z|DH$)=RR3^6uXm*L_URdX+PTb$7w>reCvPeIe%(^8ZQi0KHOc#^ zViR1Kf{-~1ApPBF005}eaP|ZQrQ=9yeByyT0wCyJ7~9Le8X+)qsV#9wUE$CqGHrJN zlIo49`W`tS{O(_!1TrXPLz Rk^g|X{%slljbvx=zX8;`0igf@ literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/dir.png b/docs/busybox.net/images/dir.png new file mode 100644 index 0000000000000000000000000000000000000000..1d633ce4a673207a08d856acdfacb1933c98c3b5 GIT binary patch literal 309 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz0!VDyh@)w!{DWL$L5Z5#R|NlQT6G+TVGX?^n zu*ETTF(AcQ666=m;PC858jvGa;u=vBoS#-wo>-L1ke-=lRFIdhV5DcFXW$#4rUX^ZoU~&Ke literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/donate.png b/docs/busybox.net/images/donate.png new file mode 100644 index 0000000000000000000000000000000000000000..b55621bb90d526b26582d40f3d8aaf103582cc7f GIT binary patch literal 807 zcmV+?1K9kDP)Fn_U1Uvqp6ZP`)p|HaL`rPBXx7pz4`>i#u!PkeCuGpB9_Tw%FXKO>4SiP#smYUq@@0h0s8v-0~SsZ5D@?W|JBvl%v2FX00001bW%=J z06^y0W&i*H0b)x>L>_VqAS(a>02XvbSaefwW^{L9a%BK#X=XBTZf77eE;KGMO;9Ex z0006;NklSMr|Nj40)iTSf&_JL0qoG43k(4-}PB8-B8U`b>}lcdtt+}Y9@UTbA}W66WWJZ`<7 zJix3V07EDlX$9~}k_$a$WX(ADw$y8X`FnN)ZZX58ZHON*kRw;jOVUnCq)9y1Ipk+c z+4Iod@?LSh)w&2J8Ydbmu4k?F3^YhoJj}QW3n7@Rh;eI)(p?azZjOBb6J5Z%8A3=m zqx5j+?$Vi0Jx(v3$+|hMjr6oy#`$PWh_0*U7m`ztsPOSFRqhg#H{WR)_P1RHrgxQB zb~>Xl7G`si5XhuJa5XxkUN-MJ6wYcbtP?o5hTCTY3G z^lo;iBtMefsZ?*15_9*1^jdcB>xpD?gounJt;11D<%r0pUrBQD#LFyA!y?ziZ?Wi1 l6-%)l$frJ6(?|bl`~kM`8TbW^4#5Bb002ovPDHLkV1fogcE$hz literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/fm.mini.png b/docs/busybox.net/images/fm.mini.png new file mode 100644 index 0000000000000000000000000000000000000000..c0883cd34c35133634e52808ba6dcbf0a047361f GIT binary patch literal 7708 zcmWle2{e@78^>QlQ^X`crpQu?$u=Wpgk%aCB$LP_(qtP=lI(kpWG{`K$u=Yn*~yk| zM%L`h#K@FMjBP^p|NPH;?t9OD&wI}Mp6A~0_ul98MBcn%bmZ`f!vFvrF}{8c&FwMV zCJ*A}-e1mq{>kn5ob}E00iZNqaL0k4J3jQ}x{W6Q2#Wr{@eE7`?{FU;d}eHBaIl}B z7mQH8aoM6408SPeU(>hr%`o%#`-ry=nsPS_xV+uH@!ijN!)&zAeVKC}!s`UfG5dJ< z_^~Vt3>24UrHH-@bCc4ikteErqXh(sy2NFE%X2 zIe&NVb6b$1Wht{m)(0*Ju-7uz(i@i|Ue#53(@TS!>`Nl53{)iD@sCKCyhM)=KCbS| zxHS0KOUydfFxrAe-CD!7RzJeUK%ro04{{rJZJb=878*@0EwBim2`w})AaV>a^AW9cXXf0 zgPsS6JXv3`5v%GAS~0P&VOLe!Eq!_L9#a$}a6qoBb}ltDQ`h1tRr!q)_9kS+b~G*_ zo2gEKvB>B&a17i6g-N<6R=af2;7wgI&CeKAiLX;O>D}Z*bd`bRP=#Mr$VBj|Uk^p1 z&^VhUu@iF7!2=kjGcj<&Dda0WJ+~humqipe&c! zZf@n{@565|O;nDy!an}H(Df8F-S3j&i0yoyV|ujec==Ivq9_%FG@ZB;#n zx!L&h)H@b=Nse;Ypavkhm}Us{noSbf(FWxa1E;3IyCDJq86lfj@ePBGfu4pO1Mo5D zqJ|*G&{l}DWB|zo1ob1a=6-%WB$r1-LFI_(&MGLNNC_v}K6FicYIjZr1ZS?i@_ZcQ zA!UN1aq8ubi|y(jTB$W?lYA@DYHEAdS5m;}Ld&R}-_Ir{6b~4>sr>iE zF5lHEDIA_vsV<-#trh1+7e|gku7Xk_5^`-Eol!416{7^rleyz?=Yk=vlsYDx$3tXk zL$qVyG!FRh-Zpo1C0vuMH@hlWkC7A`E zrk!7Ef^Qpbb0dSuJ}ItF{9)vZd_dPe6admjw$V{Y>(l2 zC&P{?v>Y}Do?m$d#EqG9SDA9ubM%mlrTbL9Ym6l(@A%aY*=sNX?CXpCEuzO4fFH1fS}{p5sn=t&A^; zwXpCwKUdmitx@Fi^1s;V_@G~I?{=ciA3uKV9iV-t@40M7X6Edv#3)y$Y>YxO+)%Rk z2pE?V4!R&HD5&SR7-5h~Z~!Y1zz=Ok+C0XD$wIC@%3Ylt9kW}|Gh_jW*iUT`Nh~Ol zfCKQMlGCBEvjp|?QCldEEU4mWs8Gvt`Y6Xv2#J$w3`0@(tc3 zI`(R@>Jk2be|L5srusTNM{rs=^|&52zqI_qfiqrGZUL+$_y^r#(?PkLwuf5Pg6BB# zd%L+JdOOQ~q>$p_IoXfl-8ElYEk@U&jvZx@JkbA`&lSmM8}FCx+#l2R+G-xGNX}I8Ko>Ri3#2(5opwJa)&eCFiU5c@amU_Bh{;t{RIJWK zTdUbi_x1EtVpK-G!WB6>)ogJx0sWzJw*F=(tm{4!B`q0?U>U2c`h|g4b zPIA;1`N+;PYHd%g5fqXO(nr3}=@<}GY)7Tq#iM!e-kk@flF3@C?=M=qL9ZPG-0z2d zQ^V3EvFh>g&S*<$HW5w*0Nw{&b!TY%s7Ae%P`fvivwUFGgOZ~d(dy#kqbJmAm^=Hq z*s@;ntxjJ!*X^KQN#e{EMC7gBGsw2G`tel$Giy^*Ve=Ej2de61`Ibv3L})9aPBv7n zQA5Li(3SJ_pq(Q7v!QzneR>}IyBkYOi}xCRSK8k-_-?bS^|lv0-|a3q>#e@o_oYv) zu!6UCW=DEPOKgh>d)kYBqp9Z$i0UDm%LS@-H9Ly|jIaFr(-D_H!J3FpE9>i4<>Pm3 zZ4pm@zGC^_rQelMwRO3e?lb8(8k?>u9rU-~yDn(;=hv$d!e-`X_Me~5NiqWKF%(lX zGe*Et!`i3D+MxOD2;wSN2-s*3*9ngT9qHOI#I=5|GaBerv;HR^*SM7Ndf^WX*dVqz2%1_ z#}#?~xYM1hsm>&x(B0i#r}8j3FD%w+EmEOzJzPy^b)& zv9)_M_Qu`?FKF-oWgnFGozALDmXB-W#JEE|+5TbdaXKgLL!7jwZV{YC)WJYf{c|UM&fGi8jQQZ2nyR_dd61;4*&yGJ^ z`;{i`-%HOlROg6J+20&GVm<7p(x zn>ajUVtylMcsRFdPmSTjaR1&^Q&ZM78Zt+=u5xGJH{}WSysT9P!P=HwQ10S=p)W~& z1YTXCH3$J=3-!vOn z9!eF6=>A<%ApLVvd9<*wfOlUSneS1uF5dfX)E7rvSz%UZ>V^C$Tnbs@{?J~h63bbe zLyOP!og>?ekpowpoBr)>1Ya-Am50OzOlG&+j#BOB(^`#NvDL-1E%hTP~8R2~4k26BXMe1G!c!tNg>iR9#Do+mc% zCo>GG%Se%aO|gnH#W&=v+{uQZ$KF~dzg)zW$T2IN3|8tixdNX9wL+N) zbKS6c$*(V{PBLCCpgs4Um{k4&JWb zXSWL`$LdpNhh+AC$asltiCq3i^T`Y-Cr-HV0fU+2T&9JE|0W$^CFh4T`R3>EwmRq+p4Zj2r}z)!j)n4mDbU*;J$Ue7gs_UVZq);!#{HnC z%?`%p)k5>#Uut%Hn*%eez3Z2Og~ew~54dJ^jsNel?(Xi{*(9uW{)E-EGfDEH-y;pR z!^VZ)Qc5P#xkjTp)tHB{6HZnC)x_b2LbDaz^(Ju_o<{{?chPq8B=c0WYlUUXxx0H= zb|p%V6KrqMKKVJ=9~xDb5(XdP9)H>0lGuuoFnf#641~vO#~ZA}o$cm9M$?Aw{)|sB zi$S4KCVdz|iKw+k#y>B*w@McefP~1!h<-Z}Sb~I|zSGmAsv*ht?@4`k_d8t|IN+Ml z*QfG4%%^EycYc0e=3mdo-HRVA&jHJcC4ssp&SbnG!hXk-L+9qSXvG4-?E6hJu?at5 zf26M^W??8+$w@ea)Ms@8#+L@p-4gvT{59_=9zQ$Zu+x>OnNvMEJw24H zmRWI36+T+sBq3&fyPv?afxCgVWWkM=MD~Qjz zKEK^Y+vhzEww1BF*9w~AEN&BSs?{&I95WA$RoBthW(GK%aml>w#X9M1t~ozk23x7= zI3M;#L!jZ`Otj%(Ympnrvj~lYdVmoE66PUG0p@|smBg!_=gVWS7SSM%#zeDIg0)Nb zyYp9-Ok!pvHH&!%B-1syX0d}>-Vs!@ZSr7N9aAuo+o)?T8XbjtdfEXOXy#H&B1R$m zi6!;x6XBAY?!viysYKZ5p!4D#ZSTMz-6sqF#MXvy<`zqehvyFowmM}zOjfn8 zl1g%7PEAkudiq={PG^z9)RJ%YkpF(U__)uTxqs&Tn>fnw0U3ns|1#p4-q>qhvYu zm^F!V-B@pDL@$tHv!R?m)beMeaepX~WmrB=%Cy;9iMRViK^0ldZ5ZuNfv`IZ<|Ou! zDnPKWI@jYJ_#^Rz(Oh$Ig{Rvy?Cw@}Vx>GggtZ^!uJK0_+nX8iHlb!RZFke8hIlA|Ra(X)cQwq4e%K|*gXHK=AUCo+HS~}oKlQrP z8`)*0>?Qw;a?~#bRbDE@OR7NGgyL0AHyg01#AK34aXB53^7{Hm^!3?UClZTd6^%X& z*d(=kSc;t!YPBl*#ST`mSC#4hNc!y10^91Tf7bM{(ji#z)u9%PUcxm|uuLPqa=&q zQ^_90yGi9WHKvLgP0KC!VYYYDJ`CdwNSma;^?)4m6%R~*x~Hd4htn$9=x#onsKIv& z`6@vG+o=`LM3~(=lU_^%pjSY|ehhOe%N#49akq2k=BU&s6$aQGlWHG)jj>%2Y4UT5N z^mNrY*OeeEc-je0dl0vDn^*72Nj>fch#=ji4C9j6;}>UI^1BZQO2vvO&)zWW`I-8q zCaKrXu*UtU3^EmfSMSvHA zO-tklOPyKi-6NsN&6j#kIoY_9PDjD0NTu6iMD&Y({U)PQZz)>U_cAd*_2v33ajRC; zWLdon(Q-#;`6}J}^T7u1^6Gl^!Fea5ma+Q9X<%AM%?~Qs1B=7b*7Lr^j3ZN&`TM;1 zM>CR*kqC(@HtvXx)%EP{hV1rp!>_{~*4iT9-Wp828sQ8$gHrF(4?2+JK7r9Lrz@=d zEPj&DMJY#}>JzNwMMYXI-=n!lBKQ0#bsZ&}2g-2M(uJ&G*K*X-LZ zA}lP>`r<}`qm5O`Xp!Z)HU9?XHW!UQ-gU0kk`*}MzD*KqD5!cS;J@fM&G*0KhA@=5 zx!#2*5Lf;<=2%saRyBLe@}T=;8*0iYG&3Wf_VvfH<&9x- zjaDx`i}Svg#fU0_TFSoVI(&bm0e&rSyuL2{vj0ZNCw9#cM@dpr(lI-Rn)C~9NKlMc zzm56HMMbr60qi+G$N>V7>_1}!r}~j-9)A!c@?328`w5~A5>I2U`0D()Y&)vbm4LpK zjW1Vzi%fQe5`ew~p*nrKM%#_}b!vaC}_r+eJfj5;;yrNuJNxZ#t3^p5LcVuH1O z(3;xi&4OV5N3gNR>bSAM*X7jfYdRS!exe1X9}lq_civ2F3|jEaYtl zQZj{GU!z~bSH4FrZKH90rGpjMnL0vO$=y#Njy9#AAVla6XQe3oECCV<7vQQ;4hRK{ z8R#pXi+Ug>vc0?aec`#73+xqW{R@nIM(!c*LuJi(VdTPg6|>gi4xJ&{#(d_t`E7mO zJB_;J%c5`Z9)KE189AmNOhnz}u?w|?Im9Ro#K`!@wF-e$+J16ezSa(P$^r!7^cN7g zg*aA>rN^K#NsdroJ8`tq7l?*0$-2-IS_!G*n$YwnDp}l<^U*XxS;!6aB#mm5bQC## zq#A#g%bMUA$wRMB5so6?KJS2B0l{7-BvJYfAK;WTfxMj6=UF``C`duTn=>e$`M%njrOI= z+8>8nKBP;)0RiL(hs+ydFv2&24=)e8vrPHkXy#7XTY8RS%*Yk!^o|yVvInHKVKgU3nO#zUGFL_drmXxdppLoHv6ceX zmF3I++RTF&x@hQOr+5bZJg38FqY6(i8BU9HgBvPG_jW(SP+pFFU@XmsC$gv;A27*E zN<|9hq+oWXb<;m`AEl&3Mz=~`YE4UGUb7@ZK|2vvq4?K7XE62B?~AshM_NA(&earW zva}TWL7in>;B5?5Fcx%#m7chX%aNDFp3NsfhdV?2&-NT6Q<*Ehy*HW|_Lz^F>D-zG ztB>KkwxcvZFG*q{;&T53$tjX;Zv;jd`j$C6#tayu+7D%?z)rs*2_hCGgii@Ih zOhgj?E_3DgpJAoULVi^Fje^ShGT+sq$X-*jVg%8FAws8U8D?TAZyd+V`z~*+cB$PJ zpWE0zqo!CoGV0D}S7+15auQDzrM^x~OsrdK-2UBv71Qcga2BA!b? zoTP=h-&OT`rE>JfTeu>nFacADbWzP#)K|*V_&fg<;s0Zk<(vOl&x7ib;WEaUs`dNT z8NRmW<}>BzoNM!+j`=`#81E-OWk4bncn#d7{PN?UI8+ProDyZv`u;VH+^n6a@(01B~lwap_#-uSGzU6P~C z?Awb+$d)?NX7z`V*I%i}F_p~|(Z=d~w>whWPOI<(2$KE7aWyyj6tRISe6j%#2u=mb zAwlh0;2A&CxlMVIQv?|G?4^!8nTMxF8Z07j9R>u#9Bn#Nxc*24LXRH+RQPiKTAznt zZIbo_z2peuyx_wvH$fIo#{r#x3L!`zZ*PGY&3W1d7>byI9NBcRLb*$)kE6rPFwZ&7 z3e5Rt`p^ORhB<+iqRA0VWsf1h{LDSdyZUk!1bG4PF5{Nsl=TbMy8!G>l;W9S{Ox%~xkNGaAsq6^Pjk1OSs$;FL3o%roao2$S>@8F{_}Fi5 zbQ8GzaA9%rtnt8yVLY9brvhQZgt)8+Lm8Q!o&A|=eB6R!B^hf-KB20j0;f|UG>`9W zZ=+lbnxs2$s#)*9y*7W>t!V$ke R+)6HBY;faRG4lTN{{e4=7E=HK literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/gfx_by_gimp.png b/docs/busybox.net/images/gfx_by_gimp.png new file mode 100644 index 0000000000000000000000000000000000000000..d583140348966d345f223c002abb7efb899f071e GIT binary patch literal 3955 zcmV-(4~+1MP)~Z4)94tK~#90?V3MmBujqBKU&w_*5Zb(2OV@E@C1Xeg9#?sU;+gb zC{VD$7%ottU=16LL4kq|6udye2Ex2Rm<=}AK*0tAZ=hhoGANip!2|-&!5I1u1&tZF zen<0Ot!b>%yLvkgs=K;pG?Er`?+kaZK$`BJ>Ri91n=>A4;{_w0n{V;~Pwe|U5JkV)oXt!E_Ri+jI|$ru=8qCQT#MOSAYm1zi8gW@+jrq{aq!`TDY2yD2fbS zfDnpSJ0=XH1(2(8AJ~;~mUXrk@?4Wl$E0bBHQKMGu#%!M2*87!=h+u6^uEPCGz1!B z?+5(0Tm#7Rlk)g}!&nOfgGG{SgA@{hCeMCHp8bv>{ClD(@=UJ4eu&kzUdHP;n`Pu# zLZLH^1#1kI>RF~SrUbg$LjTvTeV3)(cZ;A{5-jrvpyDv}? z6d3}L@|WnsP|PM66B9)dQc8p{2q72bt{Q$F7K%cXXBm0+Ta3NNnv&swHGf>t=~45< zS@z5R9#W)IHp|j`nYRjHHw@MqhQr~4JSVF@UY6aISA8d?L>nhV3W*et5Ui2U5~kA$ zvsp$_xLs|G`>SMEfAuxcT9YIb(yK|izH2H9Tfo{!Sbjlae^KjIDc59WmSvou|J%X> zc6R>p)`C1ZRs(7Q5P;EW#QFJ%!^1ntpRH&j3Li8w#X1-4QuCAyUSWr zx{W_#?T@7nipt%2*U^l?Z{8fSv-5&ZCtg^@+1VNA=OaM#%{OnEPA5zz|3RLAVt04Q zU@#yI6;Y(v+#C?cF-J#7?Ccnlg`wiZhg0G>rqhWRcI2Ybs2iF0%&S-Xbh}*uwzs#vF)EiJ#2S;y#BZTI*Z$$1 zPM6AzDLqA0=`L$KZ=j9Q$Z9utHSn_B~dKzSpzS}~nYmk%FK$Y#GSeQv||+W*1YwMI~! zHyFEC;{24rvsnT9Hlu}r!^1=N_xB0Ih%C$KcDrnEZ*zWrRth(4ZuZ&VcLsR>{)D}~ zJ@PzbI2>ZFlQEf0h@uEC`{>li$W8I5rK+$*6VZr@xLgFYruHlYwV2!*MhM(2+E>UN-%Q_5-Dp>;)p0+ z!J@TGuxGQyJo=%OZ!(#XWm)M`H1FRZv$wam;P0D^yK6Kvl`jc&Ns=%cjTjDxXstOt zJ@xm^W*J&*+U*woUWfkXb7Y{rE6lQ#^Yam{R?9PgGMR9BIp)KMQ#zerF&zFA0B0Zn zl{EcNtSM^^bAu_aG4=-R+_4-OaU@bD5VGj=?!R%ap~P{|F#;Odb>E^*XhJ; zZuS8eoHjSN{QZ?XS`M_$`|IaP5At0(Y*R)UDt`TI=Qbvj2>{QZ4;Tyvbh}-olw4h< zTu!E>Y2uA@b(PR+#V*~`wR;p%w3xmk>d zOY87HdU;XH052B@P4-nFG_#ath9mDGJTe(Gf-A$RmU#&ogvUlzKiY{q#-gpRZj4 z0wC7V#Wlu&k}fTjQkFW@{gXROj|$xCv&b@L7bA3%cp$<~OnWd`%)u{4%r3?}+uvW3 z*es(L8N3ScCdjL@3Tq8oYtPKf%Sq|}zI8#b%!l(_b8&G-YrRElU6Ljv^1J|GmZr4Y zG5vm@>C`d)<;x*KAel}k=&B`QZ?N28%pAcSW7j-#*3L17L6mN&0@=9^S?*R-2gCbD zIvR0#cu0FNp!0Hw3>8 zkBcHhN{I?2GH?Zh&NWH#8-#p{;0J{GjJ1y#ouAOveZr{4*I)1Bs=kb5I$=6-?!?$f zE`_smu*Rowmd6NgFcyrR6HtTP{99mjom6XVqr$V+kX?+qJUpbgzfZK$^9yYc21LCc zK^WEd3y`6rUHVU*BxK_;?VarfnTw#;_gV`k&%Cj22jj)&l2R`4TwSGPSynE}u}KCl zAW5kxjAOm9Amn3Y*?A}m!+1O*kO4vl?wZcXa*Zi_7FvJj^|h=yzyVR}Z;HCicTBFH zTQ#XlR@cliWml~R!03$e;US$DFNijJx3+Q+MJ)T_>APd}Rl?@Gw_Kf`VzfqnJR?sM zgn;$u15~@ksv!hiy+3isN|9fVF}X%6#rpFtTF(bdK@_^(u4_$}JEyr&=RW3W4H$zI z&P;`vBUp2~n;Qx~VePf=ya5>dh(dpd)}IK1r&uJ`YOF1blClIZ%r!yqqy(BJMmN@0 zD}CE<&d$dGo}tVBa&|HD{(F6+=b317P1UQ=NrFxi5ANjf5E&};Rq9yTZqa$Qk5tMl zm5s;9P|@1jLMTO%rd+*0K_>~_SNjWK$8k)n)k2qz{bgpEBEuA)uwsr8kC3)(nN~g) zS5aGxFQ;t@@+>!~phU|U4|3%;a%~W-l?HaZM@otGeUiHMy1?k=<2*@NZ@7yJxaqsM z9;j-_P_e!ddGNqEz?7o9zmICSn0-8BdVGuw72Ul(EO2#v%;nenY`l5vpDmT5dvHKd z8qW3%K|5x8bmWW{$6m)EP;6}UnNBA@2nNC9lFg9K{u@(VBjj4?C;tX3-NOB5v~@{s z%#Y~e6Iu2>DpW-4e}}b@%~J0+z>pUPW3CB;ugYgCQ~*IB5mGI5IeiK4P`@4hg@AmT z+%7hRr2Tvg6~~xdGd(^o1sU{Jf@;V9p7`Yp&*o@ri|O$(t>**6Ue6olVt3EqCxk@B zaSd|i63SkL(fXF2?RLw7nq_|R)*7-bqtM@BF<4U;Z{{(={0E|VC6j~5XCB@+3-Bd)wV>tE8Vs0S?2}!Lh&K8N z0V7G@J8*u@bN_a!B1RBMqDT>i5lShr#s@^KA=d_7XoPr7 z5J>;$!ER(4RsY&rxjK)o6bsuiQLo44H%D}|ruDpT;uKj%a(qlS9uvQKfdKN-oe04~ zzFj@IxJt?gEau(SA}ZzdRZ2b{d)DXUF|)HX+B@4`-l|kkN=dujA_yc&l2FVvv)S*< zV#5VMR{<^>yh;ZHrBH!H1%HcF5duw;8VdahW9FsdObN1c8BKtlpDtf8$T|gG$U2&% zxrk@``&=F#GJSW<^xZL0uZPjv1KEDQMeDgMy~B13sg(aN>h%^&$sMu?*VZ6IXduJONMx-B49q@5XYjX?3Qh@P2l{!lDKmJ zftnB~(;n4s5ymg-MFOjc<+nD**ZNHs*2PF2eATnA2g{Ttb zcJoDU^Zsf*%RP+c3b0Bg>)26MDzZ^^d!e@l8?4DD$1;Tw4p!TeB!yW^xmciauQWJ=~C%BiK%Whz~ExPJ?tD98Ia~7LbQ2*Sm`LlugX9TO&Y^IOP zz}KJ7-K^?1rm5?}6oo;|W+|7K6HZT0nNBC(4^@*ZYc(TkbHR7@s_z2lA%Gt0BsRfY zC2Lhz@V@h9dCP`hmfHmXp&eQ!8PN##rE5#$kqHD5N-wC!4?2X?u{x8ri5!RQZiLC$t N002ovPDHLkV1iRUmPr5r literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/ltbutton2.png b/docs/busybox.net/images/ltbutton2.png new file mode 100644 index 0000000000000000000000000000000000000000..9bad9496aef8c100b351397bdc6f52785a195bb6 GIT binary patch literal 6798 zcmV;98gb=`P)0T3Vw5+Jym)J8G#N}CzaOeQnZ$aa;Tv6PxB+fTO2 zFE0Bb53U+7dXb&P&e#=Cl2N2_B55LOWJ$3`E=iUxkrV-vG8d5G0s;gYz2E(wB@f5y zp;DF1DOA^8&-u^)f8Y22_Z+~96DKN_3II?_;r)gXqLdOs2q6H#7&A>%AJ97*Ap~QL z5Ylhe>vaGql}d~;0MM^sj7`%tO;bOH5W*M(fMFQLVo@KI%VphzZQB6gd7l1tT^9f> z%L0IEwWw6$8q#DDW!&CXxp?6g+f7FMSb6Y z@WBTwl?r1_N~zv&gph8x>$)x>B+qlpvZR!Go)ba_6Vu}=iBY}=;&x1$GXEPinJH>aA}$fAq>N?Z5tt!=Q-y*P17w~wq#kx zIgg^q_kG>7c7&8tN(lf`N+E=l(zb2GFm!JSAw5u%B%bFvj>9?6vMf#0JkJY-0z&9V zKl%|`US4iCn+Fdblv3(<03gdU!!Uv%&?Y8HVi*SJ93doxKp$+{7kQrBwjD*0<2X`E zjByx-mSyF6E`-q0(_JBiOw-guWLY-AAONIk${0(M#BrP;2r$Nu_0A zvQjRWF~)hG51^yX62~!|IdkUv_3KKhD2jBDHD%&BzH#Hm zH@@)=%d%Y8bsVQwtNrw+Kjj~6d7f*?DWy)GIyE&lrM+|T;K5g4eRaUTEX(vfbxV>Y z3`2caN~ycjew0!sNfJd-6h&be_Ika3zn^7U5CntQg%D|)>g(F4gIjSNYh#im5kd^E zr)jF0l_W{4)f!xHHk-F+HHw?oxO-gB==NeX;i2xACaih@~8XD51KnS_6t8u4g zdoUJbOi$9XtSrkc%hK%Aoy2jh2{qu0X_}gDj4{qRV@yja0CYMX{aY*+wTf%F*|tqJ zGPV5b18s4?-_P@0-!)Bh;ORT1bReQSEaT(jk3Rb7`1rV#G6({Vl%b)aTeog4EiLJ& zYUU3O4RtylAw;9m=yto))6*LEcinYY5CmF5&!0cPwzgI-m-9ULJg;7_o2F?PhEnRi z_ukWV(172uV@IV@X|-AxE?m$=&}4L7cYb~z09IC3*4Ni9%hI9kbUItMY@w7Yr8Lo^ zC@PgoQc5^^^5msUm$EF=3f%Aa2gGSMo8SKSw+9)4G4{d>FQjRzALg8k_nU_6>#x7Q zfB$|Rful!{zWCycN~w*FjmI8)Oh?A^Jm2?+hll6p<}Aw^1Yc*n+1c4Q-+WUutJmv& z{_~%Q_p|l%^z=(Fy|li*u9Vu`+PN(zo%P)WGQ=if))^%OR z*k?cc*`=kWMx*hSuYAS#eaCTh$d4R362~#;JV}xyNi?~nl*M8ZYkg*n>1i8=p);J$ z#;sNhA;dY?aM0-hW1J+3RwLcgGzEZKt=4L_w3Sg5xvndvtkr57bUFn_QM9?a$vMXu z&&C&a|eCIow7q)FTo6S3!B7~?`t4gU-siY-Po1}%%w(Vobj(zWY-#dQ% zxK7M@onVHm#h$}3;} z>Q@Vef|OG0OO|EbZWjPJ=elapz4dxM?N*J=Mx&u;qNmjB^>iIDH#hgqZ+`RW(W6FI@qh2zJM4-XFuA#@7Y)q}2U@SUaCz!wOiG)>bqwJb{r zk!6|Ys^d69h$xD*=nj&(es@p^4La1PI!6rdmP(~Pd-f=$3WdVG_ui`u2dzX1AW2>>KK%2a|9oO%qTOz%X{rfQC=>wT zvBw^J{`u#9-(Oi-(HTSAqwA^Pu4+SY@rBczIXw0|U?U!D9Nn6!!xAg=x(1Z}@&Ydfl%dYE| zN~Nt^x1Kn0;tOB+LZi`;QtAMBo<|7z!4H1${qKLj(P#{+{^8+a(=>G+)Ul)kINX^v zwF@}sypM3JiKmM`)1Pa4Y``EVa&wcK5U;5IQw2!rL>ZX|)$Fc55kD}#< zF;=Nm!Z7rGzuj(kyIm<|zuzC!+JjVT7)Gzx+t}F9a9dbdXti2eO5-@zj_CDz#bPlG z!>@hqYfnG@bhTRTcDr#LhheCtx=<)wx^(IB#~**>kw-K=+wJzC!qSsfO3{Jr4Zc1G zBBE0fy#Gl80B^qerVzq$oPn_GT0nDQfQvzInqL}djInaL+-Nj(4XQ`!cDub^uib8I zr`KvV{qdG%+3f7>*x1Z*3)i4!N5mzUSp)})jgq=iBuNs_@wV6)l$$xnXr)KgF8 zdEV`I8DkGU^w1-ZJff6({PD+s^PAsbj60pq@#Du29Xj;&uYbK>uTx63j%ub*O0mR9 z5X1pE#tMKaz+B*5K$f}$Y>Su{Rx$%2hU@|rf&!QXBPo@XQi7Dw>GXIekdR17%q0cD zTp|uwASIw5L>v@gl=HmX?-#P|NOK6_#b}Ac^1bp zGmS#2_!nP%^w_aa?cKZokN)UiRfeik$y~{I-Z}N$-#_2(2gEc|!GVyNCmBxxB!EP( zo_f0<1lxDbA|Y*o%p@%%EW!XA5aaxvi;JhvpBI_*z_k-(8D@yHej1jlwe@BP8#WhW zyk4Iit>1so-51WCJ$H6tv(dts)`mxR&dp8D&ZGq1I{DV(!n+mU>$ck_BlY^kM?d^g zBC*M=&9%n6@19y&y@^dCiFAGM6URSIC}Wg%Za4C7aQgMP@^0YTPCre#!Aj%f#cH+N z4_fO@918@}wCU`gUAuSfTwFYJ?!q}mP!PwK>)BS}lgIw0$(YFbW5 z=yxzuahw*MLa!YZi@wFII1U8}V33KB5lR%!!q}juXW89Gvs(1AK!7onO3h9?M2fne z6aWHTM5#h~7TJ!GWN{D&naZeTSdPncND^Lj3qda|l}Z4z(Oma@hhP>5sb$$2l8Ojw z(j<$Kkd=~;GcmJc2dz84FvV1<9CzazmzQoVp7oN{L&{=Os4NB{5C%j+FdHn_u-fe= zvaHqO(&e?)@JS8{wQrHN@83!+HDb(sN`q6c{n7%Q40&lP!`NN&1zqTEiW6i0s&@V*c4HOM34y#6y-V2 zRdDsj+kf|Le`T%J3nGdm*ZSBW|AFB#Vb~a`TPT=-D9AJllv30&D8bO>VB01m5>$}I zy7fTHE> z*tQ*lz?E5zP&bV)pL{27wGD&ycxIIi6v(nW#e^B;Br6bO8WF zuF{nAl$gwP9EKrJ1wauH<_=1Tpp*fCl_5waY_z~Ih(Spl5d{$TK|*wKxwWuZK-9-5 z3X(l{?PH!LU78TcdEd;H3kFDO#u*R1|lkf+e?K(S;RREDuxbb*>_#tnliz|<;wQ5b?0q*9!J;J*1odqHNJ;L?yf!6Lx;ezawVBzf^C5mfRH5xv^p!7FUCO__j`hKgEHH;9m}M| z^y)J(RHm*K^jp}*t^`mHgo(Jm8b|qN6s~s~L*-%#lcCw&!`K|nlyBiKA>1+!?U{Ed zMbrp79T&57b#rsIfm&T7PhFeEkTy0uCaJii_2Q9(1%oPr5tkcposMqat~iC2cEgz3 zvg4kEg`yQDu^}bO^W@a&n-?!rzl2JqwJ@68Jy+i~mnEDrDg~mOJjhiV%9tpyOai6@ ziUIFmf918`EnRT3)b&d?!9(L)$}@EsDYWCXTpog)Lm0#LTWjx~hb#};-RGWr4kP55 zwr^QxB6z2Vql8f7o=^XAdEcyg?C?m@1Ds!3SY+4Qa;5#dH%_ zCO22=2j=|o321iL-+A}cFMeq$)JS`UecP*J#q#bMMY1qU8s{%u_=o40&zxOPVpS-5 z6I&lJoch#k!LtMhr4a4riQ`(V;0c~|v&2BcHW}FL;_{LN@H{UFg3UCn7hOD3?aLU7 zZU)lGa!9g#`TC1L`I(840vIV4kYN;Dufz|30ra|&SFO~xPC}^wCgZ?z&J6+b+=XQMW*r-=>#LQ?@wjB)d;Hkc z-o4;@aC-6APyWyB%1vrw%99Ik|8~~{d(7>ljgx0K&McbE$S@0i-uJd``Q)Py+56|S ztCvF~T73PTnW;)=v)?Cly@bZDl52#`cTd%~RJ<^Lid2!v#8fE*G8e)VUoyHB#H3+^0xi^CU)!sY6MB@ zSQU#xe&cul?FT=yR#r!CyY5#qBp>|TXXuCafyp3AU}kFn$L?QBUp@Ku>EW3aQ?`B^*wvRbAVvRtoOe+C9P1KM!8DUf@wuz z=n4s(d-g@2I<5hXXCw8A-FM#yJ9YvXo?&*Pz@`=z$WY2g$Usto9G~7w$H%}j!8O6P zlWW)CeD7=?wnz5wS~Ey#ViYL+<4dosox4z>wq?nYYOO2M>h^KLav@XF>x)j0cY>L* zsorKMm1y6+N9t4CA3_WaiegTXOc?g` zwyl0;sDJB5hoI5<*-t<6MQ7U-5M$5L2Raw8ZnSSrj*JUxhM{=j-~UYtO7c9}+?1W5 zSSl~a;n3V}w?2Wk)Io?FS8iI4U3Lp+PoKTnxxMzkav1i!O7Z69%a?zBawqdw;%NP5 zb86cdo}0(EX-bKJVP(L`X^ACKi{@KrfZ-e6j5`vhLKn5HD zOfUcx0H^-&5Hii4$T1~BtHYZ8iA<4@j8OsrNTT1o{gzwuN2aGdW@Y`j2sB^14$Usk(pd7y zrG;%1_T)s2AmU(CfCL-_pfW%StX^9_zc4+$BWU+6uh#CR9Fy9XExAk;WX@O>KtXOo zp#n(&c?zy`^YkeT8#G5amZi~p|LToGeWcY`Lj~H-0wJZ5v#q=4pi%&tLzb*vx-?oH zih>@pNH>;grW`VnB^G5CW)l1IQlL11lo&9^AOL^>(N9Vx1VRW90>=cU03=8WLV}RMB`BpRMy3rp2Z?|f zu)O^16aR4Ljn@kZw^}Xc+WQ|ka@VK-z}T}J43bD7h++i-lv3jN3wKyy1q9(*twx2C zL@5T?GEBia0B{pqel^j%wip3MNGSvoONjx2$_0SH0Du9E!4k2=5&*R2A6RXa>W-ZT zy$lOVDQN&9fUyDqA&^oSA%qda2rvKuQWy)R6d~GZLm~jA6jBN+q!dvKD}ljqJf3Gb zO_>C7nHg7+8Vsgz1e z^uCYbgKP8v00=mMKw#PqAVel7cOKX`GBP|;sjRK7mul6@%nVG{figgV7`Cy+0MP$6 zA^-raudfRs=!FXx78Vv3&nz&;O0JjZc@o8X;~pWT5GVu)A%&1afD~XUK4{TDGk7|S&2)= z{XMGx-|Ka`FQ3nIo->~FKJRm$$a{B{Nr@SVArJ_ustQU6{Ph4ISwej9Tk2~12n51f zsfv=<^G>%q!(wGS1)(u;I9vmK2JfX#FEYbmofh2E>o}y~eJs|B{vkmE5h7bugu0XM zWqeSOJgu5Yj??eohrFH9bOMA7{9IhUZJ$3!@%_pS3R=1XR$FtEMvt}LlklUA76>|5 z7AEN7A_201u(8n!;toMb=#p$6XLR&^ngg8e*X^9U@xE{5Sk>VXxbBps^11{s@3PgO z;1Xcw&abXEB{4h<G5%>M^>16kAvbk!qA-_;RAaeN5s^T`WC@-&T&Aix*L1TSC^)qX1 z*}u2bLEI<^(>y&(=l;xM`Hv$vD+;5LRjOYaTZdyAuGqecCNptDOH zyDi*NXz{oS)kMBzodb79O}5{($m&5qo3)bN(=ybU(d)2M5~PSUTI<+WH~=n&pf<{cUY~naRnA zZN`_LrjvvL^#{qAWqz3G>b`%z{)JWEz+gQ7;^HFH!^7j<^wd;_ zm5og(jcVUxn!FPAl&eOFdsn$M;0fv6{SZ~lV(i7ab%Xm#_~8CZ@6Fx$X5RC)p{q*x z_~EUUm6b7UY;4%-nwreb&CPez)YN{TA*wc$kbwjAh^;}f(SxtwzU3V)cazAtxNL8w zi9IqeD=6>|4eILy#~x(iV7w42((r@y zg67>2aeEKH(I>~0rC10SiHEQxs4isqgdwMV8!Mzsxb#k^5o(`BaMVUypk8LuUxuF= zeDj*L8}3~ar7YeCGq08tXLzActWXQe#$#C8bC_-s;_QF!3%k^SVZBd*ocjoi#a?BU z@W?ebG4Z*C9a|=*j0`TVcg`nRY%9T-t|@rI{uE%82oqa8LQF(qF63rngge(DJPte= zxByRp>)m9*QYLox_xh~?ShCBdlL0Dh9UHX3*nfLrC?>uSyvZ;&G8z{a5;80L_|f=q zYoe?kNZC4jv8or$<4I_(^w1`iS@MZ984z~`c)FL@ur&RgIk9SLZ-d~Es2pY4#7Rmz z5C(h$ZSAiX+1c6gIXOA=r^m;PmX?;{J>U$5L;oAe$whwn-be1y42SiF(5yR_pTKU5 z?}cP!ob&2sd|4Yxv+eBYI3{YK|0`7=wY+)yfXQ8coSmPazubAM(*E9sEZF20_$cC! zjjuD~FEfa))`#frU1BUdP>>y8<;@cC8H@l13lWFqKIkIS$Gig%S(AF!?2KUQi)}$L z9eBcPk%J<`GNTB0-oJY(qBGK?3cm=<0elRaX9_#8;t1 zoy|6Q?u@*XuCleXqzEf)__4Fok_b{t1eWX7zGi;0&>B#rk#R>Ks2LOuR22{q*kxwC zP`FGR5U3nQiIJaJY`{lmS^rS4Cr^f7CnwJhwY9m0L%%8>%i~{=eG5=RBCVb(EBF2w z$&`I@);pII(zF5s$@=~K_bN^fj)EmJQ&->FsVNB(lQ;XIx-HybW3%D`{vs(jQwA~J z2C3v3JRVZNsT}Pvnm-h>dD;!k@H*W5hWhsMpB}0lG65G-gI!%Vx4=r>0%d<8mMD3T zec1wu1(4P69334?TYL_0)UHX)GSxj=AkzgYUMpF?^e7`EL-InQkp=9GeHs@LF^N}m zAS>E|`Q(ck##2Xi%<>-WEp<|xo6o-jA+WdMI> zgB5%#<-OPTxxb(17eJ5oo#S2zPoe5#0-3-a0i(H?GFOFbXA~y~=y-i2qm*9sNalAC zaG8{}H0CrR>mElXr5TYmzA6Hv-M{(Wr3;^qtKGxSy9sou9E*0sge}rHWAzM)K zB9Pfj1BXBumP^Qa-Ca=OyuBA#jX6MTHHU`cj=bX1N(K0R78e;QA@Ou)_M5Y_Gb;Ya z|M>{@)9>_1(_uJWf&reebjuFJ78?^2v&+uTzMK2ugN1>Cfy^)@QbPy7${1K@0~}=C zY|~GbA>p~kEyB_x7bfOBnRWj2ySj_J`#u3YL50eDZ!zd7Uo)-xWl@bIwH z)YOzDf@O|Br`P1K8;es|$u3d1H^I_v7Ds^)=gXE)kTfKRSig#kWeF|~oq`oxhVT_~ zP2vuas!&Lw9MmX8DY`K4*M zp1tuRVoefIu-<7F_y!Mu{lXAEOEd`)u{=|VYjL_cOd&Wv9g>RgZNEyG(u1h$NqL{k z!DDM&tHo0i)cq>bqCfs0!WqQbSb^-lI6JAj0b`@PETLy2LP95?P`br}aNU6AQgJ;N zOnV8{vGKPCZ|?2w9Sd6b#002#_+fVDLNqCbL<9x*0#1G^hC^%0j(cZjPH&Ad)n(af zSH+?5i$jwI1@rUsg#j2~%OWHDL&C$uPv__7B|$RW6_it>Nkp^%J9i)vdLVB<+AF*Y zskt2>lwg?Dt|Vn?mo?#3P6In;pdAD<>_ZBzdzO`~ptr`i&j9Xte{z|r9kOWkm$tXD z`4tP*@sYA*aVMj?gxX7tISPc-D5>}1VZnAXl~q-p46=;yFSNTL9TZJXO@AR+D7D<0 z3CODY>CKP?SP=I4qN1Wq`@kfMR(cjrvZ(b9_ol%DhNItLO?dEH^cXW*^S6 z-hIi~FYA>n zN=`!Jt7mBVR1b+(Genmj zuhcplp2=do;hUh(a07*So_EV#tk9IRC6u zx*^kJi+<9C$x7;0V`)HwM=nPG+ZB1D}p2J2?CoBwVyFcJ>LcF{K zb_*@fClD-$-vcjIxpY$;Wq%taa&QDVTkPcc*j(Shz`Urqcs1Z)jdTSRtH`e-KQ~6X z-uA{bzt?&uG}ri8J*%|z?h?=&2cFPJE$+3RFb))`&dbfc1}xBS1k~h@H<$wgpuLw%zo^9Rm+vAip%=;4!9cW zgrJg=lEQSg>&B_jN~@rZpikv(M1M6XPL(7M6r?~g6MyuC@J@aH49h*JZ5Xk#zal9~ zo?z6~n!NmOvAtYya4;?YQ~aA@cYWOaZ##9X1*N!A6v225m6V8MbL($%?+afm4B`&5 z2`BRA_6hm?9(`$n07MYqdhji4WZ&zt@}GKz(-LU5FR$1e%AAh3pO9X1FD#ag`lS(_ z!1?dPXf$vTGbqvzq9ZRrSU>$YHuh<-z5M}prI*|Y7G(Q#wV!x)c6O<@wssoihcZ3O zz*%2v4d53T1>h4U*~ny*g|N7is28hK3p5ge01A>nDOP)-FdUtX9sJJ`uh5l-mF&v z_mT%}U^899Q;MFYsL%4s06+)$#AH4`76kdp-6Y*_$}fqMT1 zH9meH?yv9N(R5`tmX`JKKVI&rJ5u@q>JV)J_Cj;M+F{h$&~UlF4foU@ZX0c_L!s z9l%>$e}VVAvyVJ4*_1L*qjMnzixLL&^M0zFDa1ma+6&Jm&mPf+jUlaU~=-90$|o-u*mc1=%|TS zz+CEdr8iy`>?#JRf2-hsIV{IH9{|aF@v*QlN5BU;Kc~L2T!fB_ifXzG*d)*^K=Ywh zFkaR{FdoSh!K`5k9g_EakgD=>qjS&%Ig_-dWKmwNc3bL@07s0Ao}vg-Py_jKUqb`# z+)nqm?scS+0I*J9-r8zP1Xkhj@@_3nPELNeA9+{u8*IQLio=-|6y8z>wWYTT2*5g% zlE#_=7k>bd)NUxO%j_qSo0$0DPHJkZ1i+s7ASE_(9V@F#1NwnhcAz`ucjWgy`wYlt z4B!^kwY5h(s;{mOUG|fuoNu|8o}C_TuXc5HH6W45F_XlT44{VlrHVw?ul@47I;e`oY5Y5HefICmB1RZjml;ti&f1VtVu(Ydmr2I>_wpYqFW#wjuRF zTy(g#ZruNj4!8Z5YN971Tvl9mNNlt!9UG)yQFQPKiW;8=7;mP=f=`AeGN37fob;EbV+xjjm5{8|d z;_LL~f|^^Fm(#dFWwMQP9ppplpHS!1miAdbeclVhdZW^?5my*4wvV_nH{?;c%ki~SN9H~TOnWfF>rbHF_EX3Cq+|r+ML{MiHl;t*^Be6vPnK5fqG-ql5J?<{JlB zMHfXxi$@YhS>nIQ;i4v^M%Axy=UH0w&|V=q^v~6!80G(FNl@d74Z}2l55bTnWe%ec z-bA97#~f=eb`E`Zae~?)k_!@kt60N&tS0-w_n&D~&b34613HNR=gPAjy&DbrqdfB3 zWAR>h&gCdVZzJG+$m~U!Wu*~c6IP}Tb#GI}v zy?3`2WiV_o-AU)@C+&5ARFTHX4}F-!{m!dtP6QHxVq5W+^0%&{uS z4Z&iQ(ng$u+!=di1(vm_vyyGq)QLBAJ zE54J8(@Rs1uh>jJKJ)RBdN;|h@x*fbQ|pphnGaTn`CH4NT~!Qs6fQF2gTrr1$h)Po zfm1EVU?`-*h|hI&qdK0WU;m??&)d5nl19)ZcEa@5e6**96I*uS53YH?vv94OIW;e{ zFVeel12S2`r%oTM^r78SoGDhx>9uX<(doHKVy+gv(@ixoJyt0MC-A_r=C@GV(5z3h zISvF@S&9rFCY7va_O!`t!mdi2Cc1l9g~LPWT721=e*nvyfsC-uC9|S$Y|_`X+{76_ zBf=YOjbASX2Bc_Qr`8-b$+%m$EWVR(Q1pzEtXH+cw3hRgYVqg>#n*>paVzyanIi)S zCydkEwbx&&q$p~8-AdD>V(o$jW^qSZRv4kgWr}Vw#MUnC>h60pE-u~*Kyz{Qy32=B zSl5^=d@|qVg%r>zR#4%(8*)>R1+)A>W+(K!*0mDg1VJHu0e^BnJzd(V;~sUm#Z{N$ z{I5OL?ZLX)VaqIv>ls~*k>cXwlCzmCrjLJpz)csHXu!1!C!_3ABxIV7I{qr3{p~+0 zyb1-Yrl)tdf-rk+r|@2KmOGgvk8Wu;@lz8Qnet-@=^kK-DEB^l0uRKk^dH{+6L%AcuU zq<^K&^LASEPZ`(rPQ_`u3!epfA`i$}#$B#2yBGt=9v#DMGXi@x50&1n866`UVDq*5nR*+(yZG1R0V+kQbi74dJ3k2hUgKJF+<$Vk>`u>?e0w3?f#Q8j}oGsr%ko`v~5)UEv-w$RKa>XPUrXQ+_(5*I@9SXZg@5J*;R=^ z$}0I5wADs<@<<0N8NPd*-~_%JL8H?>%SJc&I8{yBFLbq&RExefio#d2r<^Zn1R=+) zp~vY5X!A1kJ+%cdvxRX{7YqGwtCoU+=w~=>buKeOE8?!#U(w^#KQ_aVWOFc`v@lx^ zl_`QCB-&0w2_}wrZ~aMA*uHnVP0U=cRfR3UF8Or|?qoHA^aH0+(MSJnRL{89iM@ZO-ul<8 zsm+cZmI$SVq(sO44@%lmk-;>SL@G?dZ2YBham@A4@c7wM%-q~OpX}qH#D>1GxpOZs zTHG(lbGe6{?#aEmO&4hvy3-tthTGqf>V+&a#`lUs65yTARQ0Yp%0Zjg>3e{_0Ntq!(=mM24p?iON2q6g8jv{%DINEWv;-dO6iH*x1dgQ;T}a)}24zXojsNL%N8 z+jnK(n^2p2%80M0-jsjWu8K8nXw8~~it;9HbcrdyMZOMC*^pfbs4Wjg8LT@6tIz#+ za~*!mWRCF8xlRlpe9|fUHh;fl+_S>y7r^ywhg8Z~CD~W~s|?msZ)}Yf37^9td4-PE zIv*K7jBeb7e0rPTFt`0A+NXKAdd7tT@&f>&%v+7YyC2x?gx{Wc-Qg6Elf0R%#*A2* zX}8Q`3vDqdO6F1h@8&?|Q?~+ze{nDsa_p3&U?g0Hh5NB2+@5rQOTm}OT<~?_C*E@w z=sD*c(+#p*tNNLyH0sx}VU5(IKYr|JG?h0c`~vqy4&XL; zi{N-YQq3GF#;|N4teugBv2SnPk=6DVuA)mk)%ooDR&$qQd+>+cPt?zh)gsJA!}#wM zojY9!jJj3=O3h(~3k^pzA_@B>%IXp9>7HqKL=*_2^S&wc9NFWy3uCS$ZWBN_94tD^i9j|NSi*-p^-~KV`y_{KVlSG_W&{N_+c9}4L=y( zqG}$}ZC%2W@^I!RCF!J~>35}PYAbdnS#hmmRCqqt2y0&1YGe!b-h5-*ZlYe2?9SLP z@xG=<0#jA@=SE>Ljg>4!5Ta<&KgQ9!F62{Eh_4XbR);^sn-JLZn%CL0CXXZ6#?{J^ z$ZiC?dXi}f)yf+K@-#=i7HYSf(vE4MdcE$mNJ6f?1RZ|UCdZ4Ye20Rq)Rx_^Cc0`5 z5co&WdwsPg9z%?rgnAM@FPwGNs$B*eO9u~PJmcIMBZaI=ntvRDfeW}^Nv$C55Tr&i zb9+SjePz$C-pLu`Tb`b=2`gTNN<}xRpi0@fsw;RXe#v!ey3MKX&QluIj00{w?lLnx z+`y}abMW!QGss<`9(6TU)arcg_SUOYuJe^GaKorZlt(>o68<17DHwBYu{o11SSZ|` z5t@R2OM#=W5Bw!z|GVXb14F)pmGSG$Ch0#oHI^TVY z<5`Mv)5Ep{CYxO z7m<7WVt<98v)LDeXz@Q+t1t$VfQN&JkbaBH%Z<}>bI&h_l8=my-}Zo(vW8y8LVC!1 zXI28tgYofQTwFX(bJ+3AtExn}Z`=@eb9dKv{yUinR$LVT9l0+hC8Z=7EDyhvdZuIf zHAgXbEV+E+`fG5JCIZG3e!`-nDA$TO9EN|RpT zpjUl!qUC*6bQFB!be*k#*#X9-F>l|VOoCQc_d?-%mg+})Wudp1b3-f^`7Yu7^72GU zU42W<*Y|V?+#&qz?(S~5I6s|;f(na;J272%_R~LFY!7)2yrENASeR*RV`CW|trTr| z=h6q4Qp`_@1gtzgJf0|_P^Plpds&XEs;VqtBtZ(h=(D+;&EBuFi)93NMAmL@f-4X*MH`39G35(&rKJU`^@2$b*8P+{Rxcjo@4O4%^|6NE#BooX^ z?mXN0FVFem%R#9xio+`He--GsP8dIURm?S1RYj%P(#A$=VQPvfT4Mv7ApiJ!N+S@Z z92yo@0!Ekh+n}%hDQgpX4TD|st=-J({3n4EI^gi1VX0|kQbK}JH&CJ_H`nGnh~x@b zU3SXsVGuh8zQHC+?$-sse7Voc#f1(7Gdly&^jmz#?#XaG{qo?SEH1G36u7bjV-2;^ z%jt-vlT*`HSL78YFc*mhmIeHL$*xQr5fL$KXJg|h=rUcch-Ka4d#23(vgL1Jz`QC3 z%c}6`KLLvt_`&|ZaSu=_3>>h^oSbE-v~+U_xMDy9K1!wu4$LsnECj32$QZKpX`eC- zpf!jJo0l1GaoFb$hy-E#kG_}ecYzm^W&O{@!PK+Uf`lX*{p{H@uIlRQzD92w>U)>d zhs#w*^W>Ak#!&n&2E9oflSx)2Gonx2$LarHfI-q%*oy=c`86*Ws4Ct?l_D*J{|8nG Bj@$qM literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/sdsmall.png b/docs/busybox.net/images/sdsmall.png new file mode 100644 index 0000000000000000000000000000000000000000..b1024501b624641e42d6e9d1e0b3d597da84d93b GIT binary patch literal 1593 zcmV-92FCe`P)RZwdy$XI-reo)?=(zKEi^NPhln(j zl98jMB0N2OuCH2$hckwTIcsWQqNA6=z`nx5M5Cf|qoKCg*L}LV{r&qVOigCGyc%3w zLRwm-rluZxd1QHcM0j_$(9vmXX-!X0B$Jb(wX{Z&ksKu?8-|4%Yii!#-(Fu}M$5SU z00009a7bBm000XT000XT0n*)m`~Uy|2XskIMF-IZ0uVO?Vs~&a000F1Nkll@j_YVZm zGejV=aPahYAGEVuBmf1Rddl+(|RVF8LX(}gC??i$kwyRc3wLXDe3A@v}SsF zKqQqT6%HDjuG?j6u9*=@GDN@ntxcW{Y+9M}~7(KM6xt^D2Fn(=*6F}ZN{S(be zpaXi?8fj)GDR0n70gHJDecf*%=B2F%oNRnwjg_N$YUg^{N8bV-<3j zw@j@6b2NMQjVO-e=vv;<$14tuj_xAkAG>iJk9U(AopPE`sSdG>&}oZkLPig-@O8uk ziJ0JlLR%lj&>Jox{Yb3io9Kka}DmKBAiybc#Qc=I?Wulzibr4d)BEF+{Go&0#6%m3b$=5+E9ubmfbq$F$ zR|#&TURORCfLt;MOFQH03shfEL=%XK!)~-rYq&CnveKGc3}siLMYX9*ITlrLD(dQv zWUNHpq6$v-4J$2#c^}i<)~Q<{c!p@^xT;Tp5w-%X4vPE<ZKY=`on)7pQ%&>?w~s(uly=8t+u{Eimj@>qMgTE91X9khZcl5izL zq*Jxe7|WruBkFuTn;@PQ4<6s9(`lCs2PLcj2aRtxpes3FU`6Pu``|+licM$J%TUv! zeYB!C68}Um(6okU&}CQL+pD?}dGht<`&WwF_g?+@YBC?PC=aS{qQ{-bUX8`X_tttK z)v~{VcIKF^&0VU~-t!hZJP7Nf{stN@+KQj_x5Xk&*J+wA7EsOj{YHET-6qHiDbP0l+XkKRKIJp literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/valid-html401.png b/docs/busybox.net/images/valid-html401.png new file mode 100644 index 0000000000000000000000000000000000000000..ec9bc0ce00eb9159f5ae8adfe5551bf889c81684 GIT binary patch literal 1950 zcmW+%3slqw77ag%Q0w?xM^SWGt)!xo?)n)(&LSH?#;G|l0JLOSyfmHdB907eslFbIOO z0HD`_4w(R8T2)n=fHk`vKuiuB1reOXN`fe3mkGPwZbVQ5$50d{3Ct?uya};scFu%o zGNIZuiA4dFQ3S%E2XP#yX&OOMBgGRKq*6u>0AP~0D}>$QK#h8t$9V>4C?kfUG$(7C zCdgLKgky+-msCB@lB^`FHlq=xNlcbylonVTV+2KHQHjGv76)Jx0_znXR}=-qFij$C zBH>U8t4*_t1cior#wjhQUz-#Yr|6#Z02ip_*n_1wvpDBdmAA zXfz5ePUA3wAXb}AvD#P)mLy51)7d55YPG5Y4jFW;sK^{OBo4*ATfxcF^651DN4d|mB%QGat^@==_Qdf86i$oWgMa@5(EJb1XPA0F@q`+ zxJe{XgOgFAX_KHBNghWGENxV1qYh^&9D)p>4rLe!RGh7_v|!K)46dj)7=~GfA{a?R z4Wd(wdaLk1uncXJaD{|f5^`viCSU?da13sf2rJK%I3#j7k3pK`q;?jl5|6V{OP4x# z&NUZ{iSq+~Hk9cB!2Nb~_|o|N6tR*x91yMBvoVfuZn@H&-ThW{>iX{H?BR*o>1%qs zp3#|k^G)Uh>ZHy}+T8Kl;%E-D^v`)Fx2)cI@0(E_J6`?l zaLvr|IiDQzE+w{D=T@@f$ls5}b*}E&^XvF^+d~(>ySQLDHS>XYiD_->=E*^mPMa%U zPXFczSOy%L*;R;O=cI8}m8Y}1)K^{U)BI-rkhL(G$9>?L%l%V|ax1Zd{!Oz>W(>4^ zH9ziA*pYk7vLEL|Q9hxAU+}3kmIlSFTDkICo0+Ysg`11WD=*yVB>kIj2;Q~GFC$Jl zJY#KTb#+B@n|@(N&6s_0&rkHG?0-79zdgbiskwp&wd5~pA9iUiJNp&+8y7!1r#1x4 zQG;r^HETN3j<{0lucth}6`=9%Kw=QmsxzH{>Y z&<~xRnJpRDujWcmrud$i6=ci${Cs?LgJW6u{T<9FKfWD%u5NbC+4FwE{&VL%dX-S? zH#)bYrN3-VNXYvUX?yym0}mVK^|;*fCHX;RdseW}wB|NMZm!AUAR)pPc`*>@%)VdWqaI<1)c8MUIyE^UdKNd7F-ItQhSNYbE38#k{%yKx+#s>v? zH6{!U9y~VGKHg`=!agAIyR?to_wJ$+EZ#lAeNgl^)pB@7{r04RlIr&Q_63Hr>3Mkp zogQ@`4tCG{eahpJP)Sm7i zt^J?z0hV<)hH9i<3%IKMq?Ys9(d%(-qb2?MmchKjk6@oFA#=*P=4}tRZW%lJ<3hv5 z($+JVGeun^zxc$d$f@X=GZCvbt*u%dtUA=EbJa$fg6oRQu+xn>T#s zd1xq=TKDdG6#aE$l1pHkTL*e}=ey#yS<5cOLla-WaeF^G_CdjLzWcl9XWjZs7amn< zzKgp3c*33LM`-4VXFQUV=U+L;JAPN1yQX)0K#Y2NId00P8OOrLrJKz~Lz@Ri_WMtK z6EM&8?Y7_yd&22$$Zk(t0f>8h2Y`i5gaJ<(WdT7~kgWT5o-Z#3^^SbY* v^MM?5JbC`-MTeTAYL7M)z8dNB!8>NvU$=eYQunR%)&bEGG2y3?)Z+gFn-;kV literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/vh40.gif b/docs/busybox.net/images/vh40.gif new file mode 100644 index 0000000000000000000000000000000000000000..c5e9402e73780c2174188f521bdadaf1f5d86e2a GIT binary patch literal 906 zcmV;519kjINk%w1VORhk0Q3L=|Ns9pGc!3kIZ8@OW@cuDgoK%ynas@0nE(K0GXSNG zW_xpHduB7|y<`8(X3Uv0y_`xI7zq0R|1pfU5L$b+*5_lT##)@c7<;Wsl)V6EnS{00 z=kNEuy~dd{W&i*HA^8LW000sIEC2ui09XJY000I5;31A=X`X1Ru52m2?*YtoZQpqA z?0oP4z+mu^fW>miBvQz1I(I~)M5e4(pV%z4Xrx-dI3EPUYm6P^^tgdum(vb2+ZXME z?>67(!-ISR4ts)o41t6^0(*cAABA*WFkX0fdVD*6fMbjiZXXezI}M(H4vdaHb&)h0 z7!@-W8WS8e92gq6xEC`W0Rsmd1vQmvjDVM+t}`3A7~Qwl z9iJZq3lYLSgM!9r$UA?20uB)Yn5B)-kkSC#88jEwG%RSaz=6ZR2>>_@_&}h*rvc}Z zeIVAZUNS(RmeqUqNQxH@tY!fas4L+F4X$ecp#;!@;fwBj{M&06AALj?=fO~{B~$@_94wjH1ccRa009HoNEHtY z)G1w^>KRC20T5ufAbmx8Ak>j%PEy_v?FChUdpn%Rs1Vz!wuMKA;voce*--~+nDI@z zVx?awmIY&-dI}+^hl=V1gQyUSYN)DWA!tXRz6xEeiIE9F3%sK6z^}jtE9|hu7Hce_ g0VJ#JvdlK??6V0(EA6z@R%`7Cwb&_?;CY zBztGX|Lb+H=bqQ|JfG+JKA-U(cX1ZxhD;ZEFG3&?CSxN#ORz_RO_ZJ%d|wEcBZ3`` zla84V1X7XAaQyH*IHt9~X{ZM|J^Q?AB;d~iJ&o@BKp+fkXB$-*29*L%()k*j>C=(V zQ?s#4lXfgY94or6?rOG+E8jiN&M@M-?Rt+(aHC!Ds@Z=XWpl7x>7<;iI%ZNA zgwG3|*RuE^yeTBfE}SPl<^qVhar4+^O4;1Km0brn zCjq8YdSbXcF=lGWx-QoeRAp$}X5;ltz3e@|Nl3Agf2+UGo=+K1%iwB>KeyP<4-~kXT}qwrsc@YBlYd82a&lsP%y=Ww@6M`vg||NwiAdgWzhgA_{V@bD#&cyUclW^1dCUEs|5!^6YB8*MBHYXQwY9--TpeXm}DfQW@Z zBPD#=p}GD2=3G$d0f@-T&BfT;M`Dyo8ykud@bK5K`8zVgc1uuV{K-QjBTZ)4#!8+r&xb~F3{wa6;W1t z3tQWyR`kz*`_mHKjFy&`k%IJTX=xT#R#C{(Z&+gf$m2Ti`HS1z+ZwOTX=!N#*Bke_ zd3f>(gcu{yNMz~4LSGIeBjXDr(KO8td!@spBOU<(Cmb#t%RlRAG$yA(UcXzk;hkInJAh<0$ znij^-g2&_A?Ug_s`}+D|M=L~d8N3evZyd4|!F=UvJGs!1yyQDs=i^jDLDEqvp3}l? zJ?MINb*qX~^1hF4tw+y((6ds~R0d_nQ)43##>m7ZWXzs?eUBTopncAZnI9&}FzCF2 zYh+0ii7%M-`&d>cY|LKc{=;(Z%LDlx+;s@;(};qC0=K#tWGPppT{2ks&;v@1hBS0N zzjw1vIg6CS}qiv{KlqoommsBw2iLrt(q?7>m<1f^E+NA zslB;4=3ATM=Iz)d|Cyq-Ox+!RB)451TWu|S2AP@(P0ybY=9qJN1Dz4)=&DziHv>P7 zxgO6#n*~|vzYh=V+1OleY;4TE>K6|eY{^n_`ny{m);Bph>0iA4{ml*W%J?KvXj$pO zVz0}%OWysGe|}vF#S<9W{1>wnA(O0z%q=Ej*c%)#X*A%3fXEV%&xxrw!VMI z&hGiplHMhKfE~}e2Wc4@?DRB7grSx3ssv|M6H)foCEo(Fvo#VaoNgkuCekuHE6mBs z$<0XXm|Iz?+>r>{u&}V8m}Tl*h5S16X;A&r#fNG+0>5X?OieuxcODIQi92Xls^MrN zW)(uHm@1>8^eV~E3rJYwm2rIog6@qy<;s@7!l=mm++2e&3$o2Xi(*jwJ#PD*b%(=1uiPD+Ho? zN#p#>KI~%`7xgDokE`7G(=9~8wh-_l@~6{-#rH5T>KE!TDkE`r4AQ2;*B5=*%JO1z z%}<%Hf_t#er@hQpB*O@8=+AaW*);${qN zw4>2!S>Fe&1!fa4!rc!8>Ryt99F~y{K|FV*5iI=eNAn8jB<-FXq@q1pXP&Z1;Ij!A^1#it8`K*8;OwG)|sJEhDfVHP^%W@eR z8DQv(h(tUflJSWN%0YV`l%^$i#Va=a{w1C-{z-v`bej`3BuAq6)`uFXk0AUiUZ=r3 zYQ!SeRqZB>->L3P4m$W684mH2j#5$>>M~$D8PjxKeL6NkZ(AE~UZ_(}!3VDX zZ|y-{5q>W+YJq=#@rXTD%Z`jR$n?Br*PB7ghCHTmV z3x~5{VkzvG=t>RJe5^*RaIKmwBO{ae{5inxyAL0}+E=)a5m@UuC+ix6_oai_!VeCD zK*bfg8Sf$xl>j|p>XD@x@$naXu`1!SuU1qg#~58MG*Nu=c)8K_&3PGwJnY`aL+^vS zL;j#=9Sy1E|7aN)J_-g4oaf$N-MOwD@R6L5kkGKzEpxnA)9e@;YRBcSIygMsjylW}Z4wD)vTDT+kOkItpkS*rNG zTwEOVxW!p@3owR?re>TuVU8q`hrwc_LC!$*fzAQgf+0#^ zBPq?>qBRY@zp7k%c`siE&F)^r{X6vIW2Q?=O3I0~KFgx$ zX6E-L-{T#Eb_bTYIVW)XGFj^|HX?#b)OdDpbpi~2=oUWj=PvGGh9%6%$!W28iKA&RJ z%2AmZa`Q%ip3~l}{sE14rbwzezV8(Aj^XWq(8OZjrwHeSj>v&U-I9^5so?dB;C*)> znwo(dZz{8XutcGCQ$^<=u>9()C@W(w8TmjX{kRGlv{^-&&=K-vqoGPSDcX?>yykN5 za+1N*G1t(ar`nb97fHNw5ScMq%sQ@HIH392S0Q-BFHW^vhi&3|B_odkjz>mD{{9Os zAWH%V3X0%n94<(IiIY=mmqDQc0>5_;3fy2Pf3PX}@B8;sgOA2aS!kawtF@J))pC?_ z`t*d^fRnMdwbd`Otf;N+0>bBBW1|U|l>LS70rnIy)w9`Idtkk#6cjq`D>hD!)&U-YwH$apSakklcGJUeA9v>ec+c|f}U4@3p1t66rUtob8*CupC zQBdm4uQt5fdI?=Qw#v75u$QN?wdFHg2O%NLHq*lV$>rB7E3IW!)z$GNjM@{bQbdVf zgu44Adp}g=1qE8{N)Z`d^#_{d z|K(Li1{Yw@8BcEDWps7Vx!2@?5=6(uu<56qqhsFaRT;2Ap>DzW3H9~abDd!MPowe~ zTSc)+LGd&br*o!M6I$Je9$$BMRGkK1|Lg74^jTnxfTNuZ@1L3hdobIlK7HKVdHelc z8fq$Z(vn8l39R{eUO{`i^_0TO95c^gocfl63bXxCUXIH7-*H$_?fm>HgkRaIFr}&Z RIrtX?VythjS9Sv(`F~ZGgO~sS literal 0 HcmV?d00001 diff --git a/docs/busybox.net/index.html b/docs/busybox.net/index.html new file mode 100644 index 000000000..1bab6b069 --- /dev/null +++ b/docs/busybox.net/index.html @@ -0,0 +1 @@ + diff --git a/docs/busybox.net/license.html b/docs/busybox.net/license.html new file mode 100644 index 000000000..76358bc65 --- /dev/null +++ b/docs/busybox.net/license.html @@ -0,0 +1,97 @@ + + +

+

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

+ +

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

+ +

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

+ +

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

+ +

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

+ +

A note on GPL versions

+ +

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

+ +

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

+ +

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

+ +

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

+ +

License enforcement

+ +

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

+ +

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

+ +

A Good Example

+ +

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

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

Related Sites

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

Mailing List Information

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

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

+ + +

Search the List Archives

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

+ +

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

    BusyBox 1.3.0.

    + +

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

    Changes since previous release: +

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

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

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

    BusyBox 1.2.2.1.

    + +

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

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

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

    + +

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

    + +

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

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

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

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

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

    + +

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

    + +

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

    + +
  • 30 June 2006 -- BusyBox 1.2.0 +

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

    + +

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

    + +

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

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

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

    + +

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

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

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

    + +

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

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

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

    + +

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

    + +
  • 22 March 2006 -- BusyBox 1.1.1 +

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

    +

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

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

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

    + +
  • Old News

    + Click here to read older news +

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

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

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

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

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

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

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

    +

  • 13 October 2004 -- BusyBox 1.00 released

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

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

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

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

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

    Have Fun! + +

    +

  • Old News

    + Click here to read older news + + +

  • 16 August 2004 -- BusyBox 1.0.0-rc3 released

    + + Here goes release candidate 3... +

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

    Have Fun! + +

    +

  • 26 July 2004 -- BusyBox 1.0.0-rc2 released

    + + Here goes release candidate 2... +

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

    Have Fun! + +

    +

  • 20 July 2004 -- BusyBox 1.0.0-rc1 released

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

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

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

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

    Have Fun! + +

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

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

    + + +

    +

  • 13 April 2004 -- BusyBox 1.0.0-pre10 released

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

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

    Have Fun! +

    + + +

    +

  • 6 April 2004 -- BusyBox 1.0.0-pre9 released

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

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

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

    Have Fun! +

    + + +

    +

  • 23 February 2004 -- BusyBox 1.0.0-pre8 released

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

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

    Have Fun! +

    + + +

  • 4 February 2004 -- BusyBox 1.0.0-pre7 released

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

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

    Have Fun! +

    + + +

    +

  • 30 January 2004 -- BusyBox 1.0.0-pre6 released

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

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

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

    Have Fun! +

    + + +

    +

  • 23 December 2003 -- BusyBox 1.0.0-pre5 released

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

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

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

    Have Fun! +

    + + + +

  • 10 December 2003 -- BusyBox 1.0.0-pre4 released

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

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

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

    Have Fun! + + + +

    +

  • 12 Sept 2003 -- BusyBox 1.0.0-pre3 released

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

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

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

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

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

    Have Fun! + + +

    +

  • 30 July 2003 -- BusyBox 1.0.0-pre2 released

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

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

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

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

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

    Have Fun! +

    + +

    +

  • 15 July 2003 -- BusyBox 1.0.0-pre1 released

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

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

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

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

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

    Have Fun! +

    + + + +

    +

  • 26 October 2002 -- BusyBox 0.60.5 released

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

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

    Have Fun! +

    + +

    +

  • 18 September 2002 -- BusyBox 0.60.4 released

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

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

    Have Fun! +

    + + +

    +

  • 27 April 2002 -- BusyBox 0.60.3 released

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

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

    Have Fun! +

    + + +

    +

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

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

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

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

    + +

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

    + +

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

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

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

    + +

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

    + +

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

    + +

    Have Fun.
    + -Erik

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

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

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

    + + +

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

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

    Have Fun! +

    + + +

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

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

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

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

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

    Have Fun! +

    + + +

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

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

    Have Fun! +

    + + +

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

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

    + + +

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

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

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

    Have Fun! +

    + +

  • Busybox Boot-Floppy Image + +

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

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

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

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

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

    + + +

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

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

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

    + +

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

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

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

    +

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Products/Projects Using BusyBox

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

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

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

Busybox Screenshot!

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

Hall of Shame!!!

+ +

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

+ +

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

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

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

+ +

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

Sponsors

+ +

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

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

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

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

Accessing Source

+ + + +

Patches

+ +

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

Anonymous Subversion Access

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

+The current stable branch can be obtained with +

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

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

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

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

External Tiny Utilities

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

+ +
+
+ + + + +
BUSYBOX
+
+ + BusyBox
+
+ About + + Documentation + + Get BusyBox + + Development + +

Links +

+

Developer Pages +

+ + + +
+ diff --git a/docs/busybox.net/images/back.png b/docs/busybox.net/images/back.png new file mode 100644 index 0000000000000000000000000000000000000000..79923869bf32cfee14ad6a1d8abaee3e9f231cab GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0vp^B0wz0!VDyh@)w!{DWL$L5Z5#RL15<0nQ3Wh#>U1# z;af%-tU!vfB*-tA!Qt7BG$2Q;#5JNMI6tkVJh3R1Aw4tAs30$0!AQ?U&%if8O$n$X z45Y$2KQ}iuuY@5aBePf`v8Y4=NM+_Jlw_nT6qF|AWF{-5LrY6jpBG<_0Bwl!ba4!+n3HjE z56cCG6n=Xv2X~DkJ%zLXR^K>M;kal0W7Rn)%w^Z`N`!n{>X2Ea;q~=r!R9*>w_je> zKYM2N$we!&X130ZtPorq%zyI!&fmNCsg_AjjnA7eB(KAlGItix76wmOKbLh*2~7ZK CHF7rq literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/busybox.jpeg b/docs/busybox.net/images/busybox.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..37edc9614648070b96a748a08368c168eb5195f5 GIT binary patch literal 9023 zcmb7|J2~_Kub+cLrq0XLqh`w)6y|;Ff!27Gw`ypGjj;?2@47G2?#*M z6{H}da$*7kP&H|}>xwWKOjt_ehPslrf-+3$e-H{V7|cM=z{AMMqa-3AqV)f*t04d@ zEd>>YIgmmWK*>r0WTm(o1qc8Dl)!&yPyqg)C@29y5EV5I?LSwW1wa9$1pQx8Qv&}* zDE^HCML=wda4JieKz7mCTxzA-E)FrP9U6q|OHSpWh4W9kaa=I;ziD0y!2i(y5B$GQ zLHUpU&yrvTP*VO|{BJqnf7t(Pv9gH(72%d3c2So=4n*wL27uvz305F0;1=Lh$860ot(6)>&o_oUb071=~4457S=kJ$Q%l;mUVLRK~CA zh#!}h{2C}_HX%rUL}$y8w@>Vp z=)L)YNpOBUV30C1GUJfP2A^;BQI;lKg=Q1w?61)%dztM6ftKz#ZB{;} z2~4=UShqZW`S&Y09gFRULCPcSHnN?dgwZJLy^CsVP~IJ?T} zK_J82?};8G?~&S>d4p9&h^zpEN;XU zic?w_%~L#rZ8)dg;4~wYBSeg|Cbn*HDgB8aHCA%n7^6~>w{Jc+9Le8`_?Hnjo! zqE2HZ?LOjBK+qT;x^!?rQD}WJG0kiqDD<18L_ZN!wH@*Ou!Z~rclE^cnN=2sI_l&~ z=yvVOd+mo=xOFo$m)qGTB7E3P2iyIxO+e;|4SSNM4#(pO<`PJJsJ+h{1jR{@=I>`K zvY=@k&wTr-#7xE-eaS1K*nr`jUu^KB#-`!1jzy}u759i|aa$=al_s?w_bThVF*01` z^=OX<`~eUkm*8OO5vnHe+{;LN*gI^!`wEEL5UjgJBw-Vab1NcXUJo=4? zf`gqU_l50{YCGtQ)CC}`Y}>o!PmlbxMsT;5A6tBP`pAB}zsfZG((bwKBV$MOLm~fH zSd&VMDEnIs+ote}XL~2&*8yw1b;=5F~F{ zamK5c-{xJx%z$!|mw{*Gfhrrpcf}VUGA1l5M}dT$+In$NY_O|`dsL55#mcd7)%Q5Pw_B{!9XSl{*#0y}F964?shS#v>m0+V@q1t_`3YLtH(8$Mq}EMR_F(XNAN$yH zKC)orR_SY+V;RStuZ`fFSyXD%HO!%H$kaZg?yu@GaXxX3jt>0u=s#=5*~9&cK97lm z8a#ieVxqbA6?l8b2Y1gp?KV~9nZ6Z%wLCwhb2k&fh~38vpFW-++V1?Arfx5Hx>E20 zvhWaA?KRWl;=3!zaeU*|AH_?jHfGA*tbt*T_JiG9TTyjKEMg1N3UAJOt^j!p4KbYe zBk%rIOmEY^z^OG9hF(j+dCuNpdB0HIe>;V{y5v4V(0KMQs@fnuj&`?aCxB-ySu;0M zm=NH_^wJ7HqYMeRe?8cUS1V*LGVfmFpymYzJ;pOl%p?GMr##T55u@mEvsBZpw3%LG z2WWR8W$Dy;IE{1vEq>uqi$)tcjV6ik5$88&120%DIn^Cfxw&PVQ|p)M!seEr|!=}p?l--66P?e(!wqN4_1Vee_L{x%%hG-SRhfb~-C2p&1A zc&uA?rYFB-UQb%#98~pK`p^MOg764wZ;6WKmni!U44B> zR-b>F(_h~3K(Sno*WSPyR%}i@bQSodz~+M5Yeit*z8+&vQg2cyzhlT?!Z`9s*kfTh znsJoKOZ*8-wqbiXz4)7Z%r5hL4CMOHFfr@~Q*Y9Tyy#iM=x9Z+qLF9@o)|6OW=Y_m zHM8nrN0s=ws@f}nrLgN0jm$@2FXFJ{XEjF_#f9xn)p%!@lA}L1zuGhF5haYxh;l;% ze61)gzJZ>nuzWus<`@W9aiq2|0{Z6_^Juo%Ylsvra)^_CWJe{#QBGX><%8brj}`!s z6fB10`bq6xg=#>215b9S9Tn$(f>tZ-=?#voda{XIU02|sxOrVf*CCmDi2H@RH$v~@ zX%hk_-J33~FS$W+R1}vB0!mO(kPH{tJ5sIqK){%8Q0S#q7$0Fi4sFlR!2jRTz;Dm0 zhx{1FAI3*|>kkTTn>>dxe&&UD$G^EukfL9~sb3$IW-3J4Q%&`;4R2(n;IY58n0HrI z7j3<|0H5ZIk3lSV^Esj3T(tAA^S(#uQ*?`5S@f3srurlgL9l5s34 zV7OSBoANKQ%hT$gTsph3(u1PMayJ#Lgsw47#b5BNhe3zdop=?+WincuP_?{afq{2u zEm{?JzIO{=0ru@_x*WB`sdgQ&zm{V7)^m}b9r_WjRoIlJ&?vbF!8C<>7$n1faxO7N z-Vd7l^!^HfK5%VZ8Tege&)qwI&crp%3(7%(PR_y`EA`ht4sIg}m4`8^ZE7C{C5Lk)Nbk`2%ws;Et~b z;eMIi&ThDsrqz`%3lEcSVvAYvu&n!1qYdG9lSOq|#pgeJT0YTz_qqxEio2r^p+0VE zJ-EeW-5mQpDW2#7C>i`&h+s6-WG?%VokxB%I>sv8%`FL#xDLs@ndYH9>AV|~UR;)w zWt)dA)Dj0>rz~2W*c9Z*g<4y&WKD2Q&VvqEa!HMT^uXF@W(|!B>@5aJ|2!>+1SnbL zL|j67)@(xm3J`M4>>@D|5*In;`nCnuu&Bc$IT<7+2{V!4qZ6kRHLH~y^skeDYZ*gb zC-@(2oSKa77eguOO9QwLwT8^w#c%5NJNDXy=iYmd6DG?r(QB!T=Mjna;yhBJPm(Rq zTPAps2OZzzQUoG10sEqde~YFt;|6Dx&C1oB?u-Y9?&n!XPoXrw3;8@J7sWw-90^VL z>p5ij(Sxis)kFYr5Mp+$@+V!V--5DQr+~fNXRIb$PpTB9l|Tz>It{_2SnoaiZ@9z< zb}re|y6C&r6+uIeV58UD2Qitv4iu8j8rd~TyiX#WQdzyLu^(3QR1OIoiM`d0(q~O= z(rw}rhWAIa{#vHThsp;?;lNMHIn=XqliF;L(Zqb;}y{`SKj&;9@E&cAJF zJ_;XtF79t{I@}*S8~FP5TkYr@y6}8=v%v>!T_5ii5KanETjHs>c&6MaMj1{K~KC5ST_Cu5~@ zF0f_I7tDHCsvb*dL?%DkHS}J%%nJ_2xHi|J%~J#P{x7L|#~btTZ;m6jU(As|oJt0; z67!|`8Kjt$r4|7xR2Q)}%hw%wO4wyq>53hi36ilKS!wZm#XifEUvC5{*qe5JJGJ2` z%X9R*r{KTPXHThHomYwDYOTYtLH_#unm+~UMr++4{?aE~Tmeqq0Aik76xll*gnWZ? zqPDjqZ9&3b?zAUg&s9Fm*sn6+M-Jtm6^G&T6O0CuN3cyb)0;p%BUlcIK>L>q&TE2I`iMdZ#;YFpR z&%o}qGHkr(rv6Q|_On&f%fIDq3irI9#7$f7jE9|(GB`^Ix;|-}u>E8TeruEov`KD# zzdE{1*kxM`(Yj`uH7*RAZ+iK_NzE}EoY?0@Jb9mHs3u;nd96v;k9dCSOncVLI~`I+ z%YXJb$RPB*019mD2vA`XNZmv#+ zI+@M}gK_H}y6g9BbJoiO7DF7?Plxq#$sW*hL`YNs@RMwbzMLWM_IP2~nsbz+k=C{B z#~W-S_n8=9oj9LRV-qhXx+;LHHU%wPcplEyaw$v{(9Iv?hF zT0F)tw#{89kq#l7wtIh)=#Nx;wtQ-u<-w3c%bZ zc+J;-X@Ei|=OZtDrDg%X^lAHZCcg5Nc1I?c_`fS&g7`)k_zKhLdQTk}?zdS(yU_}n=dQv znp_R4jW8MsCHo*UaFnV#uo+Qezz z5}8L-II)^@k4p~Tvz?l=A}}fiRQ^m+!&rCPaG){@9ZDuwEb~I`+pIi5pN z^2XBZ9Kqf3eBs72(;;RFwx~Y2HcsiHr{#!MPgttm38jC{5qi|{7Q5~^j@mzvUKNB5 zu9)c%X$^kqTep9c_n^=Ukh(g>SGNrjXB2PtO~@FHRkQa&`;K-?g_h3VQxs~IwrM(E z`1+b1r4@^AO^6akzJT#4J??HUa(qfN5*_piG@e@Qa$@&!mHSBxPGhz;In0MsVNunw z$52n)-Y361&x~NZqpQFO@6mOw;>yw9I*=^^1)Js_Z?Aa< z_^n11Ne@$n!yXou&}$KwBNc)-&9Qb9?@AZQ)~BW-4b@m5n3;Y@m@|b^gITNM*ice- zs7Y6LSXQFVSGldCU!ppcP98JA^$}j9*umC{=<%qAZ%kR`T^#mqutJBfOme5DLiAU? zxE)`~d`2;cu1CVM4^{$3m&0Col$*}a0Wm6WU0b%pgcX+HMsX1cMWps(-u*fR3jtDh zhgfW}-eL|RR=`dso(~|CJCB%+eQ>j?0QEC!j6%H@A$NOpcy_Icz=3gbfsp2)PG-F0UFuv9dHM%qZ%J#0AuW`iJap_h>RiV8rl^YP zR}^?JYThgzQIsEdiC;IV_79Hq!k zWBmj|<|DHdPpkJ-4b{Z`h^!RxDW6Qc1y0ECcY1=Uj33Cs(Yl>N6U7r4h0JC?gUn0p z6OPNf52sae>!QqhWqx7FDv|#!n8qAST2Sg3X+_oQC#7fQhS~uH|6t~(By_wE5~$<& zW46GWF(LFdjlrqUceGqmogtO`G@#%&{2kH8&fyJ1CV` zL?XVzOYHCQCN*nMYrJCi6zjJP# z`LCua=4*Oh?i;eflP1;BH1klRE3knfiH)NV97%VA#X1EYEGLq@kSFB!a*G$m*JFD;6{hAWXT42%{a zdDq;BDORjdMXx~rUC&H+`$e2ihS>}G&F|g^wa5J(xjZTHCRy&3oLqX)?Ef*Kxpa^| z=UY&b?Wzxq{3Xeig^c`g78~-`=3zDUP^U2KT;v}~NIOG){7Cln2uUc&7zO=O(D^;> zK-wzm@N3-T#qIv7hyDh5vD_SeIE~yhZ+&O&``SF64!KuVl^{ zL2XNn7z_?uI;u@UOh6OA8GGSOnqE@gM?9?_HrfscCE8tB3#w%6+wJ9yTlRsm8b-H66K8WIr{_Oy%=)go9rZV!mNVKQ>dSE5 zwYLR9@(e9&v8virjW0XqA-q#lB#(K1w-$Vr!Cnmw6kPGv>!b?FX$0DM=^30bG`LW7 zE)MEwlssIKMVaze^5Z$pr}!wJw`)Z&ax%H$4`l{VS%dk4fLw<%FB==t#<~jFJcDnx zJ>Gpgbt&r;_WEOgif=qhtjl!X;cp4Zr1yz5D$!9_2N*$Li1_`tV&iO_D$7?WZ1R^c z+AQKB^nAwQg(Ts#dbx`5Za8aW_AadRYwd1no;~tUang<%kY)bRQ1%i0ui*DI@+XZd z)#8GDBwC1nV#kXT-noG#b@r$2O3$~P)i&K&YpzWg-WgpQLF4|`mY+SQ`#}Ln$};UX z3zRmP(=x=rub_zHN5!C%H>{^(m@U0$?{JrVc9yL>X}+iqHK_`-%*EtN-#Zi518#p$ z{yEbw+9PdWsEQVz7jb8jiXuprmAk^USR4b6&xjAK!haI?zDOf4Ohqz$gj~RoV3X%~ zY4Ni%&YCNL8irP9VxBD4T)o1_m&f0tQ##+sciTAOqPtv2_uWZCl{cKfF;A3z79voP zyn<8)LEhK-_{s5(?M?;@vU~CiyEhCi9`CKEpwbg>`y+6DQw2}F6MAKI#K)&*q+Ywz z@)1)Mo~`uXm3{rYqb$c>!_imvL+Voncr!tDKXh?7;Rz?Df2VDQ(RIXInb(m8ITVyC zdi35Rjtp<4E%JX_r%6hHE_KR}+CHDLLJVRp{K2jYBGQ70=usa^MNK=a(Cs(M1C=Z0 z-g^(Yhy`zrQba^tD9_pQyToK^iiVfEBT16JLCHL{Jh3xvc5QGL>d&Q>H&ax)U2o(A z;Ho#L9AKyy+9aUhQZHRi7*V8yhzi*RVu^#7(D^*my;xbObZ^ z<=^A14t>SAp5-+_ZnNcTfx=hhN-#OLN^ggvq$zZXxwwKDNSBv0o6mh&2z&u^$f#x@ zCr|N;VB_-8#(Y@@Y%`yW{5G(N)rSNvjEu7cX5&FYb`wl@f3&24$O0#~BL;w0qp| zMaZmF=|FTAv16s~T5ff-lYC7cc*fb~66A_@dV1%8jssnNf+q)ITAlBnj9z*<; zas6EH{z_K#jk&%u>cZcgO9&O83OCes-4QM(a=+|0&W$7Z?61!ad&Jbzv$A_D%+ z2@!gS(L3YVk`AQaS91lHizpQ7uK>6ePg)4+jVKc$jLBF&Yrz-4-ddv50Pn+&62?2v zw}%}K<(HJxZZ<+Wf?gkD+t`&0<_k}NDG>^gISDi&dDBT}*b$kkS!Zh>FzDtzZ2eO@ zSmy-8^&IOKOKsHWo^=CkI+6IWV4`{pN%t-xBDuS++#Yta!0~) zcWM4a4AGO(gnD4r+uEW%kXcj-U(iigP?l|x3f0B0a{VNg;Ue05%r=X)Kh)59FLlMn zDLhM}tO=v4*ohc+{rt>}E<&ApZox}Np8sWcvIzn;FKRjKzp8JsSyN*HqBrJid=b~A zE<(A>Sg_QGQ3kq}y@&@Z6&Z?ypA%(k!1s<~qwVJt5J`tMXkC#I&9w_-I5o2S9yLMG ztu0K(HjaW+2ml<1Xu}7jg`G0kj4lu;|6={TBOBE|o?4XM&S(r&mMTE=5V&I*D7@m) zolq*@5MJ>KWGf@%id&?39U(aNFf)qkRVlmz$IF&d{PRO5;n7ZzPzsIOUC)DR_Ya)l}iMMWZoC4HUkUb0CpVz+X2ettvH zn(a33~_<4iaS-B62jILYChu4*Y(_QH@Ifg@_4tD_ zIxERFy8-<~i-FAz&njoW$W%KMp=o=K!uD}Jgq7FSu|SdEhLVB9`1Hd0U~NBKGRm)? zxyi0YB<#DBq63}Y@q&p5c|}DJ^@d<98>a%cGse9{L9csT%NLdG%!;{|A6T~en&B@^ z*YuXh8EXE!uz)d6Q4RUHh!E#hShsj%y}CiKKQIPJBsWA~*lPkqHXePiojys-_}c_vR1DOQrKzdL8Vk(lI~$#0O{gW^(u%Dj~v$2OJMc`y_WWb0WyU{XkI zI{)ceXi9QYV7UU&YSfT}P5AUjt9%W(g-t+fo`DK~6hkv=X5YiY*IgO}e?S~59J!KRqH}eZv>rWBVU|N-D~-PU zw4`x|*k_LQ+s_YxD0 z`z?n!d71?PTym<7tu{WX(*yD0EXhSvTKQRC-}YI&TUXUle^H+~F_x@%mb0utvg3A& z3)3;{yR;DNBLEDq*xVQdH_Rr0WN#!%uZvrYLUY{E#pb@(0MWJ2;KtxsoPvu$izcw zPK^XDn^c@r{X_~_%q2KbN!D-jw`h1Yp>;gNOC>?4?M#e*`&D}_b-Z=-dSA`C*+V(d z$8w?vFuh&@wX8HKL6pY{;N41%g4Ve@#>$o52D zk}5%xS$;vEljD`!q@jj<`LC8bRKwVh#}+&+s+M~A(n%^SK`|pseJJ(Jwu;aMjb?z? zzqAOu;!K71aGDIl_1S2RqmHFss%|G+=uhY&?+p%1M(+inMk;cb&6UIQnx6f+LGlrC qy2SApw64beEjm`KkOK~FU)=&1au^Y%W^|-0<{1ktJtuRo$o~h}A}@LX literal 0 HcmV?d00001 diff --git a/docs/busybox.net/images/busybox.png b/docs/busybox.net/images/busybox.png new file mode 100644 index 0000000000000000000000000000000000000000..b1eb92f3811c3fc8a029248bf95f61ad8a53d67c GIT binary patch literal 34014 zcmV)TK(W7xP)Icc5TG{{```OfpcoM*I`UNnQ~2le&dp2MMO0$kY-1Thr^_h zqlbFSlYXXNKP-}n#(;wDtblWCT3LsIa#chvAk3(YesaOPl5S2iC4F;g zzL$4{h4tREj);u*YdtH|!N;a@Q~37yi*?DQc2k#fjjMHA(b@TZZnd_a#$z-bo^nxp zR7Hi_@>>j8p`4O##I00(qQO+^RN0R#mHB_pmW2mk;e07*naRCt{1 zy$e9qWw!obJ4#d0lu}251T@$UN*)#S!k}SkCb7klsT6Vm>7s*zf;c-xAq!2z6fzYE z2T7Czh@#+SFeC)EgaoA+1{qLEydZ`!gxLRi*8AN^P17`!=bSm`|9kh|-+cj1K0Ir! zXRY_UclX@$|JMIo9jjN59*>-UWarY^3mum(?eRbTule7&diD7F^z5BW6B84cW@ctq zR%T}QHS50cfBIkbziRb*WcJdfOC1wE0C7ZOqDN+?Ri;&NwJzAz%<;$m+}FMSyZ(j$ z<_j@Ad#7WDqob9Fhlit;l~phR@-EoQ)it=fT36Yn@{u3;^C0grV(ICx|J(k-|9-1i zFEU)dv~C1~*sL-$N&u!?r3a_$bbJV+1XEHc3dd&9l=-$11rAMOU>F53%|Ly-C zt5=WbM23!tVGk=05CpN>SAVk`o(nK2-0W;q-MIx#J?B5J!U&*Ms?|aqVhzS zeu;@Y|NkS)UOgW98U%N$T$t%$Wi3FJ@UvxAROW02!5F!9#)bGM+93L42EDm zR=VggJ%;zjbOD*};bBa_Xl7=Xc>0I`($h;Ft?=b8-Mg5Xb?;KyrTatO`#H}3KWdjK z!qYoj+;2xJe|4x<&KRv)a_UBpGF3pPN9dV^kX z)aV&wVmxs0z%eRj+{Gxoq#vg>6Fs^Bq!}n`k9 zFO#M)JrikB7hEkrKS)B8$cH+%&MHF!mfTb;jc>Xr&(ec+3b5Q*U`*LkXUwEsf+91` ze!%}MbWE(I8^06SDN2#*$=RsQzN|cz{RHLzd<_S~r>Q`o6FbBBZ8K!(B?pcm3q5NB z@ad}|FP&arT}?Sj07Iw24CY`NMX=sbeB`PLQiK|uu0?yW2_kXEd(8z1v5N65Xz_|#x|=A zBf=NeB$%TvU}Pj$SJ&yO!fL$^WJQoLKEc8w*ci;%#&;{SDA_RMQn~dk&QvH1x?D{e zRFBs()jdJ}t&yC))C!S}N@I4XX#0K-=m~)dj3C;t-)n!T(fqF>C_Nq1pd%dNhz!Rz z#|$gu2+$j0Wi)!wR>SZG>y3ImBrt#!A`~R$NkYNmXAr=_IwL%|72P9pWiW|RpjJYX ze@HKf;B>9aju9zJ;?%_iQ^J)>k>8(^_gd{3YO~*O>yF`G8d$B)gk^-$8H%^oMZ4|?tN6aTdgpOda`>2k-V%L^Qb4+lcW;cbp>!`lR-F@ske z@Jt_rAF`SKsFb z;!dn4N*1#&$~zUQtL$Rt{M~ZWy?HO&3g* zXaMOA$#{5Z2Tk=oug^qts?n#0z32ajT|MU9Ey$ zK@qt#nDtncDJ5Q~6IU)32MVPkFEX}eL~d4T_I)Ec+tG^q6N+RPU<6Ot#}janLQjcu zvWX->LYRB!O+NccKaZCaJreIi2}c}5g{Nn~>?mu*8{jy6X+c3oLBa5X3`nn_zP=42 z%xD{4(8h;&l3@yQO_lhejd}xE@+QSWr)TjHs(2!1QA!0uo4O5Fyn{1?GeIviI3!c~ zCcVB+lPvx5qeK_Ki#Gh5fdz{|M#m3lCf*6Whiu?NEZIE9<0a>;c zAgqxb3wU5|q$VXxc3u*d27#c{gCM7j=`!h7By}t*$aAhb71`+|$(O-Wr07cA)zt)7 zov!g`^3_XHg@93J($ke8H{&F2cIVh$=x8P8hl}8lDx&}rEg@R%%_q)IKI=RAtWu<} zZ;h|-Uf;cp`MrD3&YSniiOKUueE9Mci68yF!TXOdP8J*!6Z<75CW;hMQHZi0Hv$|! z94Z{nn1&+p7zgG^d_z6&>W!U3n+eir2w^3rDyt1ratt9x^3aKy#9g&Mgs%e|wdM+w zRLyN_UW0>~FbOo>m0xmY=|-w_ zI4a3-4Iv@o5ia!w!;K(VfD1U**T)QxG1ePz;sKs>UdW*$5E>1NWw4kWBV}o%9ipu? z=!IrF;hFGJh&mJ$+&C9wX{ipDNOmPm$S<8=VGYNPGOu>!w3eRktaIlsP5J7vuip6Zwcq~}9D6xF)V+K6 zewCFTi5?jv95XUTkl}D)s5mOB4EEvV2*7POjl+$&*0;rwB#k(CS*#2OktMv38cl#g zdazV&J#>ZwOgYlpxWXD%2P;KVoj~J?N3PYvQcrSt4u$GmDM~$GB4PX;!D1kpH}(8O z0@7+)$B*;o>=7IZNrp5t1I2NiREQo~Z4lbNN|F>MC{9ihMFM9{3Z9uro_poxDG$8- zGOj<_TRxFUkd>KvS3ss47@pDL-AcK%*-4)SD($zAUu$a%IOJtg2PDDnZ0ekez0` zkDz3CQG!Hz+zgK6P_m;?ZXiq0+pHLB(T#PMiUdaA$>doz8BxCX*-KTU_g;GAtKUEU z+!L?8_7hp2Ui#}_|GIl6dSw{Jf)4!^6r8*xNcDP#fXx^#I5UJGqFO1^h!(L;uk=Wq z)%;p=tw7xw=1u@km~A?kPzZS6^1BXU4<#WN7PuY#l47@`G5fr zmhvSrl32?-ew^8fR=O^#sbPya&P)x@s7P5Ia+DBfY)i3`3SLD?PNpE=zjWymZm+%k z)i=*Qk@&?=GDMFoedt%e{&l8DU4aVdH&j-v?<7d$a4FguR3xtqY9n(bAOd4O5wg9O zO+UHnr7Nk^hv<1l!3lv;qyR&h7eURH=s?j`A{bX!tZGSr_!^(M>QpI`*Of}EsZ)_C zD-id-@Q8w}>==^wnC&4IyNhbInInD2L6IOCMSgNPYoZC9VuNZF&ApSQ?Cia?_x)Ec zy?<`+rE~MDK79H2U-o+7xu4L7f8Dq8*T4CX%}bXrC&-%&?2`p2ObhXpz>}blDYd>f&2vT8GB*#IM5M&e<(j-_;RwPB3leOZlt~Dq&Xc$?& znIq3t?S22!-e=$VaPNm-eY54uK_Bh>M=j6SOS=Q4SwX>aa1zHeoF)fMS)Fk)zA+p@ z4{OPK&hTcItY@{U7l`%gjA+Dm2gVR(zN__hA^JK)T^*v@iV*RI5CnHHg-G=UYl?JL zg;fDFVUfSF5qtjn~^Phvh{>6`z?3?)O-~6h(aXBOjJ=UMRd6L-? zPKWx0!4WOqG~N_%e$$wN8Ur`3Ql!2f-0}>0dF%?U^cqTI2W%0Lf6K+7wVCi#f|M?=gO(;$%Y=sjRV6d$}#G!Bua)h%J^n+L0WsG8ppl9^JIu2iaduSh+f)z?h=l#ef8z< zcYgT9kD`2pARmuHPAs@t{{}rGV%wA))s^NFv3wI*vYzvbdMPui+Ty z*yzDcEv*HVzPma^BG^?>RhzI?yJ`?6xFx2w=juiEM+7>}dayb&(wRyjNmq4#)a(oo zlZT`Q8aEDCg4_%_lIB@pM1!dE{tIjYA}J2Vwav5~08BRetYI;rZsceP@*QLY|I5{e9# zhK@~5rddZ|M^TjH$W2w7&GbHEe}rljYmPLFN{{noA~a8EDu5)3=thj=ZtuA_zWU~L z;-DY#LOc8Y2C*H3aQ=ju5!^aJX0YXMlU_&kmqESO8}m5yVN=bJ#?6q2I@4hxS!L>o zFeHSvCYWZNvCZ-f_YMOySu55kqy9sfX=0=@K zHJc~&R!XI*M0Xf9PL}Vuvyvkb+M88sq%=>QI58RKoORpR#Bwq^MBe6!2ci-s!uJ9s zGG*1Ec_|Nkc;CvuU%($p{1sx`1JR2r0!7x>-!%E2n^JCY&lJfPv0gJqhj{w5G)jiTbN}X)&&2{sTEX%?e3)^L6@9=17X( zHN?&iK`j2K$B4{KE1f1tn9FfyfX5`c*<>QYP($g#hpKj~&;~&nSk9A~+FryrljNoM z-#_<06MVWaIK@#b*MNyQhay>Wm?zJk5K&T(G(fobtzxD;r&bL) zRw2CiKKsd~uYPQV?)d9p{p#@(M&N?;&iIaXG!EC4INTVc$r1kBpht~?5&XBZLIA&g~6oyKVm7C2}WN!vY^ofKDj!{vQ&u;eE%s8JM#i1H_PDY?B zmRfYU;payE*>Ka2-5L;M*OC@?|N0GV}xXV zo@%P=c~qgTDY9BGdjoY$gsd+55Q#u>k**Tnd_|MoK$7fSoOO3zuoKeS>vlR7+5?$Y z)$bvbf4%(OuBh5Nv&@AH<;^!{W||4Vtl@Vtw8 z^G^8A)AFP$-RJsMi6pN|2f54+^ta_h0?y@h;=q+AXN$ZJJzPe zlp;f_LlDLgSBD@)of^hY6;@Jf&-OG>D{fp)IA$CH6flSDbtPGd)S3Z%OKPVU4L>CM$# z2rmz0*|rMSUDPm|jWa{Vz8IwMQV%6T7nyMPC(EvFJHga@T)yE%AdEkjZ`~k`E zp(RW1T~a*i;one|5B$Ld@{z=c9)GC2v0!-tFghA#!lMRfMghrz8Mj^~J&Ga~Nm@oW zga+K<5Mn@U$g&&(^B62eH3UB_Q(3T*q-tTPKs$yvQA&6V)PZ(7!N>>N#T4TmBni__ zbwkuSb?-_i*~LT=7i&Ug3vZFLtes&yXNrb-aY!KEol4u%FZ0>g!ay)@X zv1(pTO3h?)gdFh@@`NL=`smVwDPOEi83bZa+_&T{kepMzf8>2D4^%y2YQY^Jdg$@) zRt3wK8+krOy;5Yo)R^HUNH^YQC;-GZqke=z7LsHg3O1@tohVYwBm0;TgR`@<3Ss=X zC3Pl6iXfp#!Dws#ZV$eGes8sm`-xw7GkT*wggmyDen!(|k zdo*$c$q_C=vS90FPC+xt=&sJItBWx3hvFWW!I>yIgAA5=4g5}tw16RYasx@=88~pD z9j|tZT#21ZhrFY|tn3h?pj0FWno@xptwD?tbs;hsQUFcnr}83XiTJK0$ctLhrXhkg zrxl_cH*@>J*EEoXrG%e6Ax4tJiz?rmy|`4#jX822E>%?@Rjou?eBebA`{hIAxMWTC zvXK{Zq0)~YcupCh#~<$!Wwo3;gv^E_Nw(P>Ot!I^;mxSk8bb!aaZ!zcSSNzSbG9(S z!Ok63MTs)R*)GJ64DFmloQcy}h}fy@Xh$hhnRceE2y%*^-9Vjsq6Ky}z>9_@sg^*L z&is$aA2CystVCV9cv0T;gy2ShGY-0pQq@}y=x(?_E25OR22B!V)rK0;Z2P9L@t)^f zGdTrmlDSg!h%`AmW#z-8KDtL`#3v6F-}_c^F*LcqWn}KkhX%dIot<8ZzkYm>nMa#r zTS10IcAnBBvSNXRcAK<@M!f6c_LVNW#QVE>%V; zkwIKC@Qxz2GCi=8@r-*@Y>OZzeGX*x>hPK@TzR@naq+1Z>`!$qU2<9 zGf}E!$;$~bW^;tLmksxzmAQjbIx6&Y53$}9uPNTY|LCIJ z<0}V!2$H>CL$&FW-qt1qT-npAmy;@RK@XFcmsfz0Hs-aVuVJ|f$#8B%{F02WB5g$yY%L1c0?rNV)BcNR#B5Ji#N5(!g`0|%B26xWG^ z!su-0Wv6C9@IaNiOi(07BuX>Zn~QhK4S>obNmg!O)2?p%%X zk7|uhNjdOZr@`(o4~)9^ttG|#i;MRkMR>0q^xA1C@}b8c?;gcde7JE_f3rSDwruKA zUqp?0^?5vxf*BH$L>Sk}oouXVw-D$m)Vo zPwEuP5NCsgGZo2rmL)$h*=hJf z{CAw?4^#qUCFLlZlw1jnmFegll?md>E27D>auQ3FFMT-5wjwE;vhX~VW7RgY+w<{? z6)XCrew=Fi@y8!uv#ok?8}(u+JRvv$LA!8#=BCoBm`-S zZ8H`dF!lA_%r`HIAWf%A*2oDf%IUn)XUYp}KRmAy6Z@4fWO zvr}GJ{rR`w9y;{))vMdL@9}i;FLH2*PhK?s!5^58KQQXvd*6EN`t|EJHZ~)VgCrIC zxGrybLAq3HDK;RgC(1S>cU%kd&=b~^Ww1!n8Pyw?Y6J5louhMv6dSu>I|Y&w#LpfA zi{q@K+7-l8Y-`GW$!DibdEs+FeEZPbhpuivwP}-|e|c@7 zk5BUP~-y*&Dk_Fe zO;l8+cJ3t4z(7QQ!n9cPe(L*sR=lv_>QzMZRj$?B=Z^KuIddkgHlV`Cr^4DNdFA-~ z4m|J!9Pha&5qa|7x2^-^)UC^sQ}+5ot9uNx001BWNkl!SBP|h=bYTo~N%Bcm1CcQgpl3-l0;MA$PbH`dH1$hrnD|2e90LkR!-DWZDJj0b-MgemEYG7C&&XhM)B_+IY(u@wc!)Dktao07%yHSw zgh=(SD~W(2#Yl!|1WQWlN}NmR$N5CqA-4geA{ox5-620JJ$h-`&?L`FQ;IA>0w~cq zvx1lK58;=_<_0ZyL_|c1Hbv(tEII+GGvkSXx~Qnk%8TPn8?)oM%^+LdXORqFdFA6N zFCdhuLd9^wsa?m~+s)5}h3($$?VXk%ke}ZaUr|wdymZmZzaz+d?s@c`kxKw_&h_j8 zbGB}66_n!)1rsI|jA-L|XAq-oy2{?Qj2~4811;tmG?EdJCsHFHbTo#v-uU@s2}o_( zQIFvzUIaNXToh^N#rqO3NQ@URQ(gH^n5!h|WfH8&5`Rev(=Gn~2wZ@2V70c{sFOC) zw9+%9JffnAR-sf|*n8i9Wy+Mt9s|Qklm0RZ3k-MdIt7ZW+AV|3Lw7efHxs0TcdbJ_ zK>8fd&HcL^;mlt|oDeSEZY$#^9VCjA|xY9!s`@C)?+p#3~gKlB!mcc5+y6mKrgx2b#TOMW~OIS z)V)la<=++VH9dTKxHDSDbtTp7B1)q_j@er=^p zfivkT2~(2gxOAn)y?b9d`N~NOanif8CNZ2vhVAX;?f#*mK|#9*@7}$;`C!>Wha&HT z2M;>LA3yF>fdqN{$d8;DaL+wYd~uzUoO7L;T#x9Ud~^5+XXmAD96q)!jo=%U^t3?o z1|*i6Iytk4_uMQDAxIffKzzzLvN(q;bn1tcV>rbKSS4CU6d{rw$ie~H4fGO0dJQyz zlP8d*22~bNs(V$drt`PJzovu_q-^tsWhYJu$@1d3m~k<<`d<3v*;j5(d29uQILWk_ z5&@Aq1jo>FfBz(Jhr!+sShXodwY+yY2t^)0zVi4N2mbV99Phd3qmgUCkr;7Uzdk-? z?sTn)dgw4BVgg8_aKiscf9)(2ig0l{)Sr+X zC|OXVdL8d5D6e(GU>zN$mt6-tlNh~BPfRb!*TYR9y}X>grcVdUb@H3Qt1{+DR3tOm zl>anTPuOpYabuR&*FVc<_S`4rKu;2M^XB zba>EV{PFnX$+`C(c<~>-NAZD0_g( z*9eYo4bH+b#5p`7JYsqTx(t|cJ7-!%s&Ji><8%}nyLI7IoshKCl!en+n;+8~}wD&7$&8O#v|@!%oB)gd5>2oDLzi_@ob8X+K^ zX?4O&M1B+|$2Llf@=OcTfdt7rI}M{rc_wxP@4WOKknrCFoxLLau8WTD+c&arU#~>G znCO-0oGBCF>hyZF6^v!%Lzu&NrK(Vh3sg9Z49m-b($X9`ad%K?IY{niScB&1iA95p zya{qVK_0*F_<LSyL<7R>^V}sTLX(~PGTuXE^nMM){6xvSB`Td3L*?r zV?yc-&JhvL(<7k9h;S&9fgSG5GCa`a5X0B0J1=<*r$Y@&gGeb3RQl40$>Yu(wU;}B zo%kbVv7z*u9?jLaZ{MY)7}?hg{}O#QKWXVCWH{xq7hauYsw!GSESG3D=L*Gbcj#`E z7lPygk_5*G9~?Y-;$ZJ0hqAK#V-iD7T*s8D zB}GktUhAUQbrzeJC_5C`5$;}!A^&KkJn*t}_KH3{v+qohgg_DBOXaN>!>7!A>@l>< zlir@BVI;$2N`>X-W5STiIHhvuZNO-I{#gfjl1W6JMk6stvnO$ULXJ{rBes?L9B&hy?s1mg%IvOMw z&R!Z3!LDmL5dQx2SHFGgFDf0redy}yQ@i$=ii#X9fibiwGyxbBnu`WI5F!=mJ(x5h z$+GKi>_CO|HqIeTWo%$bi(`+Uo{pH2FV7W>_I53ysmn7hy3-rT&SIJYB~rD-WjXii8N zoIoj#9!->kp+O`_kzo@!237=CG&NaU7g{Hm9=~tmPZA{7bMNHtzkYqq8h4vD2yG@x zpQh30o;_J0=RWGwQEUuth79Kr$6%#OXN}_oA&97kB5@FqtfA8x%F5))EjoN%bfn;; zd5nzg9L-+q2uJbyKFIHDRsrnI8&-65#NnA-Pfz>u^KZWe#lQST6_iuER+*QZLwxqK zSenhv(Xb>cPbY78Dk zDvs0H^obNpv?Ajb9U1K(>Vi7_hZxvO~kiU!mf_ zCOR8J^l~;uL7icQ>}J;~loKXQN2mi}NnKs|bQJCBeW!bQ@jBhkOXWmqlo{M0DX{GD zvZ?Yaj_b7Y5qU>?o#kkXWj=!pBNz7W+ZPb00pVlceyepUZ?9dwdci4+vG$hc_M$_` zv0WY^x-H#qxivO6Ol-($NZ=)*p$L*hiaaRYOBu8nk2pqVEJGt>ZJnH)KR!P{xv;gc zHQ74%_&cxubddKvu@VL6y8CseNvd+d)?tO-IoDn}2@iyJ1tdA_iHK<9%q&FO8I>ei zdZzc~a00c4ivd2}3({0&dY~yoMzcH+az}`QBq-r4B-}gFq`b&Gio6378YKAW-iH?+ zo;Gdvv}xaL+4AjQu-+AKICr16djYE2kl=p&=*79~8l| zi0fc&Ks;7H80N==UPZEXQ@(YDPiZMCjCFEha$#Z5*3!~1{{GbGht@~;jatL`1r*7S zalqE8!wMZ5uHC$O(`e+%W=Z8FYPMVo@0K#mipN0dZFtc#ewZu&Y_Y_I8k z8PV$mB*)wEj*;X=PT}E^k$0eBMXRrvly|2pcY1*ljnq0}0utiWU-x?SsXzZkC?X?n z-xYiLvb}jZ%%G)0c=@vBzSw0CJ^FAHxxj`eBERjBuG`D6C7%+8MsddApH(o9PVCB zRJx4peW$SC=;hV7_soSeXD*+%<Co>ur{~6 z42+lg!16MJ+J14|ty^)6-Li5>Q4YZ9pHK@qmc=6%#>XGGj%QG#NDE9{Dk`jDd`b&j zeW=dkAN@4%)3N?=U{o;&7}9qmz}*M9Po0(Tup zL^L)O&T%!uk}a15si8QU_PS;rh1O>ohw*qBTV6DA69Z&MwK>_m{yeQ+Es+2&Dw1y07d-I^nL-7PGhv0VO#*AAt#?BbKI4jHTmhCOb z5`xT05JkoZ1mrWOp~!q@Me7QM(gzC&3lY)CkcB>_ExG?_?DMzl0iLHOGqnM0vWwB^ z46vCxt2F+=ITY`RJgBgm7TnRf&N*Ycs@e!_RP4SGVsym1k_hN;dSB^doIwx^K`VVV ztM)o+=e$IG>j*Mjy&|ED``(>aXO9EBxNsqi_Lk4T{p_>P{xa$9L#r+3&))}oxO+Es zN9A1}dRZQfopIT6=#Zh7ix)3$Y`oQ&)Y#CN)p+aHkgUcTwzohsr-3HCsJ7O-DL){- zNwqHd$Mdb5kRK~5d~jf9tYCKZDQx9xIsVZ<{n1pv_~_v!=oQ_syRUK2wwY>^Jr##p zr3VwE zb7OBMxZaU+?Zi>Net6-+(_d4H-*O!~boK447W<&XSo?N*Lwnk7MD)-kSwn{|o-qVQ z2a6m*5La6q5Z!T$Z#0IXo$)TK&8O*x<8ERwt5pFVA~m)mI|3(er9M8bJ;`y=ga2HH zl=a*JHr;TO*WYr-n&UoT>eQ*Tl2dBlKUn~8UR{munm*FmF+$uV5Jpc2I%G+ZnJyrs z;USqF5%CISxL9uLFH-c^`!HX{_e*{}tRU_XiaRuny(!Y(y=TvURZ+cWC?+$Q_y>m+t)i)9^R1e*5{iP~O&Q}oJkMY1GOFyLy!tVD$di-GYcTI8XIkGg&;`w?|%)C0JpKB0nN04Ef7bhStUuhhkZa`Y<4->*;A)(9WZQtZi@6c6F7wAlmh0bj#;uVVj2gg%4j^I-Uv}{xJDlaHZ58y z$-}^SSR_deI`_prC}K_9F>P9pEil?oJq3q&=<2CcbGa*}?Mt<8l9Nj- zNVC;PP?je9lmcT*Ztlu|uB-Vj$$RgW*j}Sr94yFkef%p^IKg1!?W~Q$(F;P8>$EYo zq(?sPjhI#SLRoE;7bHh`N+F3wx^fpC0gFBD(=A)3ZF%m?Ux4AOudY5dckbMM`^L^a zwrN9ZyNhZ3)qdu3kmb;zp+hZ2l$VD9C{Y4rmZvR9dfM8irp{08Z%d4DKr)>0b|^bo zR>s*_S%9qke3?i1$Wa!08eEcjm+X^FA<~NblqR>rNnTjA=wFR&uF=K!UWc887~SV& z%LD_R_j-pZk3ELcEi>HdEZEa0NK*|(Mrs^Y22?#thYQ3tS~M6)h7T+zX42|HHk*A6ZYd$d=-qHSBHJ<_ti$V{N|Tg(*|&^V&G;ffy%DM}|}+r)vd6MJ@fK4jDoULed z222gGwAeo7**6N>3L=&|F9pcH6QtC5NvZBFfjU#s6iEui!@V`HeOQr1Y(t8N7k>I_ zkKgtB=IJeue);IDuM*;1G%EJ7u^a3+L4N)am`j2S$}sa@)-9&ITW*W9Kr-u=TjLBN zd86I)w#abCtt@meSuSk-{F`~6iMNA;_ihKCw!^%LCB_@UY#k^peFP)AKL#?xSzCfp z5+rXei&{r6O8J*djjY!mo^$WLXcVuz3(u`{<{a%AHviOWi~if*ua7ZA^yS=9Mm`Zz zjQbvro&b{3I}S&Sv7suzl_-}kiPEtZcPFPrTSaWDI z^rsddsHE5=2PcVYA5Dd(J9(JQD@qF$iL{u^ux=&IW!57{KKw74@jL4Y?mI}!t;QEv z>jOMjzy0=Vi;vMP=1o|Na9#@AGo2jKULWq=H+ov{35Q9N(i4V+cQ~@cWh;aD$n2-T zeDu-JK6`5IYUq$vc&t5($7R3Uw`1+&{BE4dic4kELtJNp6C(CVmaTxi#)1-uFOMB- zyLfDB9KPC+)DYG@xEWO?&08o2ILXn((Mc#eks|kC3eo(TFq&YBK5?m_3|m#P5t93p zM~?jBXMUJ=uazTFI6p_PcLypMug|0Mi54gC-k2_BxJvq8Gv)PKzeV z!^$B>?obh}6dAp+@4}~h^!ReimtTJN*{iG1&j{L$E=49@_Lna&9(vjB@?uLiC~oQjaDR}z<#y{#ShKh6r)mo1D#OLwTT-Lap}GcQ6;hJKs909` z1WN5jw$x%9P)k*W%W7Sg+cNU!R2o?aaz|#*(X^;|LX$zKCQVwfV#*YFAb@o4+qZA@ z1Qg;4>(Cf72||v@e<&uON<{be>OJl7;h8fRe)`C?r@wjZ^KU2pWzx6XEnITk7Sj*z zhMVEsX7S~rZbMO!Sxgq+iW?g@c19d@cS|T9$;v_+q#$iwT*Tv0uC`f8(4jXCwm0*k zHw})rcljNpAlW1&omi?F*G=4m31ruz#K@9^yW|QHBMOd`>`ow)(bPQrGpvtVgGP?r zZ>$aEjV=O>Ygc63L+--B}TS0EHBfY5zjk7abfRIN%GSjqDW{I=H}_= zzWMT-&$oQb^F<~t*tLmWGAeTr0(u`wqWBJR8#)xe8l}YT7F%Z9I8h_*H9)4Os(V{4 zS>9?y!#CJlH7aFgX=x4))L&W|Z%UJCT4?7xRXCwqtFV?N8F(kCA~Bn#3z zAd{09wdAh+S(QcBDn&&N60xv6ZOce$^V!_yX8Xewx7#UKrUOhY(M-DO?mbJDLY#O zS6!WSX!VMl1@Jx*km5{U7Xl=B3c$mM-$ZTx^i8QRGpB9&=CM~lJG6HDicM(p0MV(u z-LKu>+#KCr&}GY{GeaAbCN3sJ*c)5~B`GRXb87~?k4qyUPHZkhF02)`v1fKD^F}C( z`asQ_Vl3;t0`kT7sPtG7*i^_pYStPbrAZNFASaL&g-zB?)>@8aKLdn|O3`AsE^1kn z`=ck8{-pKAsQos}S}xdRuTfJVn(_f8x#{YptLHy{WBG&$5q)R&Ua0!Kz5p4$y);{n+i&{&0$o<(I?|Ct|xOo4_Mayi8v$N;8!%tq9{$@bW z@?D1xJ!$ds%QGiTm=HblFj+1<9L)xMos1sd{Pd+ycYHeLQ^?cnaF3^_^x1xXewI4z zrW{T?=a$Smh7&wv0^JP10|05ltpi!50~ykRCQWu&Nn}UzqYvWVLT;%K+Cwxr zBku;tQN=dJ@Tre{N-St9Xq~YOsEc&pJ{Pd+UhhG{4 zX}%e`u*X+@KJIh=c0zf1O?i8bOG6`Y-CEq}wixRccTjL0!a3|M&R}I0JLB4rMj6{o zZ04!lX!0D`prj#waVe9mNQ~nT=ChbIm6g@jO4SKCDEM$4F92^qkiV)_^IM9sLOXGS zIWo|vq9eEim4_5z%H~Y24=%0oKFPdm&9!;!t)CC_iADPXa{r={vqsw3*a$}V0b3c| z!`dy@uC?ehW#-J8eWMxM%#28tXt9wSxw`FmsT(roOMMsidd;HGsrjA>wTNn$oFk1# z8XL0~yWP0a?&sI;N2{GRNQmaDkDoD*?_4c741BGLtly4|8*T*M59)iR@5DLc9 zTYmhkIh^NlYMyM!f6O))kyCk z#+Tmg29_W>rgx7&Kk($K)${E`i^^*}J#&uau-o(WBSpXVc3WHJwPkByACn|cDUmap zh>=qC-VKr*fFXKum&U6@{&;8(!~$wLUM#Cs<)v0-NyU(7OEhVk)W(lEu
+ + + + + + + + + + + + + + + + + + +
FeatureUtilities
SSHDropbear has both an ssh server and an ssh client that together come in around 100k. It has no external +dependencies (I.E. it does not depend on OpenSSL, using a built-in copy of +LibTomCrypt instead). It's actively maintained, with a quiet but responsive +mailing list.
SMTPssmtp is an extremely simple Mail Transfer Agent.
ntpntpclient is a +tiny ntp client. BusyBox has rdate to set the date from a remote server, but +if you want a daemon to repeatedly adjust the clock over time, try that.
+ +

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

+ +

SCRIPTING LANGUAGES

+

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

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

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

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

Proper handling of SIGINT/SIGQUIT

+ +

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

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

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

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

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

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

Basic concepts

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

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

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

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

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

What we try to achieve

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

Let us consider the most basic script: +

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

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

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

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

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

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

Consider a more annoying example: +

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

Alternatives to do so

+ +

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

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

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

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

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

    The problem

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

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

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

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

    Consider this script: +

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

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

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

    One more example

    + +

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

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

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

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

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

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

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

    A further look at the alternatives

    + +

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

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

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

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

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

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

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

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

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

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

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

    So everything is well with WCE?

    + +Well, almost. + +

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

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

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

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

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

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

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

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

    How to be a proper program

    + +

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    How to be a proper shell

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

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

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

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

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

    Other remarks

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

    What to fight

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

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

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

    Concrete examples how to fix programs:

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

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

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

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

    Testsuite for shells

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

    Appendix 1 - table of implementation choices

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

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

    inode number + * %g integer + * %i inode number + * %Is -> i_size + * %IS -> i_extra_isize + * %Ib -> i_blocks + * %Il -> i_links_count + * %Im -> i_mode + * %IM -> i_mtime + * %IF -> i_faddr + * %If -> i_file_acl + * %Id -> i_dir_acl + * %Iu -> i_uid + * %Ig -> i_gid + * %j inode number + * %m + * %N + * %p ext2fs_get_pathname of directory + * %P ext2fs_get_pathname of ->ino with as + * the containing directory. (If dirent is NULL + * then return the pathname of directory ) + * %q ext2fs_get_pathname of directory + * %Q ext2fs_get_pathname of directory with as + * the containing directory. + * %s miscellaneous string + * %S backup superblock + * %X hexadecimal format + * + * The following '@' expansions are supported: + * + * @a extended attribute + * @A error allocating + * @b block + * @B bitmap + * @c compress + * @C conflicts with some other fs block + * @D deleted + * @d directory + * @e entry + * @E Entry '%Dn' in %p (%i) + * @f filesystem + * @F for @i %i (%Q) is + * @g group + * @h HTREE directory inode + * @i inode + * @I illegal + * @j journal + * @l lost+found + * @L is a link + * @m multiply-claimed + * @n invalid + * @o orphaned + * @p problem in + * @r root inode + * @s should be + * @S superblock + * @u unattached + * @v device + * @z zero-length + */ + + +/* + * This structure defines the abbreviations used by the text strings + * below. The first character in the string is the index letter. An + * abbreviation of the form '@' is expanded by looking up the index + * letter in the table below. + */ +static const char * const abbrevs[] = { + N_("aextended attribute"), + N_("Aerror allocating"), + N_("bblock"), + N_("Bbitmap"), + N_("ccompress"), + N_("Cconflicts with some other fs @b"), + N_("iinode"), + N_("Iillegal"), + N_("jjournal"), + N_("Ddeleted"), + N_("ddirectory"), + N_("eentry"), + N_("E@e '%Dn' in %p (%i)"), + N_("ffilesystem"), + N_("Ffor @i %i (%Q) is"), + N_("ggroup"), + N_("hHTREE @d @i"), + N_("llost+found"), + N_("Lis a link"), + N_("mmultiply-claimed"), + N_("ninvalid"), + N_("oorphaned"), + N_("pproblem in"), + N_("rroot @i"), + N_("sshould be"), + N_("Ssuper@b"), + N_("uunattached"), + N_("vdevice"), + N_("zzero-length"), + "@@", + 0 + }; + +/* + * Give more user friendly names to the "special" inodes. + */ +#define num_special_inodes 11 +static const char * const special_inode_name[] = +{ + N_(""), /* 0 */ + N_(""), /* 1 */ + "/", /* 2 */ + N_(""), /* 3 */ + N_(""), /* 4 */ + N_(""), /* 5 */ + N_(""), /* 6 */ + N_(""), /* 7 */ + N_(""), /* 8 */ + N_(""), /* 9 */ + N_(""), /* 10 */ +}; + +/* + * This function does "safe" printing. It will convert non-printable + * ASCII characters using '^' and M- notation. + */ +static void safe_print(const char *cp, int len) +{ + unsigned char ch; + + if (len < 0) + len = strlen(cp); + + while (len--) { + ch = *cp++; + if (ch > 128) { + fputs("M-", stdout); + ch -= 128; + } + if ((ch < 32) || (ch == 0x7f)) { + fputc('^', stdout); + ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */ + } + fputc(ch, stdout); + } +} + + +/* + * This function prints a pathname, using the ext2fs_get_pathname + * function + */ +static void print_pathname(ext2_filsys fs, ext2_ino_t dir, ext2_ino_t ino) +{ + errcode_t retval; + char *path; + + if (!dir && (ino < num_special_inodes)) { + fputs(_(special_inode_name[ino]), stdout); + return; + } + + retval = ext2fs_get_pathname(fs, dir, ino, &path); + if (retval) + fputs("???", stdout); + else { + safe_print(path, -1); + ext2fs_free_mem(&path); + } +} + +static void print_e2fsck_message(e2fsck_t ctx, const char *msg, + struct problem_context *pctx, int first); +/* + * This function handles the '@' expansion. We allow recursive + * expansion; an @ expression can contain further '@' and '%' + * expressions. + */ +static void expand_at_expression(e2fsck_t ctx, char ch, + struct problem_context *pctx, + int *first) +{ + const char * const *cpp; + const char *str; + + /* Search for the abbreviation */ + for (cpp = abbrevs; *cpp; cpp++) { + if (ch == *cpp[0]) + break; + } + if (*cpp) { + str = _(*cpp) + 1; + if (*first && islower(*str)) { + *first = 0; + fputc(toupper(*str++), stdout); + } + print_e2fsck_message(ctx, str, pctx, *first); + } else + printf("@%c", ch); +} + +/* + * This function expands '%IX' expressions + */ +static void expand_inode_expression(char ch, + struct problem_context *ctx) +{ + struct ext2_inode *inode; + struct ext2_inode_large *large_inode; + char * time_str; + time_t t; + int do_gmt = -1; + + if (!ctx || !ctx->inode) + goto no_inode; + + inode = ctx->inode; + large_inode = (struct ext2_inode_large *) inode; + + switch (ch) { + case 's': + if (LINUX_S_ISDIR(inode->i_mode)) + printf("%u", inode->i_size); + else { + printf("%"PRIu64, (inode->i_size | + ((uint64_t) inode->i_size_high << 32))); + } + break; + case 'S': + printf("%u", large_inode->i_extra_isize); + break; + case 'b': + printf("%u", inode->i_blocks); + break; + case 'l': + printf("%d", inode->i_links_count); + break; + case 'm': + printf("0%o", inode->i_mode); + break; + case 'M': + /* The diet libc doesn't respect the TZ environemnt variable */ + if (do_gmt == -1) { + time_str = getenv("TZ"); + if (!time_str) + time_str = ""; + do_gmt = !strcmp(time_str, "GMT"); + } + t = inode->i_mtime; + time_str = asctime(do_gmt ? gmtime(&t) : localtime(&t)); + printf("%.24s", time_str); + break; + case 'F': + printf("%u", inode->i_faddr); + break; + case 'f': + printf("%u", inode->i_file_acl); + break; + case 'd': + printf("%u", (LINUX_S_ISDIR(inode->i_mode) ? + inode->i_dir_acl : 0)); + break; + case 'u': + printf("%d", (inode->i_uid | + (inode->osd2.linux2.l_i_uid_high << 16))); + break; + case 'g': + printf("%d", (inode->i_gid | + (inode->osd2.linux2.l_i_gid_high << 16))); + break; + default: + no_inode: + printf("%%I%c", ch); + break; + } +} + +/* + * This function expands '%dX' expressions + */ +static void expand_dirent_expression(char ch, + struct problem_context *ctx) +{ + struct ext2_dir_entry *dirent; + int len; + + if (!ctx || !ctx->dirent) + goto no_dirent; + + dirent = ctx->dirent; + + switch (ch) { + case 'i': + printf("%u", dirent->inode); + break; + case 'n': + len = dirent->name_len & 0xFF; + if (len > EXT2_NAME_LEN) + len = EXT2_NAME_LEN; + if (len > dirent->rec_len) + len = dirent->rec_len; + safe_print(dirent->name, len); + break; + case 'r': + printf("%u", dirent->rec_len); + break; + case 'l': + printf("%u", dirent->name_len & 0xFF); + break; + case 't': + printf("%u", dirent->name_len >> 8); + break; + default: + no_dirent: + printf("%%D%c", ch); + break; + } +} + +static void expand_percent_expression(ext2_filsys fs, char ch, + struct problem_context *ctx) +{ + if (!ctx) + goto no_context; + + switch (ch) { + case '%': + fputc('%', stdout); + break; + case 'b': + printf("%u", ctx->blk); + break; + case 'B': + printf("%"PRIi64, ctx->blkcount); + break; + case 'c': + printf("%u", ctx->blk2); + break; + case 'd': + printf("%u", ctx->dir); + break; + case 'g': + printf("%d", ctx->group); + break; + case 'i': + printf("%u", ctx->ino); + break; + case 'j': + printf("%u", ctx->ino2); + break; + case 'm': + printf("%s", error_message(ctx->errcode)); + break; + case 'N': + printf("%"PRIi64, ctx->num); + break; + case 'p': + print_pathname(fs, ctx->ino, 0); + break; + case 'P': + print_pathname(fs, ctx->ino2, + ctx->dirent ? ctx->dirent->inode : 0); + break; + case 'q': + print_pathname(fs, ctx->dir, 0); + break; + case 'Q': + print_pathname(fs, ctx->dir, ctx->ino); + break; + case 'S': + printf("%d", get_backup_sb(NULL, fs, NULL, NULL)); + break; + case 's': + printf("%s", ctx->str ? ctx->str : "NULL"); + break; + case 'X': + printf("0x%"PRIi64, ctx->num); + break; + default: + no_context: + printf("%%%c", ch); + break; + } +} + + +static void print_e2fsck_message(e2fsck_t ctx, const char *msg, + struct problem_context *pctx, int first) +{ + ext2_filsys fs = ctx->fs; + const char * cp; + int i; + + e2fsck_clear_progbar(ctx); + for (cp = msg; *cp; cp++) { + if (cp[0] == '@') { + cp++; + expand_at_expression(ctx, *cp, pctx, &first); + } else if (cp[0] == '%' && cp[1] == 'I') { + cp += 2; + expand_inode_expression(*cp, pctx); + } else if (cp[0] == '%' && cp[1] == 'D') { + cp += 2; + expand_dirent_expression(*cp, pctx); + } else if ((cp[0] == '%')) { + cp++; + expand_percent_expression(fs, *cp, pctx); + } else { + for (i=0; cp[i]; i++) + if ((cp[i] == '@') || cp[i] == '%') + break; + printf("%.*s", i, cp); + cp += i-1; + } + first = 0; + } +} + + +/* + * region.c --- code which manages allocations within a region. + */ + +struct region_el { + region_addr_t start; + region_addr_t end; + struct region_el *next; +}; + +struct region_struct { + region_addr_t min; + region_addr_t max; + struct region_el *allocated; +}; + +static region_t region_create(region_addr_t min, region_addr_t max) +{ + region_t region; + + region = malloc(sizeof(struct region_struct)); + if (!region) + return NULL; + memset(region, 0, sizeof(struct region_struct)); + region->min = min; + region->max = max; + return region; +} + +static void region_free(region_t region) +{ + struct region_el *r, *next; + + for (r = region->allocated; r; r = next) { + next = r->next; + free(r); + } + memset(region, 0, sizeof(struct region_struct)); + free(region); +} + +static int region_allocate(region_t region, region_addr_t start, int n) +{ + struct region_el *r, *new_region, *prev, *next; + region_addr_t end; + + end = start+n; + if ((start < region->min) || (end > region->max)) + return -1; + if (n == 0) + return 1; + + /* + * Search through the linked list. If we find that it + * conflicts witih something that's already allocated, return + * 1; if we can find an existing region which we can grow, do + * so. Otherwise, stop when we find the appropriate place + * insert a new region element into the linked list. + */ + for (r = region->allocated, prev=NULL; r; prev = r, r = r->next) { + if (((start >= r->start) && (start < r->end)) || + ((end > r->start) && (end <= r->end)) || + ((start <= r->start) && (end >= r->end))) + return 1; + if (end == r->start) { + r->start = start; + return 0; + } + if (start == r->end) { + if ((next = r->next)) { + if (end > next->start) + return 1; + if (end == next->start) { + r->end = next->end; + r->next = next->next; + free(next); + return 0; + } + } + r->end = end; + return 0; + } + if (start < r->start) + break; + } + /* + * Insert a new region element structure into the linked list + */ + new_region = malloc(sizeof(struct region_el)); + if (!new_region) + return -1; + new_region->start = start; + new_region->end = start + n; + new_region->next = r; + if (prev) + prev->next = new_region; + else + region->allocated = new_region; + return 0; +} + +/* + * pass1.c -- pass #1 of e2fsck: sequential scan of the inode table + * + * Pass 1 of e2fsck iterates over all the inodes in the filesystems, + * and applies the following tests to each inode: + * + * - The mode field of the inode must be legal. + * - The size and block count fields of the inode are correct. + * - A data block must not be used by another inode + * + * Pass 1 also gathers the collects the following information: + * + * - A bitmap of which inodes are in use. (inode_used_map) + * - A bitmap of which inodes are directories. (inode_dir_map) + * - A bitmap of which inodes are regular files. (inode_reg_map) + * - A bitmap of which inodes have bad fields. (inode_bad_map) + * - A bitmap of which inodes are imagic inodes. (inode_imagic_map) + * - A bitmap of which blocks are in use. (block_found_map) + * - A bitmap of which blocks are in use by two inodes (block_dup_map) + * - The data blocks of the directory inodes. (dir_map) + * + * Pass 1 is designed to stash away enough information so that the + * other passes should not need to read in the inode information + * during the normal course of a filesystem check. (Althogh if an + * inconsistency is detected, other passes may need to read in an + * inode to fix it.) + * + * Note that pass 1B will be invoked if there are any duplicate blocks + * found. + */ + + +static int process_block(ext2_filsys fs, blk_t *blocknr, + e2_blkcnt_t blockcnt, blk_t ref_blk, + int ref_offset, void *priv_data); +static int process_bad_block(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, blk_t ref_blk, + int ref_offset, void *priv_data); +static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, + char *block_buf); +static void mark_table_blocks(e2fsck_t ctx); +static void alloc_imagic_map(e2fsck_t ctx); +static void mark_inode_bad(e2fsck_t ctx, ino_t ino); +static void handle_fs_bad_blocks(e2fsck_t ctx); +static void process_inodes(e2fsck_t ctx, char *block_buf); +static int process_inode_cmp(const void *a, const void *b); +static errcode_t scan_callback(ext2_filsys fs, + dgrp_t group, void * priv_data); +static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount, + char *block_buf, int adjust_sign); +/* static char *describe_illegal_block(ext2_filsys fs, blk_t block); */ + +static void e2fsck_write_inode_full(e2fsck_t ctx, unsigned long ino, + struct ext2_inode * inode, int bufsize, + const char *proc); + +struct process_block_struct_1 { + ext2_ino_t ino; + unsigned is_dir:1, is_reg:1, clear:1, suppress:1, + fragmented:1, compressed:1, bbcheck:1; + blk_t num_blocks; + blk_t max_blocks; + e2_blkcnt_t last_block; + int num_illegal_blocks; + blk_t previous_block; + struct ext2_inode *inode; + struct problem_context *pctx; + ext2fs_block_bitmap fs_meta_blocks; + e2fsck_t ctx; +}; + +struct process_inode_block { + ext2_ino_t ino; + struct ext2_inode inode; +}; + +struct scan_callback_struct { + e2fsck_t ctx; + char *block_buf; +}; + +/* + * For the inodes to process list. + */ +static struct process_inode_block *inodes_to_process; +static int process_inode_count; + +static __u64 ext2_max_sizes[EXT2_MAX_BLOCK_LOG_SIZE - + EXT2_MIN_BLOCK_LOG_SIZE + 1]; + +/* + * Free all memory allocated by pass1 in preparation for restarting + * things. + */ +static void unwind_pass1(void) +{ + ext2fs_free_mem(&inodes_to_process); +} + +/* + * Check to make sure a device inode is real. Returns 1 if the device + * checks out, 0 if not. + * + * Note: this routine is now also used to check FIFO's and Sockets, + * since they have the same requirement; the i_block fields should be + * zero. + */ +static int +e2fsck_pass1_check_device_inode(ext2_filsys fs, struct ext2_inode *inode) +{ + int i; + + /* + * If i_blocks is non-zero, or the index flag is set, then + * this is a bogus device/fifo/socket + */ + if ((ext2fs_inode_data_blocks(fs, inode) != 0) || + (inode->i_flags & EXT2_INDEX_FL)) + return 0; + + /* + * We should be able to do the test below all the time, but + * because the kernel doesn't forcibly clear the device + * inode's additional i_block fields, there are some rare + * occasions when a legitimate device inode will have non-zero + * additional i_block fields. So for now, we only complain + * when the immutable flag is set, which should never happen + * for devices. (And that's when the problem is caused, since + * you can't set or clear immutable flags for devices.) Once + * the kernel has been fixed we can change this... + */ + if (inode->i_flags & (EXT2_IMMUTABLE_FL | EXT2_APPEND_FL)) { + for (i=4; i < EXT2_N_BLOCKS; i++) + if (inode->i_block[i]) + return 0; + } + return 1; +} + +/* + * Check to make sure a symlink inode is real. Returns 1 if the symlink + * checks out, 0 if not. + */ +static int +e2fsck_pass1_check_symlink(ext2_filsys fs, struct ext2_inode *inode, char *buf) +{ + unsigned int len; + int i; + blk_t blocks; + + if ((inode->i_size_high || inode->i_size == 0) || + (inode->i_flags & EXT2_INDEX_FL)) + return 0; + + blocks = ext2fs_inode_data_blocks(fs, inode); + if (blocks) { + if ((inode->i_size >= fs->blocksize) || + (blocks != fs->blocksize >> 9) || + (inode->i_block[0] < fs->super->s_first_data_block) || + (inode->i_block[0] >= fs->super->s_blocks_count)) + return 0; + + for (i = 1; i < EXT2_N_BLOCKS; i++) + if (inode->i_block[i]) + return 0; + + if (io_channel_read_blk(fs->io, inode->i_block[0], 1, buf)) + return 0; + + len = strnlen(buf, fs->blocksize); + if (len == fs->blocksize) + return 0; + } else { + if (inode->i_size >= sizeof(inode->i_block)) + return 0; + + len = strnlen((char *)inode->i_block, sizeof(inode->i_block)); + if (len == sizeof(inode->i_block)) + return 0; + } + if (len != inode->i_size) + return 0; + return 1; +} + +/* + * If the immutable (or append-only) flag is set on the inode, offer + * to clear it. + */ +#define BAD_SPECIAL_FLAGS (EXT2_IMMUTABLE_FL | EXT2_APPEND_FL) +static void check_immutable(e2fsck_t ctx, struct problem_context *pctx) +{ + if (!(pctx->inode->i_flags & BAD_SPECIAL_FLAGS)) + return; + + if (!fix_problem(ctx, PR_1_SET_IMMUTABLE, pctx)) + return; + + pctx->inode->i_flags &= ~BAD_SPECIAL_FLAGS; + e2fsck_write_inode(ctx, pctx->ino, pctx->inode, "pass1"); +} + +/* + * If device, fifo or socket, check size is zero -- if not offer to + * clear it + */ +static void check_size(e2fsck_t ctx, struct problem_context *pctx) +{ + struct ext2_inode *inode = pctx->inode; + + if ((inode->i_size == 0) && (inode->i_size_high == 0)) + return; + + if (!fix_problem(ctx, PR_1_SET_NONZSIZE, pctx)) + return; + + inode->i_size = 0; + inode->i_size_high = 0; + e2fsck_write_inode(ctx, pctx->ino, pctx->inode, "pass1"); +} + +static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + struct ext2_inode_large *inode; + struct ext2_ext_attr_entry *entry; + char *start, *end; + int storage_size, remain, offs; + int problem = 0; + + inode = (struct ext2_inode_large *) pctx->inode; + storage_size = EXT2_INODE_SIZE(ctx->fs->super) - EXT2_GOOD_OLD_INODE_SIZE - + inode->i_extra_isize; + start = ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + + inode->i_extra_isize + sizeof(__u32); + end = (char *) inode + EXT2_INODE_SIZE(ctx->fs->super); + entry = (struct ext2_ext_attr_entry *) start; + + /* scan all entry's headers first */ + + /* take finish entry 0UL into account */ + remain = storage_size - sizeof(__u32); + offs = end - start; + + while (!EXT2_EXT_IS_LAST_ENTRY(entry)) { + + /* header eats this space */ + remain -= sizeof(struct ext2_ext_attr_entry); + + /* is attribute name valid? */ + if (EXT2_EXT_ATTR_SIZE(entry->e_name_len) > remain) { + pctx->num = entry->e_name_len; + problem = PR_1_ATTR_NAME_LEN; + goto fix; + } + + /* attribute len eats this space */ + remain -= EXT2_EXT_ATTR_SIZE(entry->e_name_len); + + /* check value size */ + if (entry->e_value_size == 0 || entry->e_value_size > remain) { + pctx->num = entry->e_value_size; + problem = PR_1_ATTR_VALUE_SIZE; + goto fix; + } + + /* check value placement */ + if (entry->e_value_offs + + EXT2_XATTR_SIZE(entry->e_value_size) != offs) { + printf("(entry->e_value_offs + entry->e_value_size: %d, offs: %d)\n", entry->e_value_offs + entry->e_value_size, offs); + pctx->num = entry->e_value_offs; + problem = PR_1_ATTR_VALUE_OFFSET; + goto fix; + } + + /* e_value_block must be 0 in inode's ea */ + if (entry->e_value_block != 0) { + pctx->num = entry->e_value_block; + problem = PR_1_ATTR_VALUE_BLOCK; + goto fix; + } + + /* e_hash must be 0 in inode's ea */ + if (entry->e_hash != 0) { + pctx->num = entry->e_hash; + problem = PR_1_ATTR_HASH; + goto fix; + } + + remain -= entry->e_value_size; + offs -= EXT2_XATTR_SIZE(entry->e_value_size); + + entry = EXT2_EXT_ATTR_NEXT(entry); + } +fix: + /* + * it seems like a corruption. it's very unlikely we could repair + * EA(s) in automatic fashion -bzzz + */ + if (problem == 0 || !fix_problem(ctx, problem, pctx)) + return; + + /* simple remove all possible EA(s) */ + *((__u32 *)start) = 0UL; + e2fsck_write_inode_full(ctx, pctx->ino, (struct ext2_inode *)inode, + EXT2_INODE_SIZE(sb), "pass1"); +} + +static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + struct ext2_inode_large *inode; + __u32 *eamagic; + int min, max; + + inode = (struct ext2_inode_large *) pctx->inode; + if (EXT2_INODE_SIZE(sb) == EXT2_GOOD_OLD_INODE_SIZE) { + /* this isn't large inode. so, nothing to check */ + return; + } + + /* i_extra_isize must cover i_extra_isize + i_pad1 at least */ + min = sizeof(inode->i_extra_isize) + sizeof(inode->i_pad1); + max = EXT2_INODE_SIZE(sb) - EXT2_GOOD_OLD_INODE_SIZE; + /* + * For now we will allow i_extra_isize to be 0, but really + * implementations should never allow i_extra_isize to be 0 + */ + if (inode->i_extra_isize && + (inode->i_extra_isize < min || inode->i_extra_isize > max)) { + if (!fix_problem(ctx, PR_1_EXTRA_ISIZE, pctx)) + return; + inode->i_extra_isize = min; + e2fsck_write_inode_full(ctx, pctx->ino, pctx->inode, + EXT2_INODE_SIZE(sb), "pass1"); + return; + } + + eamagic = (__u32 *) (((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + + inode->i_extra_isize); + if (*eamagic == EXT2_EXT_ATTR_MAGIC) { + /* it seems inode has an extended attribute(s) in body */ + check_ea_in_inode(ctx, pctx); + } +} + +static void e2fsck_pass1(e2fsck_t ctx) +{ + int i; + __u64 max_sizes; + ext2_filsys fs = ctx->fs; + ext2_ino_t ino; + struct ext2_inode *inode; + ext2_inode_scan scan; + char *block_buf; + unsigned char frag, fsize; + struct problem_context pctx; + struct scan_callback_struct scan_struct; + struct ext2_super_block *sb = ctx->fs->super; + int imagic_fs; + int busted_fs_time = 0; + int inode_size; + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_1_PASS_HEADER, &pctx); + + if ((fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) && + !(ctx->options & E2F_OPT_NO)) { + if (ext2fs_u32_list_create(&ctx->dirs_to_hash, 50)) + ctx->dirs_to_hash = 0; + } + + /* Pass 1 */ + +#define EXT2_BPP(bits) (1ULL << ((bits) - 2)) + + for (i = EXT2_MIN_BLOCK_LOG_SIZE; i <= EXT2_MAX_BLOCK_LOG_SIZE; i++) { + max_sizes = EXT2_NDIR_BLOCKS + EXT2_BPP(i); + max_sizes = max_sizes + EXT2_BPP(i) * EXT2_BPP(i); + max_sizes = max_sizes + EXT2_BPP(i) * EXT2_BPP(i) * EXT2_BPP(i); + max_sizes = (max_sizes * (1UL << i)) - 1; + ext2_max_sizes[i - EXT2_MIN_BLOCK_LOG_SIZE] = max_sizes; + } +#undef EXT2_BPP + + imagic_fs = (sb->s_feature_compat & EXT2_FEATURE_COMPAT_IMAGIC_INODES); + + /* + * Allocate bitmaps structures + */ + pctx.errcode = ext2fs_allocate_inode_bitmap(fs, _("in-use inode map"), + &ctx->inode_used_map); + if (pctx.errcode) { + pctx.num = 1; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pctx.errcode = ext2fs_allocate_inode_bitmap(fs, + _("directory inode map"), &ctx->inode_dir_map); + if (pctx.errcode) { + pctx.num = 2; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pctx.errcode = ext2fs_allocate_inode_bitmap(fs, + _("regular file inode map"), &ctx->inode_reg_map); + if (pctx.errcode) { + pctx.num = 6; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pctx.errcode = ext2fs_allocate_block_bitmap(fs, _("in-use block map"), + &ctx->block_found_map); + if (pctx.errcode) { + pctx.num = 1; + fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pctx.errcode = ext2fs_create_icount2(fs, 0, 0, 0, + &ctx->inode_link_info); + if (pctx.errcode) { + fix_problem(ctx, PR_1_ALLOCATE_ICOUNT, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + inode_size = EXT2_INODE_SIZE(fs->super); + inode = (struct ext2_inode *) + e2fsck_allocate_memory(ctx, inode_size, "scratch inode"); + + inodes_to_process = (struct process_inode_block *) + e2fsck_allocate_memory(ctx, + (ctx->process_inode_size * + sizeof(struct process_inode_block)), + "array of inodes to process"); + process_inode_count = 0; + + pctx.errcode = ext2fs_init_dblist(fs, 0); + if (pctx.errcode) { + fix_problem(ctx, PR_1_ALLOCATE_DBCOUNT, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + /* + * If the last orphan field is set, clear it, since the pass1 + * processing will automatically find and clear the orphans. + * In the future, we may want to try using the last_orphan + * linked list ourselves, but for now, we clear it so that the + * ext3 mount code won't get confused. + */ + if (!(ctx->options & E2F_OPT_READONLY)) { + if (fs->super->s_last_orphan) { + fs->super->s_last_orphan = 0; + ext2fs_mark_super_dirty(fs); + } + } + + mark_table_blocks(ctx); + block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 3, + "block interate buffer"); + e2fsck_use_inode_shortcuts(ctx, 1); + ehandler_operation(_("doing inode scan")); + pctx.errcode = ext2fs_open_inode_scan(fs, ctx->inode_buffer_blocks, + &scan); + if (pctx.errcode) { + fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_inode_scan_flags(scan, EXT2_SF_SKIP_MISSING_ITABLE, 0); + ctx->stashed_inode = inode; + scan_struct.ctx = ctx; + scan_struct.block_buf = block_buf; + ext2fs_set_inode_callback(scan, scan_callback, &scan_struct); + if (ctx->progress) + if ((ctx->progress)(ctx, 1, 0, ctx->fs->group_desc_count)) + return; + if ((fs->super->s_wtime < fs->super->s_inodes_count) || + (fs->super->s_mtime < fs->super->s_inodes_count)) + busted_fs_time = 1; + + while (1) { + pctx.errcode = ext2fs_get_next_inode_full(scan, &ino, + inode, inode_size); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + if (pctx.errcode == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) { + continue; + } + if (pctx.errcode) { + fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (!ino) + break; + pctx.ino = ino; + pctx.inode = inode; + ctx->stashed_ino = ino; + if (inode->i_links_count) { + pctx.errcode = ext2fs_icount_store(ctx->inode_link_info, + ino, inode->i_links_count); + if (pctx.errcode) { + pctx.num = inode->i_links_count; + fix_problem(ctx, PR_1_ICOUNT_STORE, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + if (ino == EXT2_BAD_INO) { + struct process_block_struct_1 pb; + + pctx.errcode = ext2fs_copy_bitmap(ctx->block_found_map, + &pb.fs_meta_blocks); + if (pctx.errcode) { + pctx.num = 4; + fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pb.ino = EXT2_BAD_INO; + pb.num_blocks = pb.last_block = 0; + pb.num_illegal_blocks = 0; + pb.suppress = 0; pb.clear = 0; pb.is_dir = 0; + pb.is_reg = 0; pb.fragmented = 0; pb.bbcheck = 0; + pb.inode = inode; + pb.pctx = &pctx; + pb.ctx = ctx; + pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, + block_buf, process_bad_block, &pb); + ext2fs_free_block_bitmap(pb.fs_meta_blocks); + if (pctx.errcode) { + fix_problem(ctx, PR_1_BLOCK_ITERATE, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (pb.bbcheck) + if (!fix_problem(ctx, PR_1_BBINODE_BAD_METABLOCK_PROMPT, &pctx)) { + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); + clear_problem_context(&pctx); + continue; + } else if (ino == EXT2_ROOT_INO) { + /* + * Make sure the root inode is a directory; if + * not, offer to clear it. It will be + * regnerated in pass #3. + */ + if (!LINUX_S_ISDIR(inode->i_mode)) { + if (fix_problem(ctx, PR_1_ROOT_NO_DIR, &pctx)) { + inode->i_dtime = time(0); + inode->i_links_count = 0; + ext2fs_icount_store(ctx->inode_link_info, + ino, 0); + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + + } + /* + * If dtime is set, offer to clear it. mke2fs + * version 0.2b created filesystems with the + * dtime field set for the root and lost+found + * directories. We won't worry about + * /lost+found, since that can be regenerated + * easily. But we will fix the root directory + * as a special case. + */ + if (inode->i_dtime && inode->i_links_count) { + if (fix_problem(ctx, PR_1_ROOT_DTIME, &pctx)) { + inode->i_dtime = 0; + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + } + } else if (ino == EXT2_JOURNAL_INO) { + ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); + if (fs->super->s_journal_inum == EXT2_JOURNAL_INO) { + if (!LINUX_S_ISREG(inode->i_mode) && + fix_problem(ctx, PR_1_JOURNAL_BAD_MODE, + &pctx)) { + inode->i_mode = LINUX_S_IFREG; + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + check_blocks(ctx, &pctx, block_buf); + continue; + } + if ((inode->i_links_count || inode->i_blocks || + inode->i_blocks || inode->i_block[0]) && + fix_problem(ctx, PR_1_JOURNAL_INODE_NOT_CLEAR, + &pctx)) { + memset(inode, 0, inode_size); + ext2fs_icount_store(ctx->inode_link_info, + ino, 0); + e2fsck_write_inode_full(ctx, ino, inode, + inode_size, "pass1"); + } + } else if (ino < EXT2_FIRST_INODE(fs->super)) { + int problem = 0; + + ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); + if (ino == EXT2_BOOT_LOADER_INO) { + if (LINUX_S_ISDIR(inode->i_mode)) + problem = PR_1_RESERVED_BAD_MODE; + } else if (ino == EXT2_RESIZE_INO) { + if (inode->i_mode && + !LINUX_S_ISREG(inode->i_mode)) + problem = PR_1_RESERVED_BAD_MODE; + } else { + if (inode->i_mode != 0) + problem = PR_1_RESERVED_BAD_MODE; + } + if (problem) { + if (fix_problem(ctx, problem, &pctx)) { + inode->i_mode = 0; + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + } + check_blocks(ctx, &pctx, block_buf); + continue; + } + /* + * Check for inodes who might have been part of the + * orphaned list linked list. They should have gotten + * dealt with by now, unless the list had somehow been + * corrupted. + * + * FIXME: In the future, inodes which are still in use + * (and which are therefore) pending truncation should + * be handled specially. Right now we just clear the + * dtime field, and the normal e2fsck handling of + * inodes where i_size and the inode blocks are + * inconsistent is to fix i_size, instead of releasing + * the extra blocks. This won't catch the inodes that + * was at the end of the orphan list, but it's better + * than nothing. The right answer is that there + * shouldn't be any bugs in the orphan list handling. :-) + */ + if (inode->i_dtime && !busted_fs_time && + inode->i_dtime < ctx->fs->super->s_inodes_count) { + if (fix_problem(ctx, PR_1_LOW_DTIME, &pctx)) { + inode->i_dtime = inode->i_links_count ? + 0 : time(0); + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + } + + /* + * This code assumes that deleted inodes have + * i_links_count set to 0. + */ + if (!inode->i_links_count) { + if (!inode->i_dtime && inode->i_mode) { + if (fix_problem(ctx, + PR_1_ZERO_DTIME, &pctx)) { + inode->i_dtime = time(0); + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + } + continue; + } + /* + * n.b. 0.3c ext2fs code didn't clear i_links_count for + * deleted files. Oops. + * + * Since all new ext2 implementations get this right, + * we now assume that the case of non-zero + * i_links_count and non-zero dtime means that we + * should keep the file, not delete it. + * + */ + if (inode->i_dtime) { + if (fix_problem(ctx, PR_1_SET_DTIME, &pctx)) { + inode->i_dtime = 0; + e2fsck_write_inode(ctx, ino, inode, "pass1"); + } + } + + ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); + switch (fs->super->s_creator_os) { + case EXT2_OS_LINUX: + frag = inode->osd2.linux2.l_i_frag; + fsize = inode->osd2.linux2.l_i_fsize; + break; + case EXT2_OS_HURD: + frag = inode->osd2.hurd2.h_i_frag; + fsize = inode->osd2.hurd2.h_i_fsize; + break; + case EXT2_OS_MASIX: + frag = inode->osd2.masix2.m_i_frag; + fsize = inode->osd2.masix2.m_i_fsize; + break; + default: + frag = fsize = 0; + } + + if (inode->i_faddr || frag || fsize || + (LINUX_S_ISDIR(inode->i_mode) && inode->i_dir_acl)) + mark_inode_bad(ctx, ino); + if (inode->i_flags & EXT2_IMAGIC_FL) { + if (imagic_fs) { + if (!ctx->inode_imagic_map) + alloc_imagic_map(ctx); + ext2fs_mark_inode_bitmap(ctx->inode_imagic_map, + ino); + } else { + if (fix_problem(ctx, PR_1_SET_IMAGIC, &pctx)) { + inode->i_flags &= ~EXT2_IMAGIC_FL; + e2fsck_write_inode(ctx, ino, + inode, "pass1"); + } + } + } + + check_inode_extra_space(ctx, &pctx); + + if (LINUX_S_ISDIR(inode->i_mode)) { + ext2fs_mark_inode_bitmap(ctx->inode_dir_map, ino); + e2fsck_add_dir_info(ctx, ino, 0); + ctx->fs_directory_count++; + } else if (LINUX_S_ISREG (inode->i_mode)) { + ext2fs_mark_inode_bitmap(ctx->inode_reg_map, ino); + ctx->fs_regular_count++; + } else if (LINUX_S_ISCHR (inode->i_mode) && + e2fsck_pass1_check_device_inode(fs, inode)) { + check_immutable(ctx, &pctx); + check_size(ctx, &pctx); + ctx->fs_chardev_count++; + } else if (LINUX_S_ISBLK (inode->i_mode) && + e2fsck_pass1_check_device_inode(fs, inode)) { + check_immutable(ctx, &pctx); + check_size(ctx, &pctx); + ctx->fs_blockdev_count++; + } else if (LINUX_S_ISLNK (inode->i_mode) && + e2fsck_pass1_check_symlink(fs, inode, block_buf)) { + check_immutable(ctx, &pctx); + ctx->fs_symlinks_count++; + if (ext2fs_inode_data_blocks(fs, inode) == 0) { + ctx->fs_fast_symlinks_count++; + check_blocks(ctx, &pctx, block_buf); + continue; + } + } + else if (LINUX_S_ISFIFO (inode->i_mode) && + e2fsck_pass1_check_device_inode(fs, inode)) { + check_immutable(ctx, &pctx); + check_size(ctx, &pctx); + ctx->fs_fifo_count++; + } else if ((LINUX_S_ISSOCK (inode->i_mode)) && + e2fsck_pass1_check_device_inode(fs, inode)) { + check_immutable(ctx, &pctx); + check_size(ctx, &pctx); + ctx->fs_sockets_count++; + } else + mark_inode_bad(ctx, ino); + if (inode->i_block[EXT2_IND_BLOCK]) + ctx->fs_ind_count++; + if (inode->i_block[EXT2_DIND_BLOCK]) + ctx->fs_dind_count++; + if (inode->i_block[EXT2_TIND_BLOCK]) + ctx->fs_tind_count++; + if (inode->i_block[EXT2_IND_BLOCK] || + inode->i_block[EXT2_DIND_BLOCK] || + inode->i_block[EXT2_TIND_BLOCK] || + inode->i_file_acl) { + inodes_to_process[process_inode_count].ino = ino; + inodes_to_process[process_inode_count].inode = *inode; + process_inode_count++; + } else + check_blocks(ctx, &pctx, block_buf); + + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + + if (process_inode_count >= ctx->process_inode_size) { + process_inodes(ctx, block_buf); + + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + } + } + process_inodes(ctx, block_buf); + ext2fs_close_inode_scan(scan); + ehandler_operation(0); + + /* + * If any extended attribute blocks' reference counts need to + * be adjusted, either up (ctx->refcount_extra), or down + * (ctx->refcount), then fix them. + */ + if (ctx->refcount) { + adjust_extattr_refcount(ctx, ctx->refcount, block_buf, -1); + ea_refcount_free(ctx->refcount); + ctx->refcount = 0; + } + if (ctx->refcount_extra) { + adjust_extattr_refcount(ctx, ctx->refcount_extra, + block_buf, +1); + ea_refcount_free(ctx->refcount_extra); + ctx->refcount_extra = 0; + } + + if (ctx->invalid_bitmaps) + handle_fs_bad_blocks(ctx); + + /* We don't need the block_ea_map any more */ + ext2fs_free_block_bitmap(ctx->block_ea_map); + ctx->block_ea_map = 0; + + if (ctx->flags & E2F_FLAG_RESIZE_INODE) { + ext2fs_block_bitmap save_bmap; + + save_bmap = fs->block_map; + fs->block_map = ctx->block_found_map; + clear_problem_context(&pctx); + pctx.errcode = ext2fs_create_resize_inode(fs); + if (pctx.errcode) { + fix_problem(ctx, PR_1_RESIZE_INODE_CREATE, &pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return; + } + e2fsck_read_inode(ctx, EXT2_RESIZE_INO, inode, + "recreate inode"); + inode->i_mtime = time(0); + e2fsck_write_inode(ctx, EXT2_RESIZE_INO, inode, + "recreate inode"); + fs->block_map = save_bmap; + ctx->flags &= ~E2F_FLAG_RESIZE_INODE; + } + + if (ctx->flags & E2F_FLAG_RESTART) { + /* + * Only the master copy of the superblock and block + * group descriptors are going to be written during a + * restart, so set the superblock to be used to be the + * master superblock. + */ + ctx->use_superblock = 0; + unwind_pass1(); + goto endit; + } + + if (ctx->block_dup_map) { + if (ctx->options & E2F_OPT_PREEN) { + clear_problem_context(&pctx); + fix_problem(ctx, PR_1_DUP_BLOCKS_PREENSTOP, &pctx); + } + e2fsck_pass1_dupblocks(ctx, block_buf); + } + ext2fs_free_mem(&inodes_to_process); +endit: + e2fsck_use_inode_shortcuts(ctx, 0); + + ext2fs_free_mem(&block_buf); + ext2fs_free_mem(&inode); + +} + +/* + * When the inode_scan routines call this callback at the end of the + * glock group, call process_inodes. + */ +static errcode_t scan_callback(ext2_filsys fs, + dgrp_t group, void * priv_data) +{ + struct scan_callback_struct *scan_struct; + e2fsck_t ctx; + + scan_struct = (struct scan_callback_struct *) priv_data; + ctx = scan_struct->ctx; + + process_inodes((e2fsck_t) fs->priv_data, scan_struct->block_buf); + + if (ctx->progress) + if ((ctx->progress)(ctx, 1, group+1, + ctx->fs->group_desc_count)) + return EXT2_ET_CANCEL_REQUESTED; + + return 0; +} + +/* + * Process the inodes in the "inodes to process" list. + */ +static void process_inodes(e2fsck_t ctx, char *block_buf) +{ + int i; + struct ext2_inode *old_stashed_inode; + ext2_ino_t old_stashed_ino; + const char *old_operation; + char buf[80]; + struct problem_context pctx; + + /* begin process_inodes */ + if (process_inode_count == 0) + return; + old_operation = ehandler_operation(0); + old_stashed_inode = ctx->stashed_inode; + old_stashed_ino = ctx->stashed_ino; + qsort(inodes_to_process, process_inode_count, + sizeof(struct process_inode_block), process_inode_cmp); + clear_problem_context(&pctx); + for (i=0; i < process_inode_count; i++) { + pctx.inode = ctx->stashed_inode = &inodes_to_process[i].inode; + pctx.ino = ctx->stashed_ino = inodes_to_process[i].ino; + sprintf(buf, _("reading indirect blocks of inode %u"), + pctx.ino); + ehandler_operation(buf); + check_blocks(ctx, &pctx, block_buf); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + break; + } + ctx->stashed_inode = old_stashed_inode; + ctx->stashed_ino = old_stashed_ino; + process_inode_count = 0; + /* end process inodes */ + + ehandler_operation(old_operation); +} + +static int process_inode_cmp(const void *a, const void *b) +{ + const struct process_inode_block *ib_a = + (const struct process_inode_block *) a; + const struct process_inode_block *ib_b = + (const struct process_inode_block *) b; + int ret; + + ret = (ib_a->inode.i_block[EXT2_IND_BLOCK] - + ib_b->inode.i_block[EXT2_IND_BLOCK]); + if (ret == 0) + ret = ib_a->inode.i_file_acl - ib_b->inode.i_file_acl; + return ret; +} + +/* + * Mark an inode as being bad in some what + */ +static void mark_inode_bad(e2fsck_t ctx, ino_t ino) +{ + struct problem_context pctx; + + if (!ctx->inode_bad_map) { + clear_problem_context(&pctx); + + pctx.errcode = ext2fs_allocate_inode_bitmap(ctx->fs, + _("bad inode map"), &ctx->inode_bad_map); + if (pctx.errcode) { + pctx.num = 3; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + ext2fs_mark_inode_bitmap(ctx->inode_bad_map, ino); +} + + +/* + * This procedure will allocate the inode imagic table + */ +static void alloc_imagic_map(e2fsck_t ctx) +{ + struct problem_context pctx; + + clear_problem_context(&pctx); + pctx.errcode = ext2fs_allocate_inode_bitmap(ctx->fs, + _("imagic inode map"), + &ctx->inode_imagic_map); + if (pctx.errcode) { + pctx.num = 5; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return; + } +} + +/* + * Marks a block as in use, setting the dup_map if it's been set + * already. Called by process_block and process_bad_block. + * + * WARNING: Assumes checks have already been done to make sure block + * is valid. This is true in both process_block and process_bad_block. + */ +static void mark_block_used(e2fsck_t ctx, blk_t block) +{ + struct problem_context pctx; + + clear_problem_context(&pctx); + + if (ext2fs_fast_test_block_bitmap(ctx->block_found_map, block)) { + if (!ctx->block_dup_map) { + pctx.errcode = ext2fs_allocate_block_bitmap(ctx->fs, + _("multiply claimed block map"), + &ctx->block_dup_map); + if (pctx.errcode) { + pctx.num = 3; + fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, + &pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + ext2fs_fast_mark_block_bitmap(ctx->block_dup_map, block); + } else { + ext2fs_fast_mark_block_bitmap(ctx->block_found_map, block); + } +} + +/* + * Adjust the extended attribute block's reference counts at the end + * of pass 1, either by subtracting out references for EA blocks that + * are still referenced in ctx->refcount, or by adding references for + * EA blocks that had extra references as accounted for in + * ctx->refcount_extra. + */ +static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount, + char *block_buf, int adjust_sign) +{ + struct ext2_ext_attr_header *header; + struct problem_context pctx; + ext2_filsys fs = ctx->fs; + blk_t blk; + __u32 should_be; + int count; + + clear_problem_context(&pctx); + + ea_refcount_intr_begin(refcount); + while (1) { + if ((blk = ea_refcount_intr_next(refcount, &count)) == 0) + break; + pctx.blk = blk; + pctx.errcode = ext2fs_read_ext_attr(fs, blk, block_buf); + if (pctx.errcode) { + fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, &pctx); + return; + } + header = (struct ext2_ext_attr_header *) block_buf; + pctx.blkcount = header->h_refcount; + should_be = header->h_refcount + adjust_sign * count; + pctx.num = should_be; + if (fix_problem(ctx, PR_1_EXTATTR_REFCOUNT, &pctx)) { + header->h_refcount = should_be; + pctx.errcode = ext2fs_write_ext_attr(fs, blk, + block_buf); + if (pctx.errcode) { + fix_problem(ctx, PR_1_EXTATTR_WRITE, &pctx); + continue; + } + } + } +} + +/* + * Handle processing the extended attribute blocks + */ +static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, + char *block_buf) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t ino = pctx->ino; + struct ext2_inode *inode = pctx->inode; + blk_t blk; + char * end; + struct ext2_ext_attr_header *header; + struct ext2_ext_attr_entry *entry; + int count; + region_t region; + + blk = inode->i_file_acl; + if (blk == 0) + return 0; + + /* + * If the Extended attribute flag isn't set, then a non-zero + * file acl means that the inode is corrupted. + * + * Or if the extended attribute block is an invalid block, + * then the inode is also corrupted. + */ + if (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR) || + (blk < fs->super->s_first_data_block) || + (blk >= fs->super->s_blocks_count)) { + mark_inode_bad(ctx, ino); + return 0; + } + + /* If ea bitmap hasn't been allocated, create it */ + if (!ctx->block_ea_map) { + pctx->errcode = ext2fs_allocate_block_bitmap(fs, + _("ext attr block map"), + &ctx->block_ea_map); + if (pctx->errcode) { + pctx->num = 2; + fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return 0; + } + } + + /* Create the EA refcount structure if necessary */ + if (!ctx->refcount) { + pctx->errcode = ea_refcount_create(0, &ctx->refcount); + if (pctx->errcode) { + pctx->num = 1; + fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return 0; + } + } + + /* Have we seen this EA block before? */ + if (ext2fs_fast_test_block_bitmap(ctx->block_ea_map, blk)) { + if (ea_refcount_decrement(ctx->refcount, blk, 0) == 0) + return 1; + /* Ooops, this EA was referenced more than it stated */ + if (!ctx->refcount_extra) { + pctx->errcode = ea_refcount_create(0, + &ctx->refcount_extra); + if (pctx->errcode) { + pctx->num = 2; + fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return 0; + } + } + ea_refcount_increment(ctx->refcount_extra, blk, 0); + return 1; + } + + /* + * OK, we haven't seen this EA block yet. So we need to + * validate it + */ + pctx->blk = blk; + pctx->errcode = ext2fs_read_ext_attr(fs, blk, block_buf); + if (pctx->errcode && fix_problem(ctx, PR_1_READ_EA_BLOCK, pctx)) + goto clear_extattr; + header = (struct ext2_ext_attr_header *) block_buf; + pctx->blk = inode->i_file_acl; + if (((ctx->ext_attr_ver == 1) && + (header->h_magic != EXT2_EXT_ATTR_MAGIC_v1)) || + ((ctx->ext_attr_ver == 2) && + (header->h_magic != EXT2_EXT_ATTR_MAGIC))) { + if (fix_problem(ctx, PR_1_BAD_EA_BLOCK, pctx)) + goto clear_extattr; + } + + if (header->h_blocks != 1) { + if (fix_problem(ctx, PR_1_EA_MULTI_BLOCK, pctx)) + goto clear_extattr; + } + + region = region_create(0, fs->blocksize); + if (!region) { + fix_problem(ctx, PR_1_EA_ALLOC_REGION, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return 0; + } + if (region_allocate(region, 0, sizeof(struct ext2_ext_attr_header))) { + if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) + goto clear_extattr; + } + + entry = (struct ext2_ext_attr_entry *)(header+1); + end = block_buf + fs->blocksize; + while ((char *)entry < end && *(__u32 *)entry) { + if (region_allocate(region, (char *)entry - (char *)header, + EXT2_EXT_ATTR_LEN(entry->e_name_len))) { + if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) + goto clear_extattr; + } + if ((ctx->ext_attr_ver == 1 && + (entry->e_name_len == 0 || entry->e_name_index != 0)) || + (ctx->ext_attr_ver == 2 && + entry->e_name_index == 0)) { + if (fix_problem(ctx, PR_1_EA_BAD_NAME, pctx)) + goto clear_extattr; + } + if (entry->e_value_block != 0) { + if (fix_problem(ctx, PR_1_EA_BAD_VALUE, pctx)) + goto clear_extattr; + } + if (entry->e_value_size && + region_allocate(region, entry->e_value_offs, + EXT2_EXT_ATTR_SIZE(entry->e_value_size))) { + if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) + goto clear_extattr; + } + entry = EXT2_EXT_ATTR_NEXT(entry); + } + if (region_allocate(region, (char *)entry - (char *)header, 4)) { + if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) + goto clear_extattr; + } + region_free(region); + + count = header->h_refcount - 1; + if (count) + ea_refcount_store(ctx->refcount, blk, count); + mark_block_used(ctx, blk); + ext2fs_fast_mark_block_bitmap(ctx->block_ea_map, blk); + + return 1; + +clear_extattr: + inode->i_file_acl = 0; + e2fsck_write_inode(ctx, ino, inode, "check_ext_attr"); + return 0; +} + +/* Returns 1 if bad htree, 0 if OK */ +static int handle_htree(e2fsck_t ctx, struct problem_context *pctx, + ext2_ino_t ino FSCK_ATTR((unused)), + struct ext2_inode *inode, + char *block_buf) +{ + struct ext2_dx_root_info *root; + ext2_filsys fs = ctx->fs; + errcode_t retval; + blk_t blk; + + if ((!LINUX_S_ISDIR(inode->i_mode) && + fix_problem(ctx, PR_1_HTREE_NODIR, pctx)) || + (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) && + fix_problem(ctx, PR_1_HTREE_SET, pctx))) + return 1; + + blk = inode->i_block[0]; + if (((blk == 0) || + (blk < fs->super->s_first_data_block) || + (blk >= fs->super->s_blocks_count)) && + fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) + return 1; + + retval = io_channel_read_blk(fs->io, blk, 1, block_buf); + if (retval && fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) + return 1; + + /* XXX should check that beginning matches a directory */ + root = (struct ext2_dx_root_info *) (block_buf + 24); + + if ((root->reserved_zero || root->info_length < 8) && + fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) + return 1; + + pctx->num = root->hash_version; + if ((root->hash_version != EXT2_HASH_LEGACY) && + (root->hash_version != EXT2_HASH_HALF_MD4) && + (root->hash_version != EXT2_HASH_TEA) && + fix_problem(ctx, PR_1_HTREE_HASHV, pctx)) + return 1; + + if ((root->unused_flags & EXT2_HASH_FLAG_INCOMPAT) && + fix_problem(ctx, PR_1_HTREE_INCOMPAT, pctx)) + return 1; + + pctx->num = root->indirect_levels; + if ((root->indirect_levels > 1) && + fix_problem(ctx, PR_1_HTREE_DEPTH, pctx)) + return 1; + + return 0; +} + +/* + * This subroutine is called on each inode to account for all of the + * blocks used by that inode. + */ +static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, + char *block_buf) +{ + ext2_filsys fs = ctx->fs; + struct process_block_struct_1 pb; + ext2_ino_t ino = pctx->ino; + struct ext2_inode *inode = pctx->inode; + int bad_size = 0; + int dirty_inode = 0; + __u64 size; + + pb.ino = ino; + pb.num_blocks = 0; + pb.last_block = -1; + pb.num_illegal_blocks = 0; + pb.suppress = 0; pb.clear = 0; + pb.fragmented = 0; + pb.compressed = 0; + pb.previous_block = 0; + pb.is_dir = LINUX_S_ISDIR(inode->i_mode); + pb.is_reg = LINUX_S_ISREG(inode->i_mode); + pb.max_blocks = 1 << (31 - fs->super->s_log_block_size); + pb.inode = inode; + pb.pctx = pctx; + pb.ctx = ctx; + pctx->ino = ino; + pctx->errcode = 0; + + if (inode->i_flags & EXT2_COMPRBLK_FL) { + if (fs->super->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_COMPRESSION) + pb.compressed = 1; + else { + if (fix_problem(ctx, PR_1_COMPR_SET, pctx)) { + inode->i_flags &= ~EXT2_COMPRBLK_FL; + dirty_inode++; + } + } + } + + if (inode->i_file_acl && check_ext_attr(ctx, pctx, block_buf)) + pb.num_blocks++; + + if (ext2fs_inode_has_valid_blocks(inode)) + pctx->errcode = ext2fs_block_iterate2(fs, ino, + pb.is_dir ? BLOCK_FLAG_HOLE : 0, + block_buf, process_block, &pb); + end_problem_latch(ctx, PR_LATCH_BLOCK); + end_problem_latch(ctx, PR_LATCH_TOOBIG); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + goto out; + if (pctx->errcode) + fix_problem(ctx, PR_1_BLOCK_ITERATE, pctx); + + if (pb.fragmented && pb.num_blocks < fs->super->s_blocks_per_group) + ctx->fs_fragmented++; + + if (pb.clear) { + inode->i_links_count = 0; + ext2fs_icount_store(ctx->inode_link_info, ino, 0); + inode->i_dtime = time(0); + dirty_inode++; + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_reg_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); + /* + * The inode was probably partially accounted for + * before processing was aborted, so we need to + * restart the pass 1 scan. + */ + ctx->flags |= E2F_FLAG_RESTART; + goto out; + } + + if (inode->i_flags & EXT2_INDEX_FL) { + if (handle_htree(ctx, pctx, ino, inode, block_buf)) { + inode->i_flags &= ~EXT2_INDEX_FL; + dirty_inode++; + } else { +#ifdef ENABLE_HTREE + e2fsck_add_dx_dir(ctx, ino, pb.last_block+1); +#endif + } + } + if (ctx->dirs_to_hash && pb.is_dir && + !(inode->i_flags & EXT2_INDEX_FL) && + ((inode->i_size / fs->blocksize) >= 3)) + ext2fs_u32_list_add(ctx->dirs_to_hash, ino); + + if (!pb.num_blocks && pb.is_dir) { + if (fix_problem(ctx, PR_1_ZERO_LENGTH_DIR, pctx)) { + inode->i_links_count = 0; + ext2fs_icount_store(ctx->inode_link_info, ino, 0); + inode->i_dtime = time(0); + dirty_inode++; + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_reg_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); + ctx->fs_directory_count--; + goto out; + } + } + + pb.num_blocks *= (fs->blocksize / 512); + + if (pb.is_dir) { + int nblock = inode->i_size >> EXT2_BLOCK_SIZE_BITS(fs->super); + if (nblock > (pb.last_block + 1)) + bad_size = 1; + else if (nblock < (pb.last_block + 1)) { + if (((pb.last_block + 1) - nblock) > + fs->super->s_prealloc_dir_blocks) + bad_size = 2; + } + } else { + size = EXT2_I_SIZE(inode); + if ((pb.last_block >= 0) && + (size < (__u64) pb.last_block * fs->blocksize)) + bad_size = 3; + else if (size > ext2_max_sizes[fs->super->s_log_block_size]) + bad_size = 4; + } + /* i_size for symlinks is checked elsewhere */ + if (bad_size && !LINUX_S_ISLNK(inode->i_mode)) { + pctx->num = (pb.last_block+1) * fs->blocksize; + if (fix_problem(ctx, PR_1_BAD_I_SIZE, pctx)) { + inode->i_size = pctx->num; + if (!LINUX_S_ISDIR(inode->i_mode)) + inode->i_size_high = pctx->num >> 32; + dirty_inode++; + } + pctx->num = 0; + } + if (LINUX_S_ISREG(inode->i_mode) && + (inode->i_size_high || inode->i_size & 0x80000000UL)) + ctx->large_files++; + if (pb.num_blocks != inode->i_blocks) { + pctx->num = pb.num_blocks; + if (fix_problem(ctx, PR_1_BAD_I_BLOCKS, pctx)) { + inode->i_blocks = pb.num_blocks; + dirty_inode++; + } + pctx->num = 0; + } +out: + if (dirty_inode) + e2fsck_write_inode(ctx, ino, inode, "check_blocks"); +} + + +/* + * This is a helper function for check_blocks(). + */ +static int process_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct process_block_struct_1 *p; + struct problem_context *pctx; + blk_t blk = *block_nr; + int ret_code = 0; + int problem = 0; + e2fsck_t ctx; + + p = (struct process_block_struct_1 *) priv_data; + pctx = p->pctx; + ctx = p->ctx; + + if (p->compressed && (blk == EXT2FS_COMPRESSED_BLKADDR)) { + /* todo: Check that the comprblk_fl is high, that the + blkaddr pattern looks right (all non-holes up to + first EXT2FS_COMPRESSED_BLKADDR, then all + EXT2FS_COMPRESSED_BLKADDR up to end of cluster), + that the feature_incompat bit is high, and that the + inode is a regular file. If we're doing a "full + check" (a concept introduced to e2fsck by e2compr, + meaning that we look at data blocks as well as + metadata) then call some library routine that + checks the compressed data. I'll have to think + about this, because one particularly important + problem to be able to fix is to recalculate the + cluster size if necessary. I think that perhaps + we'd better do most/all e2compr-specific checks + separately, after the non-e2compr checks. If not + doing a full check, it may be useful to test that + the personality is linux; e.g. if it isn't then + perhaps this really is just an illegal block. */ + return 0; + } + + if (blk == 0) { + if (p->is_dir == 0) { + /* + * Should never happen, since only directories + * get called with BLOCK_FLAG_HOLE + */ +#ifdef DEBUG_E2FSCK + printf("process_block() called with blk == 0, " + "blockcnt=%d, inode %lu???\n", + blockcnt, p->ino); +#endif + return 0; + } + if (blockcnt < 0) + return 0; + if (blockcnt * fs->blocksize < p->inode->i_size) { + goto mark_dir; + } + return 0; + } + + /* + * Simplistic fragmentation check. We merely require that the + * file be contiguous. (Which can never be true for really + * big files that are greater than a block group.) + */ + if (!HOLE_BLKADDR(p->previous_block)) { + if (p->previous_block+1 != blk) + p->fragmented = 1; + } + p->previous_block = blk; + + if (p->is_dir && blockcnt > (1 << (21 - fs->super->s_log_block_size))) + problem = PR_1_TOOBIG_DIR; + if (p->is_reg && p->num_blocks+1 >= p->max_blocks) + problem = PR_1_TOOBIG_REG; + if (!p->is_dir && !p->is_reg && blockcnt > 0) + problem = PR_1_TOOBIG_SYMLINK; + + if (blk < fs->super->s_first_data_block || + blk >= fs->super->s_blocks_count) + problem = PR_1_ILLEGAL_BLOCK_NUM; + + if (problem) { + p->num_illegal_blocks++; + if (!p->suppress && (p->num_illegal_blocks % 12) == 0) { + if (fix_problem(ctx, PR_1_TOO_MANY_BAD_BLOCKS, pctx)) { + p->clear = 1; + return BLOCK_ABORT; + } + if (fix_problem(ctx, PR_1_SUPPRESS_MESSAGES, pctx)) { + p->suppress = 1; + set_latch_flags(PR_LATCH_BLOCK, + PRL_SUPPRESS, 0); + } + } + pctx->blk = blk; + pctx->blkcount = blockcnt; + if (fix_problem(ctx, problem, pctx)) { + blk = *block_nr = 0; + ret_code = BLOCK_CHANGED; + goto mark_dir; + } else + return 0; + } + + if (p->ino == EXT2_RESIZE_INO) { + /* + * The resize inode has already be sanity checked + * during pass #0 (the superblock checks). All we + * have to do is mark the double indirect block as + * being in use; all of the other blocks are handled + * by mark_table_blocks()). + */ + if (blockcnt == BLOCK_COUNT_DIND) + mark_block_used(ctx, blk); + } else + mark_block_used(ctx, blk); + p->num_blocks++; + if (blockcnt >= 0) + p->last_block = blockcnt; +mark_dir: + if (p->is_dir && (blockcnt >= 0)) { + pctx->errcode = ext2fs_add_dir_block(fs->dblist, p->ino, + blk, blockcnt); + if (pctx->errcode) { + pctx->blk = blk; + pctx->num = blockcnt; + fix_problem(ctx, PR_1_ADD_DBLOCK, pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return BLOCK_ABORT; + } + } + return ret_code; +} + +static int process_bad_block(ext2_filsys fs FSCK_ATTR((unused)), + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data EXT2FS_ATTR((unused))) +{ + /* + * Note: This function processes blocks for the bad blocks + * inode, which is never compressed. So we don't use HOLE_BLKADDR(). + */ + + printf("Unrecoverable Error: Found %"PRIi64" bad blocks starting at block number: %u\n", blockcnt, *block_nr); + return BLOCK_ERROR; +} + +/* + * This routine gets called at the end of pass 1 if bad blocks are + * detected in the superblock, group descriptors, inode_bitmaps, or + * block bitmaps. At this point, all of the blocks have been mapped + * out, so we can try to allocate new block(s) to replace the bad + * blocks. + */ +static void handle_fs_bad_blocks(e2fsck_t ctx) +{ + printf("Bad blocks detected on your filesystem\n" + "You should get your data off as the device will soon die\n"); +} + +/* + * This routine marks all blocks which are used by the superblock, + * group descriptors, inode bitmaps, and block bitmaps. + */ +static void mark_table_blocks(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk_t block, b; + dgrp_t i; + int j; + struct problem_context pctx; + + clear_problem_context(&pctx); + + block = fs->super->s_first_data_block; + for (i = 0; i < fs->group_desc_count; i++) { + pctx.group = i; + + ext2fs_reserve_super_and_bgd(fs, i, ctx->block_found_map); + + /* + * Mark the blocks used for the inode table + */ + if (fs->group_desc[i].bg_inode_table) { + for (j = 0, b = fs->group_desc[i].bg_inode_table; + j < fs->inode_blocks_per_group; + j++, b++) { + if (ext2fs_test_block_bitmap(ctx->block_found_map, + b)) { + pctx.blk = b; + if (fix_problem(ctx, + PR_1_ITABLE_CONFLICT, &pctx)) { + ctx->invalid_inode_table_flag[i]++; + ctx->invalid_bitmaps++; + } + } else { + ext2fs_mark_block_bitmap(ctx->block_found_map, + b); + } + } + } + + /* + * Mark block used for the block bitmap + */ + if (fs->group_desc[i].bg_block_bitmap) { + if (ext2fs_test_block_bitmap(ctx->block_found_map, + fs->group_desc[i].bg_block_bitmap)) { + pctx.blk = fs->group_desc[i].bg_block_bitmap; + if (fix_problem(ctx, PR_1_BB_CONFLICT, &pctx)) { + ctx->invalid_block_bitmap_flag[i]++; + ctx->invalid_bitmaps++; + } + } else { + ext2fs_mark_block_bitmap(ctx->block_found_map, + fs->group_desc[i].bg_block_bitmap); + } + + } + /* + * Mark block used for the inode bitmap + */ + if (fs->group_desc[i].bg_inode_bitmap) { + if (ext2fs_test_block_bitmap(ctx->block_found_map, + fs->group_desc[i].bg_inode_bitmap)) { + pctx.blk = fs->group_desc[i].bg_inode_bitmap; + if (fix_problem(ctx, PR_1_IB_CONFLICT, &pctx)) { + ctx->invalid_inode_bitmap_flag[i]++; + ctx->invalid_bitmaps++; + } + } else { + ext2fs_mark_block_bitmap(ctx->block_found_map, + fs->group_desc[i].bg_inode_bitmap); + } + } + block += fs->super->s_blocks_per_group; + } +} + +/* + * Thes subroutines short circuits ext2fs_get_blocks and + * ext2fs_check_directory; we use them since we already have the inode + * structure, so there's no point in letting the ext2fs library read + * the inode again. + */ +static errcode_t pass1_get_blocks(ext2_filsys fs, ext2_ino_t ino, + blk_t *blocks) +{ + e2fsck_t ctx = (e2fsck_t) fs->priv_data; + int i; + + if ((ino != ctx->stashed_ino) || !ctx->stashed_inode) + return EXT2_ET_CALLBACK_NOTHANDLED; + + for (i=0; i < EXT2_N_BLOCKS; i++) + blocks[i] = ctx->stashed_inode->i_block[i]; + return 0; +} + +static errcode_t pass1_read_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode) +{ + e2fsck_t ctx = (e2fsck_t) fs->priv_data; + + if ((ino != ctx->stashed_ino) || !ctx->stashed_inode) + return EXT2_ET_CALLBACK_NOTHANDLED; + *inode = *ctx->stashed_inode; + return 0; +} + +static errcode_t pass1_write_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode) +{ + e2fsck_t ctx = (e2fsck_t) fs->priv_data; + + if ((ino == ctx->stashed_ino) && ctx->stashed_inode) + *ctx->stashed_inode = *inode; + return EXT2_ET_CALLBACK_NOTHANDLED; +} + +static errcode_t pass1_check_directory(ext2_filsys fs, ext2_ino_t ino) +{ + e2fsck_t ctx = (e2fsck_t) fs->priv_data; + + if ((ino != ctx->stashed_ino) || !ctx->stashed_inode) + return EXT2_ET_CALLBACK_NOTHANDLED; + + if (!LINUX_S_ISDIR(ctx->stashed_inode->i_mode)) + return EXT2_ET_NO_DIRECTORY; + return 0; +} + +void e2fsck_use_inode_shortcuts(e2fsck_t ctx, int bool) +{ + ext2_filsys fs = ctx->fs; + + if (bool) { + fs->get_blocks = pass1_get_blocks; + fs->check_directory = pass1_check_directory; + fs->read_inode = pass1_read_inode; + fs->write_inode = pass1_write_inode; + ctx->stashed_ino = 0; + } else { + fs->get_blocks = 0; + fs->check_directory = 0; + fs->read_inode = 0; + fs->write_inode = 0; + } +} + +/* + * pass1b.c --- Pass #1b of e2fsck + * + * This file contains pass1B, pass1C, and pass1D of e2fsck. They are + * only invoked if pass 1 discovered blocks which are in use by more + * than one inode. + * + * Pass1B scans the data blocks of all the inodes again, generating a + * complete list of duplicate blocks and which inodes have claimed + * them. + * + * Pass1C does a tree-traversal of the filesystem, to determine the + * parent directories of these inodes. This step is necessary so that + * e2fsck can print out the pathnames of affected inodes. + * + * Pass1D is a reconciliation pass. For each inode with duplicate + * blocks, the user is prompted if s/he would like to clone the file + * (so that the file gets a fresh copy of the duplicated blocks) or + * simply to delete the file. + * + */ + + +/* Needed for architectures where sizeof(int) != sizeof(void *) */ +#define INT_TO_VOIDPTR(val) ((void *)(intptr_t)(val)) +#define VOIDPTR_TO_INT(ptr) ((int)(intptr_t)(ptr)) + +/* Define an extension to the ext2 library's block count information */ +#define BLOCK_COUNT_EXTATTR (-5) + +struct block_el { + blk_t block; + struct block_el *next; +}; + +struct inode_el { + ext2_ino_t inode; + struct inode_el *next; +}; + +struct dup_block { + int num_bad; + struct inode_el *inode_list; +}; + +/* + * This structure stores information about a particular inode which + * is sharing blocks with other inodes. This information is collected + * to display to the user, so that the user knows what files he or she + * is dealing with, when trying to decide how to resolve the conflict + * of multiply-claimed blocks. + */ +struct dup_inode { + ext2_ino_t dir; + int num_dupblocks; + struct ext2_inode inode; + struct block_el *block_list; +}; + +static int process_pass1b_block(ext2_filsys fs, blk_t *blocknr, + e2_blkcnt_t blockcnt, blk_t ref_blk, + int ref_offset, void *priv_data); +static void delete_file(e2fsck_t ctx, ext2_ino_t ino, + struct dup_inode *dp, char *block_buf); +static int clone_file(e2fsck_t ctx, ext2_ino_t ino, + struct dup_inode *dp, char* block_buf); +static int check_if_fs_block(e2fsck_t ctx, blk_t test_blk); + +static void pass1b(e2fsck_t ctx, char *block_buf); +static void pass1c(e2fsck_t ctx, char *block_buf); +static void pass1d(e2fsck_t ctx, char *block_buf); + +static int dup_inode_count = 0; + +static dict_t blk_dict, ino_dict; + +static ext2fs_inode_bitmap inode_dup_map; + +static int dict_int_cmp(const void *a, const void *b) +{ + intptr_t ia, ib; + + ia = (intptr_t)a; + ib = (intptr_t)b; + + return (ia-ib); +} + +/* + * Add a duplicate block record + */ +static void add_dupe(e2fsck_t ctx, ext2_ino_t ino, blk_t blk, + struct ext2_inode *inode) +{ + dnode_t *n; + struct dup_block *db; + struct dup_inode *di; + struct block_el *blk_el; + struct inode_el *ino_el; + + n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(blk)); + if (n) + db = (struct dup_block *) dnode_get(n); + else { + db = (struct dup_block *) e2fsck_allocate_memory(ctx, + sizeof(struct dup_block), "duplicate block header"); + db->num_bad = 0; + db->inode_list = 0; + dict_alloc_insert(&blk_dict, INT_TO_VOIDPTR(blk), db); + } + ino_el = (struct inode_el *) e2fsck_allocate_memory(ctx, + sizeof(struct inode_el), "inode element"); + ino_el->inode = ino; + ino_el->next = db->inode_list; + db->inode_list = ino_el; + db->num_bad++; + + n = dict_lookup(&ino_dict, INT_TO_VOIDPTR(ino)); + if (n) + di = (struct dup_inode *) dnode_get(n); + else { + di = (struct dup_inode *) e2fsck_allocate_memory(ctx, + sizeof(struct dup_inode), "duplicate inode header"); + di->dir = (ino == EXT2_ROOT_INO) ? EXT2_ROOT_INO : 0 ; + di->num_dupblocks = 0; + di->block_list = 0; + di->inode = *inode; + dict_alloc_insert(&ino_dict, INT_TO_VOIDPTR(ino), di); + } + blk_el = (struct block_el *) e2fsck_allocate_memory(ctx, + sizeof(struct block_el), "block element"); + blk_el->block = blk; + blk_el->next = di->block_list; + di->block_list = blk_el; + di->num_dupblocks++; +} + +/* + * Free a duplicate inode record + */ +static void inode_dnode_free(dnode_t *node) +{ + struct dup_inode *di; + struct block_el *p, *next; + + di = (struct dup_inode *) dnode_get(node); + for (p = di->block_list; p; p = next) { + next = p->next; + free(p); + } + free(node); +} + +/* + * Free a duplicate block record + */ +static void block_dnode_free(dnode_t *node) +{ + struct dup_block *db; + struct inode_el *p, *next; + + db = (struct dup_block *) dnode_get(node); + for (p = db->inode_list; p; p = next) { + next = p->next; + free(p); + } + free(node); +} + + +/* + * Main procedure for handling duplicate blocks + */ +void e2fsck_pass1_dupblocks(e2fsck_t ctx, char *block_buf) +{ + ext2_filsys fs = ctx->fs; + struct problem_context pctx; + + clear_problem_context(&pctx); + + pctx.errcode = ext2fs_allocate_inode_bitmap(fs, + _("multiply claimed inode map"), &inode_dup_map); + if (pctx.errcode) { + fix_problem(ctx, PR_1B_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + dict_init(&ino_dict, DICTCOUNT_T_MAX, dict_int_cmp); + dict_init(&blk_dict, DICTCOUNT_T_MAX, dict_int_cmp); + dict_set_allocator(&ino_dict, inode_dnode_free); + dict_set_allocator(&blk_dict, block_dnode_free); + + pass1b(ctx, block_buf); + pass1c(ctx, block_buf); + pass1d(ctx, block_buf); + + /* + * Time to free all of the accumulated data structures that we + * don't need anymore. + */ + dict_free_nodes(&ino_dict); + dict_free_nodes(&blk_dict); +} + +/* + * Scan the inodes looking for inodes that contain duplicate blocks. + */ +struct process_block_struct_1b { + e2fsck_t ctx; + ext2_ino_t ino; + int dup_blocks; + struct ext2_inode *inode; + struct problem_context *pctx; +}; + +static void pass1b(e2fsck_t ctx, char *block_buf) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t ino; + struct ext2_inode inode; + ext2_inode_scan scan; + struct process_block_struct_1b pb; + struct problem_context pctx; + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_1B_PASS_HEADER, &pctx); + pctx.errcode = ext2fs_open_inode_scan(fs, ctx->inode_buffer_blocks, + &scan); + if (pctx.errcode) { + fix_problem(ctx, PR_1B_ISCAN_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ctx->stashed_inode = &inode; + pb.ctx = ctx; + pb.pctx = &pctx; + pctx.str = "pass1b"; + while (1) { + pctx.errcode = ext2fs_get_next_inode(scan, &ino, &inode); + if (pctx.errcode == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) + continue; + if (pctx.errcode) { + fix_problem(ctx, PR_1B_ISCAN_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (!ino) + break; + pctx.ino = ctx->stashed_ino = ino; + if ((ino != EXT2_BAD_INO) && + !ext2fs_test_inode_bitmap(ctx->inode_used_map, ino)) + continue; + + pb.ino = ino; + pb.dup_blocks = 0; + pb.inode = &inode; + + if (ext2fs_inode_has_valid_blocks(&inode) || + (ino == EXT2_BAD_INO)) + pctx.errcode = ext2fs_block_iterate2(fs, ino, + 0, block_buf, process_pass1b_block, &pb); + if (inode.i_file_acl) + process_pass1b_block(fs, &inode.i_file_acl, + BLOCK_COUNT_EXTATTR, 0, 0, &pb); + if (pb.dup_blocks) { + end_problem_latch(ctx, PR_LATCH_DBLOCK); + if (ino >= EXT2_FIRST_INODE(fs->super) || + ino == EXT2_ROOT_INO) + dup_inode_count++; + } + if (pctx.errcode) + fix_problem(ctx, PR_1B_BLOCK_ITERATE, &pctx); + } + ext2fs_close_inode_scan(scan); + e2fsck_use_inode_shortcuts(ctx, 0); +} + +static int process_pass1b_block(ext2_filsys fs FSCK_ATTR((unused)), + blk_t *block_nr, + e2_blkcnt_t blockcnt FSCK_ATTR((unused)), + blk_t ref_blk FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct process_block_struct_1b *p; + e2fsck_t ctx; + + if (HOLE_BLKADDR(*block_nr)) + return 0; + p = (struct process_block_struct_1b *) priv_data; + ctx = p->ctx; + + if (!ext2fs_test_block_bitmap(ctx->block_dup_map, *block_nr)) + return 0; + + /* OK, this is a duplicate block */ + if (p->ino != EXT2_BAD_INO) { + p->pctx->blk = *block_nr; + fix_problem(ctx, PR_1B_DUP_BLOCK, p->pctx); + } + p->dup_blocks++; + ext2fs_mark_inode_bitmap(inode_dup_map, p->ino); + + add_dupe(ctx, p->ino, *block_nr, p->inode); + + return 0; +} + +/* + * Pass 1c: Scan directories for inodes with duplicate blocks. This + * is used so that we can print pathnames when prompting the user for + * what to do. + */ +struct search_dir_struct { + int count; + ext2_ino_t first_inode; + ext2_ino_t max_inode; +}; + +static int search_dirent_proc(ext2_ino_t dir, int entry, + struct ext2_dir_entry *dirent, + int offset FSCK_ATTR((unused)), + int blocksize FSCK_ATTR((unused)), + char *buf FSCK_ATTR((unused)), + void *priv_data) +{ + struct search_dir_struct *sd; + struct dup_inode *p; + dnode_t *n; + + sd = (struct search_dir_struct *) priv_data; + + if (dirent->inode > sd->max_inode) + /* Should abort this inode, but not everything */ + return 0; + + if ((dirent->inode < sd->first_inode) || (entry < DIRENT_OTHER_FILE) || + !ext2fs_test_inode_bitmap(inode_dup_map, dirent->inode)) + return 0; + + n = dict_lookup(&ino_dict, INT_TO_VOIDPTR(dirent->inode)); + if (!n) + return 0; + p = (struct dup_inode *) dnode_get(n); + p->dir = dir; + sd->count--; + + return sd->count ? 0 : DIRENT_ABORT; +} + + +static void pass1c(e2fsck_t ctx, char *block_buf) +{ + ext2_filsys fs = ctx->fs; + struct search_dir_struct sd; + struct problem_context pctx; + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_1C_PASS_HEADER, &pctx); + + /* + * Search through all directories to translate inodes to names + * (by searching for the containing directory for that inode.) + */ + sd.count = dup_inode_count; + sd.first_inode = EXT2_FIRST_INODE(fs->super); + sd.max_inode = fs->super->s_inodes_count; + ext2fs_dblist_dir_iterate(fs->dblist, 0, block_buf, + search_dirent_proc, &sd); +} + +static void pass1d(e2fsck_t ctx, char *block_buf) +{ + ext2_filsys fs = ctx->fs; + struct dup_inode *p, *t; + struct dup_block *q; + ext2_ino_t *shared, ino; + int shared_len; + int i; + int file_ok; + int meta_data = 0; + struct problem_context pctx; + dnode_t *n, *m; + struct block_el *s; + struct inode_el *r; + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_1D_PASS_HEADER, &pctx); + e2fsck_read_bitmaps(ctx); + + pctx.num = dup_inode_count; /* dict_count(&ino_dict); */ + fix_problem(ctx, PR_1D_NUM_DUP_INODES, &pctx); + shared = (ext2_ino_t *) e2fsck_allocate_memory(ctx, + sizeof(ext2_ino_t) * dict_count(&ino_dict), + "Shared inode list"); + for (n = dict_first(&ino_dict); n; n = dict_next(&ino_dict, n)) { + p = (struct dup_inode *) dnode_get(n); + shared_len = 0; + file_ok = 1; + ino = (ext2_ino_t)VOIDPTR_TO_INT(dnode_getkey(n)); + if (ino == EXT2_BAD_INO || ino == EXT2_RESIZE_INO) + continue; + + /* + * Find all of the inodes which share blocks with this + * one. First we find all of the duplicate blocks + * belonging to this inode, and then search each block + * get the list of inodes, and merge them together. + */ + for (s = p->block_list; s; s = s->next) { + m = dict_lookup(&blk_dict, INT_TO_VOIDPTR(s->block)); + if (!m) + continue; /* Should never happen... */ + q = (struct dup_block *) dnode_get(m); + if (q->num_bad > 1) + file_ok = 0; + if (check_if_fs_block(ctx, s->block)) { + file_ok = 0; + meta_data = 1; + } + + /* + * Add all inodes used by this block to the + * shared[] --- which is a unique list, so + * if an inode is already in shared[], don't + * add it again. + */ + for (r = q->inode_list; r; r = r->next) { + if (r->inode == ino) + continue; + for (i = 0; i < shared_len; i++) + if (shared[i] == r->inode) + break; + if (i == shared_len) { + shared[shared_len++] = r->inode; + } + } + } + + /* + * Report the inode that we are working on + */ + pctx.inode = &p->inode; + pctx.ino = ino; + pctx.dir = p->dir; + pctx.blkcount = p->num_dupblocks; + pctx.num = meta_data ? shared_len+1 : shared_len; + fix_problem(ctx, PR_1D_DUP_FILE, &pctx); + pctx.blkcount = 0; + pctx.num = 0; + + if (meta_data) + fix_problem(ctx, PR_1D_SHARE_METADATA, &pctx); + + for (i = 0; i < shared_len; i++) { + m = dict_lookup(&ino_dict, INT_TO_VOIDPTR(shared[i])); + if (!m) + continue; /* should never happen */ + t = (struct dup_inode *) dnode_get(m); + /* + * Report the inode that we are sharing with + */ + pctx.inode = &t->inode; + pctx.ino = shared[i]; + pctx.dir = t->dir; + fix_problem(ctx, PR_1D_DUP_FILE_LIST, &pctx); + } + if (file_ok) { + fix_problem(ctx, PR_1D_DUP_BLOCKS_DEALT, &pctx); + continue; + } + if (fix_problem(ctx, PR_1D_CLONE_QUESTION, &pctx)) { + pctx.errcode = clone_file(ctx, ino, p, block_buf); + if (pctx.errcode) + fix_problem(ctx, PR_1D_CLONE_ERROR, &pctx); + else + continue; + } + if (fix_problem(ctx, PR_1D_DELETE_QUESTION, &pctx)) + delete_file(ctx, ino, p, block_buf); + else + ext2fs_unmark_valid(fs); + } + ext2fs_free_mem(&shared); +} + +/* + * Drop the refcount on the dup_block structure, and clear the entry + * in the block_dup_map if appropriate. + */ +static void decrement_badcount(e2fsck_t ctx, blk_t block, struct dup_block *p) +{ + p->num_bad--; + if (p->num_bad <= 0 || + (p->num_bad == 1 && !check_if_fs_block(ctx, block))) + ext2fs_unmark_block_bitmap(ctx->block_dup_map, block); +} + +static int delete_file_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt FSCK_ATTR((unused)), + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct process_block_struct_1b *pb; + struct dup_block *p; + dnode_t *n; + e2fsck_t ctx; + + pb = (struct process_block_struct_1b *) priv_data; + ctx = pb->ctx; + + if (HOLE_BLKADDR(*block_nr)) + return 0; + + if (ext2fs_test_block_bitmap(ctx->block_dup_map, *block_nr)) { + n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(*block_nr)); + if (n) { + p = (struct dup_block *) dnode_get(n); + decrement_badcount(ctx, *block_nr, p); + } else + bb_error_msg(_("internal error; can't find dup_blk for %d"), + *block_nr); + } else { + ext2fs_unmark_block_bitmap(ctx->block_found_map, *block_nr); + ext2fs_block_alloc_stats(fs, *block_nr, -1); + } + + return 0; +} + +static void delete_file(e2fsck_t ctx, ext2_ino_t ino, + struct dup_inode *dp, char* block_buf) +{ + ext2_filsys fs = ctx->fs; + struct process_block_struct_1b pb; + struct ext2_inode inode; + struct problem_context pctx; + unsigned int count; + + clear_problem_context(&pctx); + pctx.ino = pb.ino = ino; + pb.dup_blocks = dp->num_dupblocks; + pb.ctx = ctx; + pctx.str = "delete_file"; + + e2fsck_read_inode(ctx, ino, &inode, "delete_file"); + if (ext2fs_inode_has_valid_blocks(&inode)) + pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, block_buf, + delete_file_block, &pb); + if (pctx.errcode) + fix_problem(ctx, PR_1B_BLOCK_ITERATE, &pctx); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); + if (ctx->inode_bad_map) + ext2fs_unmark_inode_bitmap(ctx->inode_bad_map, ino); + ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode)); + + /* Inode may have changed by block_iterate, so reread it */ + e2fsck_read_inode(ctx, ino, &inode, "delete_file"); + inode.i_links_count = 0; + inode.i_dtime = time(0); + if (inode.i_file_acl && + (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR)) { + count = 1; + pctx.errcode = ext2fs_adjust_ea_refcount(fs, inode.i_file_acl, + block_buf, -1, &count); + if (pctx.errcode == EXT2_ET_BAD_EA_BLOCK_NUM) { + pctx.errcode = 0; + count = 1; + } + if (pctx.errcode) { + pctx.blk = inode.i_file_acl; + fix_problem(ctx, PR_1B_ADJ_EA_REFCOUNT, &pctx); + } + /* + * If the count is zero, then arrange to have the + * block deleted. If the block is in the block_dup_map, + * also call delete_file_block since it will take care + * of keeping the accounting straight. + */ + if ((count == 0) || + ext2fs_test_block_bitmap(ctx->block_dup_map, + inode.i_file_acl)) + delete_file_block(fs, &inode.i_file_acl, + BLOCK_COUNT_EXTATTR, 0, 0, &pb); + } + e2fsck_write_inode(ctx, ino, &inode, "delete_file"); +} + +struct clone_struct { + errcode_t errcode; + ext2_ino_t dir; + char *buf; + e2fsck_t ctx; +}; + +static int clone_file_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct dup_block *p; + blk_t new_block; + errcode_t retval; + struct clone_struct *cs = (struct clone_struct *) priv_data; + dnode_t *n; + e2fsck_t ctx; + + ctx = cs->ctx; + + if (HOLE_BLKADDR(*block_nr)) + return 0; + + if (ext2fs_test_block_bitmap(ctx->block_dup_map, *block_nr)) { + n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(*block_nr)); + if (n) { + p = (struct dup_block *) dnode_get(n); + retval = ext2fs_new_block(fs, 0, ctx->block_found_map, + &new_block); + if (retval) { + cs->errcode = retval; + return BLOCK_ABORT; + } + if (cs->dir && (blockcnt >= 0)) { + retval = ext2fs_set_dir_block(fs->dblist, + cs->dir, new_block, blockcnt); + if (retval) { + cs->errcode = retval; + return BLOCK_ABORT; + } + } + + retval = io_channel_read_blk(fs->io, *block_nr, 1, + cs->buf); + if (retval) { + cs->errcode = retval; + return BLOCK_ABORT; + } + retval = io_channel_write_blk(fs->io, new_block, 1, + cs->buf); + if (retval) { + cs->errcode = retval; + return BLOCK_ABORT; + } + decrement_badcount(ctx, *block_nr, p); + *block_nr = new_block; + ext2fs_mark_block_bitmap(ctx->block_found_map, + new_block); + ext2fs_mark_block_bitmap(fs->block_map, new_block); + return BLOCK_CHANGED; + } else + bb_error_msg(_("internal error; can't find dup_blk for %d"), + *block_nr); + } + return 0; +} + +static int clone_file(e2fsck_t ctx, ext2_ino_t ino, + struct dup_inode *dp, char* block_buf) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + struct clone_struct cs; + struct problem_context pctx; + blk_t blk; + dnode_t *n; + struct inode_el *ino_el; + struct dup_block *db; + struct dup_inode *di; + + clear_problem_context(&pctx); + cs.errcode = 0; + cs.dir = 0; + cs.ctx = ctx; + retval = ext2fs_get_mem(fs->blocksize, &cs.buf); + if (retval) + return retval; + + if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, ino)) + cs.dir = ino; + + pctx.ino = ino; + pctx.str = "clone_file"; + if (ext2fs_inode_has_valid_blocks(&dp->inode)) + pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, block_buf, + clone_file_block, &cs); + ext2fs_mark_bb_dirty(fs); + if (pctx.errcode) { + fix_problem(ctx, PR_1B_BLOCK_ITERATE, &pctx); + retval = pctx.errcode; + goto errout; + } + if (cs.errcode) { + bb_error_msg(_("returned from clone_file_block")); + retval = cs.errcode; + goto errout; + } + /* The inode may have changed on disk, so we have to re-read it */ + e2fsck_read_inode(ctx, ino, &dp->inode, "clone file EA"); + blk = dp->inode.i_file_acl; + if (blk && (clone_file_block(fs, &dp->inode.i_file_acl, + BLOCK_COUNT_EXTATTR, 0, 0, &cs) == + BLOCK_CHANGED)) { + e2fsck_write_inode(ctx, ino, &dp->inode, "clone file EA"); + /* + * If we cloned the EA block, find all other inodes + * which refered to that EA block, and modify + * them to point to the new EA block. + */ + n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(blk)); + db = (struct dup_block *) dnode_get(n); + for (ino_el = db->inode_list; ino_el; ino_el = ino_el->next) { + if (ino_el->inode == ino) + continue; + n = dict_lookup(&ino_dict, INT_TO_VOIDPTR(ino_el->inode)); + di = (struct dup_inode *) dnode_get(n); + if (di->inode.i_file_acl == blk) { + di->inode.i_file_acl = dp->inode.i_file_acl; + e2fsck_write_inode(ctx, ino_el->inode, + &di->inode, "clone file EA"); + decrement_badcount(ctx, blk, db); + } + } + } + retval = 0; +errout: + ext2fs_free_mem(&cs.buf); + return retval; +} + +/* + * This routine returns 1 if a block overlaps with one of the superblocks, + * group descriptors, inode bitmaps, or block bitmaps. + */ +static int check_if_fs_block(e2fsck_t ctx, blk_t test_block) +{ + ext2_filsys fs = ctx->fs; + blk_t block; + dgrp_t i; + + block = fs->super->s_first_data_block; + for (i = 0; i < fs->group_desc_count; i++) { + + /* Check superblocks/block group descriptros */ + if (ext2fs_bg_has_super(fs, i)) { + if (test_block >= block && + (test_block <= block + fs->desc_blocks)) + return 1; + } + + /* Check the inode table */ + if ((fs->group_desc[i].bg_inode_table) && + (test_block >= fs->group_desc[i].bg_inode_table) && + (test_block < (fs->group_desc[i].bg_inode_table + + fs->inode_blocks_per_group))) + return 1; + + /* Check the bitmap blocks */ + if ((test_block == fs->group_desc[i].bg_block_bitmap) || + (test_block == fs->group_desc[i].bg_inode_bitmap)) + return 1; + + block += fs->super->s_blocks_per_group; + } + return 0; +} +/* + * pass2.c --- check directory structure + * + * Pass 2 of e2fsck iterates through all active directory inodes, and + * applies to following tests to each directory entry in the directory + * blocks in the inodes: + * + * - The length of the directory entry (rec_len) should be at + * least 8 bytes, and no more than the remaining space + * left in the directory block. + * - The length of the name in the directory entry (name_len) + * should be less than (rec_len - 8). + * - The inode number in the directory entry should be within + * legal bounds. + * - The inode number should refer to a in-use inode. + * - The first entry should be '.', and its inode should be + * the inode of the directory. + * - The second entry should be '..'. + * + * To minimize disk seek time, the directory blocks are processed in + * sorted order of block numbers. + * + * Pass 2 also collects the following information: + * - The inode numbers of the subdirectories for each directory. + * + * Pass 2 relies on the following information from previous passes: + * - The directory information collected in pass 1. + * - The inode_used_map bitmap + * - The inode_bad_map bitmap + * - The inode_dir_map bitmap + * + * Pass 2 frees the following data structures + * - The inode_bad_map bitmap + * - The inode_reg_map bitmap + */ + +/* + * Keeps track of how many times an inode is referenced. + */ +static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf); +static int check_dir_block(ext2_filsys fs, + struct ext2_db_entry *dir_blocks_info, + void *priv_data); +static int allocate_dir_block(e2fsck_t ctx, struct ext2_db_entry *dir_blocks_info, + struct problem_context *pctx); +static int update_dir_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block, + int ref_offset, + void *priv_data); +static void clear_htree(e2fsck_t ctx, ext2_ino_t ino); +static int htree_depth(struct dx_dir_info *dx_dir, + struct dx_dirblock_info *dx_db); +static int special_dir_block_cmp(const void *a, const void *b); + +struct check_dir_struct { + char *buf; + struct problem_context pctx; + int count, max; + e2fsck_t ctx; +}; + +static void e2fsck_pass2(e2fsck_t ctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + struct problem_context pctx; + ext2_filsys fs = ctx->fs; + char *buf; + struct dir_info *dir; + struct check_dir_struct cd; + struct dx_dir_info *dx_dir; + struct dx_dirblock_info *dx_db, *dx_parent; + int b; + int i, depth; + problem_t code; + int bad_dir; + + clear_problem_context(&cd.pctx); + + /* Pass 2 */ + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_2_PASS_HEADER, &cd.pctx); + + cd.pctx.errcode = ext2fs_create_icount2(fs, EXT2_ICOUNT_OPT_INCREMENT, + 0, ctx->inode_link_info, + &ctx->inode_count); + if (cd.pctx.errcode) { + fix_problem(ctx, PR_2_ALLOCATE_ICOUNT, &cd.pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + buf = (char *) e2fsck_allocate_memory(ctx, 2*fs->blocksize, + "directory scan buffer"); + + /* + * Set up the parent pointer for the root directory, if + * present. (If the root directory is not present, we will + * create it in pass 3.) + */ + dir = e2fsck_get_dir_info(ctx, EXT2_ROOT_INO); + if (dir) + dir->parent = EXT2_ROOT_INO; + + cd.buf = buf; + cd.ctx = ctx; + cd.count = 1; + cd.max = ext2fs_dblist_count(fs->dblist); + + if (ctx->progress) + (void) (ctx->progress)(ctx, 2, 0, cd.max); + + if (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) + ext2fs_dblist_sort(fs->dblist, special_dir_block_cmp); + + cd.pctx.errcode = ext2fs_dblist_iterate(fs->dblist, check_dir_block, + &cd); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + if (cd.pctx.errcode) { + fix_problem(ctx, PR_2_DBLIST_ITERATE, &cd.pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + +#ifdef ENABLE_HTREE + for (i=0; (dx_dir = e2fsck_dx_dir_info_iter(ctx, &i)) != 0;) { + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + if (dx_dir->numblocks == 0) + continue; + clear_problem_context(&pctx); + bad_dir = 0; + pctx.dir = dx_dir->ino; + dx_db = dx_dir->dx_block; + if (dx_db->flags & DX_FLAG_REFERENCED) + dx_db->flags |= DX_FLAG_DUP_REF; + else + dx_db->flags |= DX_FLAG_REFERENCED; + /* + * Find all of the first and last leaf blocks, and + * update their parent's min and max hash values + */ + for (b=0, dx_db = dx_dir->dx_block; + b < dx_dir->numblocks; + b++, dx_db++) { + if ((dx_db->type != DX_DIRBLOCK_LEAF) || + !(dx_db->flags & (DX_FLAG_FIRST | DX_FLAG_LAST))) + continue; + dx_parent = &dx_dir->dx_block[dx_db->parent]; + /* + * XXX Make sure dx_parent->min_hash > dx_db->min_hash + */ + if (dx_db->flags & DX_FLAG_FIRST) + dx_parent->min_hash = dx_db->min_hash; + /* + * XXX Make sure dx_parent->max_hash < dx_db->max_hash + */ + if (dx_db->flags & DX_FLAG_LAST) + dx_parent->max_hash = dx_db->max_hash; + } + + for (b=0, dx_db = dx_dir->dx_block; + b < dx_dir->numblocks; + b++, dx_db++) { + pctx.blkcount = b; + pctx.group = dx_db->parent; + code = 0; + if (!(dx_db->flags & DX_FLAG_FIRST) && + (dx_db->min_hash < dx_db->node_min_hash)) { + pctx.blk = dx_db->min_hash; + pctx.blk2 = dx_db->node_min_hash; + code = PR_2_HTREE_MIN_HASH; + fix_problem(ctx, code, &pctx); + bad_dir++; + } + if (dx_db->type == DX_DIRBLOCK_LEAF) { + depth = htree_depth(dx_dir, dx_db); + if (depth != dx_dir->depth) { + code = PR_2_HTREE_BAD_DEPTH; + fix_problem(ctx, code, &pctx); + bad_dir++; + } + } + /* + * This test doesn't apply for the root block + * at block #0 + */ + if (b && + (dx_db->max_hash > dx_db->node_max_hash)) { + pctx.blk = dx_db->max_hash; + pctx.blk2 = dx_db->node_max_hash; + code = PR_2_HTREE_MAX_HASH; + fix_problem(ctx, code, &pctx); + bad_dir++; + } + if (!(dx_db->flags & DX_FLAG_REFERENCED)) { + code = PR_2_HTREE_NOTREF; + fix_problem(ctx, code, &pctx); + bad_dir++; + } else if (dx_db->flags & DX_FLAG_DUP_REF) { + code = PR_2_HTREE_DUPREF; + fix_problem(ctx, code, &pctx); + bad_dir++; + } + if (code == 0) + continue; + } + if (bad_dir && fix_problem(ctx, PR_2_HTREE_CLEAR, &pctx)) { + clear_htree(ctx, dx_dir->ino); + dx_dir->numblocks = 0; + } + } +#endif + ext2fs_free_mem(&buf); + ext2fs_free_dblist(fs->dblist); + + ext2fs_free_inode_bitmap(ctx->inode_bad_map); + ctx->inode_bad_map = 0; + ext2fs_free_inode_bitmap(ctx->inode_reg_map); + ctx->inode_reg_map = 0; + + clear_problem_context(&pctx); + if (ctx->large_files) { + if (!(sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_LARGE_FILE) && + fix_problem(ctx, PR_2_FEATURE_LARGE_FILES, &pctx)) { + sb->s_feature_ro_compat |= + EXT2_FEATURE_RO_COMPAT_LARGE_FILE; + ext2fs_mark_super_dirty(fs); + } + if (sb->s_rev_level == EXT2_GOOD_OLD_REV && + fix_problem(ctx, PR_1_FS_REV_LEVEL, &pctx)) { + ext2fs_update_dynamic_rev(fs); + ext2fs_mark_super_dirty(fs); + } + } else if (!ctx->large_files && + (sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_LARGE_FILE)) { + if (fs->flags & EXT2_FLAG_RW) { + sb->s_feature_ro_compat &= + ~EXT2_FEATURE_RO_COMPAT_LARGE_FILE; + ext2fs_mark_super_dirty(fs); + } + } + +} + +#define MAX_DEPTH 32000 +static int htree_depth(struct dx_dir_info *dx_dir, + struct dx_dirblock_info *dx_db) +{ + int depth = 0; + + while (dx_db->type != DX_DIRBLOCK_ROOT && depth < MAX_DEPTH) { + dx_db = &dx_dir->dx_block[dx_db->parent]; + depth++; + } + return depth; +} + +static int dict_de_cmp(const void *a, const void *b) +{ + const struct ext2_dir_entry *de_a, *de_b; + int a_len, b_len; + + de_a = (const struct ext2_dir_entry *) a; + a_len = de_a->name_len & 0xFF; + de_b = (const struct ext2_dir_entry *) b; + b_len = de_b->name_len & 0xFF; + + if (a_len != b_len) + return (a_len - b_len); + + return strncmp(de_a->name, de_b->name, a_len); +} + +/* + * This is special sort function that makes sure that directory blocks + * with a dirblock of zero are sorted to the beginning of the list. + * This guarantees that the root node of the htree directories are + * processed first, so we know what hash version to use. + */ +static int special_dir_block_cmp(const void *a, const void *b) +{ + const struct ext2_db_entry *db_a = + (const struct ext2_db_entry *) a; + const struct ext2_db_entry *db_b = + (const struct ext2_db_entry *) b; + + if (db_a->blockcnt && !db_b->blockcnt) + return 1; + + if (!db_a->blockcnt && db_b->blockcnt) + return -1; + + if (db_a->blk != db_b->blk) + return (int) (db_a->blk - db_b->blk); + + if (db_a->ino != db_b->ino) + return (int) (db_a->ino - db_b->ino); + + return (int) (db_a->blockcnt - db_b->blockcnt); +} + + +/* + * Make sure the first entry in the directory is '.', and that the + * directory entry is sane. + */ +static int check_dot(e2fsck_t ctx, + struct ext2_dir_entry *dirent, + ext2_ino_t ino, struct problem_context *pctx) +{ + struct ext2_dir_entry *nextdir; + int status = 0; + int created = 0; + int new_len; + int problem = 0; + + if (!dirent->inode) + problem = PR_2_MISSING_DOT; + else if (((dirent->name_len & 0xFF) != 1) || + (dirent->name[0] != '.')) + problem = PR_2_1ST_NOT_DOT; + else if (dirent->name[1] != '\0') + problem = PR_2_DOT_NULL_TERM; + + if (problem) { + if (fix_problem(ctx, problem, pctx)) { + if (dirent->rec_len < 12) + dirent->rec_len = 12; + dirent->inode = ino; + dirent->name_len = 1; + dirent->name[0] = '.'; + dirent->name[1] = '\0'; + status = 1; + created = 1; + } + } + if (dirent->inode != ino) { + if (fix_problem(ctx, PR_2_BAD_INODE_DOT, pctx)) { + dirent->inode = ino; + status = 1; + } + } + if (dirent->rec_len > 12) { + new_len = dirent->rec_len - 12; + if (new_len > 12) { + if (created || + fix_problem(ctx, PR_2_SPLIT_DOT, pctx)) { + nextdir = (struct ext2_dir_entry *) + ((char *) dirent + 12); + dirent->rec_len = 12; + nextdir->rec_len = new_len; + nextdir->inode = 0; + nextdir->name_len = 0; + status = 1; + } + } + } + return status; +} + +/* + * Make sure the second entry in the directory is '..', and that the + * directory entry is sane. We do not check the inode number of '..' + * here; this gets done in pass 3. + */ +static int check_dotdot(e2fsck_t ctx, + struct ext2_dir_entry *dirent, + struct dir_info *dir, struct problem_context *pctx) +{ + int problem = 0; + + if (!dirent->inode) + problem = PR_2_MISSING_DOT_DOT; + else if (((dirent->name_len & 0xFF) != 2) || + (dirent->name[0] != '.') || + (dirent->name[1] != '.')) + problem = PR_2_2ND_NOT_DOT_DOT; + else if (dirent->name[2] != '\0') + problem = PR_2_DOT_DOT_NULL_TERM; + + if (problem) { + if (fix_problem(ctx, problem, pctx)) { + if (dirent->rec_len < 12) + dirent->rec_len = 12; + /* + * Note: we don't have the parent inode just + * yet, so we will fill it in with the root + * inode. This will get fixed in pass 3. + */ + dirent->inode = EXT2_ROOT_INO; + dirent->name_len = 2; + dirent->name[0] = '.'; + dirent->name[1] = '.'; + dirent->name[2] = '\0'; + return 1; + } + return 0; + } + dir->dotdot = dirent->inode; + return 0; +} + +/* + * Check to make sure a directory entry doesn't contain any illegal + * characters. + */ +static int check_name(e2fsck_t ctx, + struct ext2_dir_entry *dirent, + struct problem_context *pctx) +{ + int i; + int fixup = -1; + int ret = 0; + + for ( i = 0; i < (dirent->name_len & 0xFF); i++) { + if (dirent->name[i] == '/' || dirent->name[i] == '\0') { + if (fixup < 0) { + fixup = fix_problem(ctx, PR_2_BAD_NAME, pctx); + } + if (fixup) { + dirent->name[i] = '.'; + ret = 1; + } + } + } + return ret; +} + +/* + * Check the directory filetype (if present) + */ + +/* + * Given a mode, return the ext2 file type + */ +static int ext2_file_type(unsigned int mode) +{ + if (LINUX_S_ISREG(mode)) + return EXT2_FT_REG_FILE; + + if (LINUX_S_ISDIR(mode)) + return EXT2_FT_DIR; + + if (LINUX_S_ISCHR(mode)) + return EXT2_FT_CHRDEV; + + if (LINUX_S_ISBLK(mode)) + return EXT2_FT_BLKDEV; + + if (LINUX_S_ISLNK(mode)) + return EXT2_FT_SYMLINK; + + if (LINUX_S_ISFIFO(mode)) + return EXT2_FT_FIFO; + + if (LINUX_S_ISSOCK(mode)) + return EXT2_FT_SOCK; + + return 0; +} + +static int check_filetype(e2fsck_t ctx, + struct ext2_dir_entry *dirent, + struct problem_context *pctx) +{ + int filetype = dirent->name_len >> 8; + int should_be = EXT2_FT_UNKNOWN; + struct ext2_inode inode; + + if (!(ctx->fs->super->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_FILETYPE)) { + if (filetype == 0 || + !fix_problem(ctx, PR_2_CLEAR_FILETYPE, pctx)) + return 0; + dirent->name_len = dirent->name_len & 0xFF; + return 1; + } + + if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, dirent->inode)) { + should_be = EXT2_FT_DIR; + } else if (ext2fs_test_inode_bitmap(ctx->inode_reg_map, + dirent->inode)) { + should_be = EXT2_FT_REG_FILE; + } else if (ctx->inode_bad_map && + ext2fs_test_inode_bitmap(ctx->inode_bad_map, + dirent->inode)) + should_be = 0; + else { + e2fsck_read_inode(ctx, dirent->inode, &inode, + "check_filetype"); + should_be = ext2_file_type(inode.i_mode); + } + if (filetype == should_be) + return 0; + pctx->num = should_be; + + if (fix_problem(ctx, filetype ? PR_2_BAD_FILETYPE : PR_2_SET_FILETYPE, + pctx) == 0) + return 0; + + dirent->name_len = (dirent->name_len & 0xFF) | should_be << 8; + return 1; +} + +#ifdef ENABLE_HTREE +static void parse_int_node(ext2_filsys fs, + struct ext2_db_entry *db, + struct check_dir_struct *cd, + struct dx_dir_info *dx_dir, + char *block_buf) +{ + struct ext2_dx_root_info *root; + struct ext2_dx_entry *ent; + struct ext2_dx_countlimit *limit; + struct dx_dirblock_info *dx_db; + int i, expect_limit, count; + blk_t blk; + ext2_dirhash_t min_hash = 0xffffffff; + ext2_dirhash_t max_hash = 0; + ext2_dirhash_t hash = 0, prev_hash; + + if (db->blockcnt == 0) { + root = (struct ext2_dx_root_info *) (block_buf + 24); + ent = (struct ext2_dx_entry *) (block_buf + 24 + root->info_length); + } else { + ent = (struct ext2_dx_entry *) (block_buf+8); + } + limit = (struct ext2_dx_countlimit *) ent; + + count = ext2fs_le16_to_cpu(limit->count); + expect_limit = (fs->blocksize - ((char *) ent - block_buf)) / + sizeof(struct ext2_dx_entry); + if (ext2fs_le16_to_cpu(limit->limit) != expect_limit) { + cd->pctx.num = ext2fs_le16_to_cpu(limit->limit); + if (fix_problem(cd->ctx, PR_2_HTREE_BAD_LIMIT, &cd->pctx)) + goto clear_and_exit; + } + if (count > expect_limit) { + cd->pctx.num = count; + if (fix_problem(cd->ctx, PR_2_HTREE_BAD_COUNT, &cd->pctx)) + goto clear_and_exit; + count = expect_limit; + } + + for (i=0; i < count; i++) { + prev_hash = hash; + hash = i ? (ext2fs_le32_to_cpu(ent[i].hash) & ~1) : 0; + blk = ext2fs_le32_to_cpu(ent[i].block) & 0x0ffffff; + /* Check to make sure the block is valid */ + if (blk > (blk_t) dx_dir->numblocks) { + cd->pctx.blk = blk; + if (fix_problem(cd->ctx, PR_2_HTREE_BADBLK, + &cd->pctx)) + goto clear_and_exit; + } + if (hash < prev_hash && + fix_problem(cd->ctx, PR_2_HTREE_HASH_ORDER, &cd->pctx)) + goto clear_and_exit; + dx_db = &dx_dir->dx_block[blk]; + if (dx_db->flags & DX_FLAG_REFERENCED) { + dx_db->flags |= DX_FLAG_DUP_REF; + } else { + dx_db->flags |= DX_FLAG_REFERENCED; + dx_db->parent = db->blockcnt; + } + if (hash < min_hash) + min_hash = hash; + if (hash > max_hash) + max_hash = hash; + dx_db->node_min_hash = hash; + if ((i+1) < count) + dx_db->node_max_hash = + ext2fs_le32_to_cpu(ent[i+1].hash) & ~1; + else { + dx_db->node_max_hash = 0xfffffffe; + dx_db->flags |= DX_FLAG_LAST; + } + if (i == 0) + dx_db->flags |= DX_FLAG_FIRST; + } + dx_db = &dx_dir->dx_block[db->blockcnt]; + dx_db->min_hash = min_hash; + dx_db->max_hash = max_hash; + return; + +clear_and_exit: + clear_htree(cd->ctx, cd->pctx.ino); + dx_dir->numblocks = 0; +} +#endif /* ENABLE_HTREE */ + +/* + * Given a busted directory, try to salvage it somehow. + * + */ +static void salvage_directory(ext2_filsys fs, + struct ext2_dir_entry *dirent, + struct ext2_dir_entry *prev, + unsigned int *offset) +{ + char *cp = (char *) dirent; + int left = fs->blocksize - *offset - dirent->rec_len; + int name_len = dirent->name_len & 0xFF; + + /* + * Special case of directory entry of size 8: copy what's left + * of the directory block up to cover up the invalid hole. + */ + if ((left >= 12) && (dirent->rec_len == 8)) { + memmove(cp, cp+8, left); + memset(cp + left, 0, 8); + return; + } + /* + * If the directory entry overruns the end of the directory + * block, and the name is small enough to fit, then adjust the + * record length. + */ + if ((left < 0) && + (name_len + 8 <= dirent->rec_len + left) && + dirent->inode <= fs->super->s_inodes_count && + strnlen(dirent->name, name_len) == name_len) { + dirent->rec_len += left; + return; + } + /* + * If the directory entry is a multiple of four, so it is + * valid, let the previous directory entry absorb the invalid + * one. + */ + if (prev && dirent->rec_len && (dirent->rec_len % 4) == 0) { + prev->rec_len += dirent->rec_len; + *offset += dirent->rec_len; + return; + } + /* + * Default salvage method --- kill all of the directory + * entries for the rest of the block. We will either try to + * absorb it into the previous directory entry, or create a + * new empty directory entry the rest of the directory block. + */ + if (prev) { + prev->rec_len += fs->blocksize - *offset; + *offset = fs->blocksize; + } else { + dirent->rec_len = fs->blocksize - *offset; + dirent->name_len = 0; + dirent->inode = 0; + } +} + +static int check_dir_block(ext2_filsys fs, + struct ext2_db_entry *db, + void *priv_data) +{ + struct dir_info *subdir, *dir; + struct dx_dir_info *dx_dir; +#ifdef ENABLE_HTREE + struct dx_dirblock_info *dx_db = 0; +#endif /* ENABLE_HTREE */ + struct ext2_dir_entry *dirent, *prev; + ext2_dirhash_t hash; + unsigned int offset = 0; + int dir_modified = 0; + int dot_state; + blk_t block_nr = db->blk; + ext2_ino_t ino = db->ino; + __u16 links; + struct check_dir_struct *cd; + char *buf; + e2fsck_t ctx; + int problem; + struct ext2_dx_root_info *root; + struct ext2_dx_countlimit *limit; + static dict_t de_dict; + struct problem_context pctx; + int dups_found = 0; + + cd = (struct check_dir_struct *) priv_data; + buf = cd->buf; + ctx = cd->ctx; + + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return DIRENT_ABORT; + + if (ctx->progress && (ctx->progress)(ctx, 2, cd->count++, cd->max)) + return DIRENT_ABORT; + + /* + * Make sure the inode is still in use (could have been + * deleted in the duplicate/bad blocks pass. + */ + if (!(ext2fs_test_inode_bitmap(ctx->inode_used_map, ino))) + return 0; + + cd->pctx.ino = ino; + cd->pctx.blk = block_nr; + cd->pctx.blkcount = db->blockcnt; + cd->pctx.ino2 = 0; + cd->pctx.dirent = 0; + cd->pctx.num = 0; + + if (db->blk == 0) { + if (allocate_dir_block(ctx, db, &cd->pctx)) + return 0; + block_nr = db->blk; + } + + if (db->blockcnt) + dot_state = 2; + else + dot_state = 0; + + if (ctx->dirs_to_hash && + ext2fs_u32_list_test(ctx->dirs_to_hash, ino)) + dups_found++; + + cd->pctx.errcode = ext2fs_read_dir_block(fs, block_nr, buf); + if (cd->pctx.errcode == EXT2_ET_DIR_CORRUPTED) + cd->pctx.errcode = 0; /* We'll handle this ourselves */ + if (cd->pctx.errcode) { + if (!fix_problem(ctx, PR_2_READ_DIRBLOCK, &cd->pctx)) { + ctx->flags |= E2F_FLAG_ABORT; + return DIRENT_ABORT; + } + memset(buf, 0, fs->blocksize); + } +#ifdef ENABLE_HTREE + dx_dir = e2fsck_get_dx_dir_info(ctx, ino); + if (dx_dir && dx_dir->numblocks) { + if (db->blockcnt >= dx_dir->numblocks) { + printf("XXX should never happen!!!\n"); + abort(); + } + dx_db = &dx_dir->dx_block[db->blockcnt]; + dx_db->type = DX_DIRBLOCK_LEAF; + dx_db->phys = block_nr; + dx_db->min_hash = ~0; + dx_db->max_hash = 0; + + dirent = (struct ext2_dir_entry *) buf; + limit = (struct ext2_dx_countlimit *) (buf+8); + if (db->blockcnt == 0) { + root = (struct ext2_dx_root_info *) (buf + 24); + dx_db->type = DX_DIRBLOCK_ROOT; + dx_db->flags |= DX_FLAG_FIRST | DX_FLAG_LAST; + if ((root->reserved_zero || + root->info_length < 8 || + root->indirect_levels > 1) && + fix_problem(ctx, PR_2_HTREE_BAD_ROOT, &cd->pctx)) { + clear_htree(ctx, ino); + dx_dir->numblocks = 0; + dx_db = 0; + } + dx_dir->hashversion = root->hash_version; + dx_dir->depth = root->indirect_levels + 1; + } else if ((dirent->inode == 0) && + (dirent->rec_len == fs->blocksize) && + (dirent->name_len == 0) && + (ext2fs_le16_to_cpu(limit->limit) == + ((fs->blocksize-8) / + sizeof(struct ext2_dx_entry)))) + dx_db->type = DX_DIRBLOCK_NODE; + } +#endif /* ENABLE_HTREE */ + + dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp); + prev = 0; + do { + problem = 0; + dirent = (struct ext2_dir_entry *) (buf + offset); + cd->pctx.dirent = dirent; + cd->pctx.num = offset; + if (((offset + dirent->rec_len) > fs->blocksize) || + (dirent->rec_len < 12) || + ((dirent->rec_len % 4) != 0) || + (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) { + if (fix_problem(ctx, PR_2_DIR_CORRUPTED, &cd->pctx)) { + salvage_directory(fs, dirent, prev, &offset); + dir_modified++; + continue; + } else + goto abort_free_dict; + } + if ((dirent->name_len & 0xFF) > EXT2_NAME_LEN) { + if (fix_problem(ctx, PR_2_FILENAME_LONG, &cd->pctx)) { + dirent->name_len = EXT2_NAME_LEN; + dir_modified++; + } + } + + if (dot_state == 0) { + if (check_dot(ctx, dirent, ino, &cd->pctx)) + dir_modified++; + } else if (dot_state == 1) { + dir = e2fsck_get_dir_info(ctx, ino); + if (!dir) { + fix_problem(ctx, PR_2_NO_DIRINFO, &cd->pctx); + goto abort_free_dict; + } + if (check_dotdot(ctx, dirent, dir, &cd->pctx)) + dir_modified++; + } else if (dirent->inode == ino) { + problem = PR_2_LINK_DOT; + if (fix_problem(ctx, PR_2_LINK_DOT, &cd->pctx)) { + dirent->inode = 0; + dir_modified++; + goto next; + } + } + if (!dirent->inode) + goto next; + + /* + * Make sure the inode listed is a legal one. + */ + if (((dirent->inode != EXT2_ROOT_INO) && + (dirent->inode < EXT2_FIRST_INODE(fs->super))) || + (dirent->inode > fs->super->s_inodes_count)) { + problem = PR_2_BAD_INO; + } else if (!(ext2fs_test_inode_bitmap(ctx->inode_used_map, + dirent->inode))) { + /* + * If the inode is unused, offer to clear it. + */ + problem = PR_2_UNUSED_INODE; + } else if ((dot_state > 1) && + ((dirent->name_len & 0xFF) == 1) && + (dirent->name[0] == '.')) { + /* + * If there's a '.' entry in anything other + * than the first directory entry, it's a + * duplicate entry that should be removed. + */ + problem = PR_2_DUP_DOT; + } else if ((dot_state > 1) && + ((dirent->name_len & 0xFF) == 2) && + (dirent->name[0] == '.') && + (dirent->name[1] == '.')) { + /* + * If there's a '..' entry in anything other + * than the second directory entry, it's a + * duplicate entry that should be removed. + */ + problem = PR_2_DUP_DOT_DOT; + } else if ((dot_state > 1) && + (dirent->inode == EXT2_ROOT_INO)) { + /* + * Don't allow links to the root directory. + * We check this specially to make sure we + * catch this error case even if the root + * directory hasn't been created yet. + */ + problem = PR_2_LINK_ROOT; + } else if ((dot_state > 1) && + (dirent->name_len & 0xFF) == 0) { + /* + * Don't allow zero-length directory names. + */ + problem = PR_2_NULL_NAME; + } + + if (problem) { + if (fix_problem(ctx, problem, &cd->pctx)) { + dirent->inode = 0; + dir_modified++; + goto next; + } else { + ext2fs_unmark_valid(fs); + if (problem == PR_2_BAD_INO) + goto next; + } + } + + /* + * If the inode was marked as having bad fields in + * pass1, process it and offer to fix/clear it. + * (We wait until now so that we can display the + * pathname to the user.) + */ + if (ctx->inode_bad_map && + ext2fs_test_inode_bitmap(ctx->inode_bad_map, + dirent->inode)) { + if (e2fsck_process_bad_inode(ctx, ino, + dirent->inode, + buf + fs->blocksize)) { + dirent->inode = 0; + dir_modified++; + goto next; + } + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return DIRENT_ABORT; + } + + if (check_name(ctx, dirent, &cd->pctx)) + dir_modified++; + + if (check_filetype(ctx, dirent, &cd->pctx)) + dir_modified++; + +#ifdef ENABLE_HTREE + if (dx_db) { + ext2fs_dirhash(dx_dir->hashversion, dirent->name, + (dirent->name_len & 0xFF), + fs->super->s_hash_seed, &hash, 0); + if (hash < dx_db->min_hash) + dx_db->min_hash = hash; + if (hash > dx_db->max_hash) + dx_db->max_hash = hash; + } +#endif + + /* + * If this is a directory, then mark its parent in its + * dir_info structure. If the parent field is already + * filled in, then this directory has more than one + * hard link. We assume the first link is correct, + * and ask the user if he/she wants to clear this one. + */ + if ((dot_state > 1) && + (ext2fs_test_inode_bitmap(ctx->inode_dir_map, + dirent->inode))) { + subdir = e2fsck_get_dir_info(ctx, dirent->inode); + if (!subdir) { + cd->pctx.ino = dirent->inode; + fix_problem(ctx, PR_2_NO_DIRINFO, &cd->pctx); + goto abort_free_dict; + } + if (subdir->parent) { + cd->pctx.ino2 = subdir->parent; + if (fix_problem(ctx, PR_2_LINK_DIR, + &cd->pctx)) { + dirent->inode = 0; + dir_modified++; + goto next; + } + cd->pctx.ino2 = 0; + } else + subdir->parent = ino; + } + + if (dups_found) { + ; + } else if (dict_lookup(&de_dict, dirent)) { + clear_problem_context(&pctx); + pctx.ino = ino; + pctx.dirent = dirent; + fix_problem(ctx, PR_2_REPORT_DUP_DIRENT, &pctx); + if (!ctx->dirs_to_hash) + ext2fs_u32_list_create(&ctx->dirs_to_hash, 50); + if (ctx->dirs_to_hash) + ext2fs_u32_list_add(ctx->dirs_to_hash, ino); + dups_found++; + } else + dict_alloc_insert(&de_dict, dirent, dirent); + + ext2fs_icount_increment(ctx->inode_count, dirent->inode, + &links); + if (links > 1) + ctx->fs_links_count++; + ctx->fs_total_count++; + next: + prev = dirent; + offset += dirent->rec_len; + dot_state++; + } while (offset < fs->blocksize); +#ifdef ENABLE_HTREE + if (dx_db) { + cd->pctx.dir = cd->pctx.ino; + if ((dx_db->type == DX_DIRBLOCK_ROOT) || + (dx_db->type == DX_DIRBLOCK_NODE)) + parse_int_node(fs, db, cd, dx_dir, buf); + } +#endif /* ENABLE_HTREE */ + if (offset != fs->blocksize) { + cd->pctx.num = dirent->rec_len - fs->blocksize + offset; + if (fix_problem(ctx, PR_2_FINAL_RECLEN, &cd->pctx)) { + dirent->rec_len = cd->pctx.num; + dir_modified++; + } + } + if (dir_modified) { + cd->pctx.errcode = ext2fs_write_dir_block(fs, block_nr, buf); + if (cd->pctx.errcode) { + if (!fix_problem(ctx, PR_2_WRITE_DIRBLOCK, + &cd->pctx)) + goto abort_free_dict; + } + ext2fs_mark_changed(fs); + } + dict_free_nodes(&de_dict); + return 0; +abort_free_dict: + dict_free_nodes(&de_dict); + ctx->flags |= E2F_FLAG_ABORT; + return DIRENT_ABORT; +} + +/* + * This function is called to deallocate a block, and is an interator + * functioned called by deallocate inode via ext2fs_iterate_block(). + */ +static int deallocate_inode_block(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt FSCK_ATTR((unused)), + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + e2fsck_t ctx = (e2fsck_t) priv_data; + + if (HOLE_BLKADDR(*block_nr)) + return 0; + if ((*block_nr < fs->super->s_first_data_block) || + (*block_nr >= fs->super->s_blocks_count)) + return 0; + ext2fs_unmark_block_bitmap(ctx->block_found_map, *block_nr); + ext2fs_block_alloc_stats(fs, *block_nr, -1); + return 0; +} + +/* + * This fuction deallocates an inode + */ +static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf) +{ + ext2_filsys fs = ctx->fs; + struct ext2_inode inode; + struct problem_context pctx; + __u32 count; + + ext2fs_icount_store(ctx->inode_link_info, ino, 0); + e2fsck_read_inode(ctx, ino, &inode, "deallocate_inode"); + inode.i_links_count = 0; + inode.i_dtime = time(0); + e2fsck_write_inode(ctx, ino, &inode, "deallocate_inode"); + clear_problem_context(&pctx); + pctx.ino = ino; + + /* + * Fix up the bitmaps... + */ + e2fsck_read_bitmaps(ctx); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); + if (ctx->inode_bad_map) + ext2fs_unmark_inode_bitmap(ctx->inode_bad_map, ino); + ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode)); + + if (inode.i_file_acl && + (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR)) { + pctx.errcode = ext2fs_adjust_ea_refcount(fs, inode.i_file_acl, + block_buf, -1, &count); + if (pctx.errcode == EXT2_ET_BAD_EA_BLOCK_NUM) { + pctx.errcode = 0; + count = 1; + } + if (pctx.errcode) { + pctx.blk = inode.i_file_acl; + fix_problem(ctx, PR_2_ADJ_EA_REFCOUNT, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (count == 0) { + ext2fs_unmark_block_bitmap(ctx->block_found_map, + inode.i_file_acl); + ext2fs_block_alloc_stats(fs, inode.i_file_acl, -1); + } + inode.i_file_acl = 0; + } + + if (!ext2fs_inode_has_valid_blocks(&inode)) + return; + + if (LINUX_S_ISREG(inode.i_mode) && + (inode.i_size_high || inode.i_size & 0x80000000UL)) + ctx->large_files--; + + pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, block_buf, + deallocate_inode_block, ctx); + if (pctx.errcode) { + fix_problem(ctx, PR_2_DEALLOC_INODE, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } +} + +/* + * This fuction clears the htree flag on an inode + */ +static void clear_htree(e2fsck_t ctx, ext2_ino_t ino) +{ + struct ext2_inode inode; + + e2fsck_read_inode(ctx, ino, &inode, "clear_htree"); + inode.i_flags = inode.i_flags & ~EXT2_INDEX_FL; + e2fsck_write_inode(ctx, ino, &inode, "clear_htree"); + if (ctx->dirs_to_hash) + ext2fs_u32_list_add(ctx->dirs_to_hash, ino); +} + + +static int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir, + ext2_ino_t ino, char *buf) +{ + ext2_filsys fs = ctx->fs; + struct ext2_inode inode; + int inode_modified = 0; + int not_fixed = 0; + unsigned char *frag, *fsize; + struct problem_context pctx; + int problem = 0; + + e2fsck_read_inode(ctx, ino, &inode, "process_bad_inode"); + + clear_problem_context(&pctx); + pctx.ino = ino; + pctx.dir = dir; + pctx.inode = &inode; + + if (inode.i_file_acl && + !(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR) && + fix_problem(ctx, PR_2_FILE_ACL_ZERO, &pctx)) { + inode.i_file_acl = 0; +#if BB_BIG_ENDIAN + /* + * This is a special kludge to deal with long symlinks + * on big endian systems. i_blocks had already been + * decremented earlier in pass 1, but since i_file_acl + * hadn't yet been cleared, ext2fs_read_inode() + * assumed that the file was short symlink and would + * not have byte swapped i_block[0]. Hence, we have + * to byte-swap it here. + */ + if (LINUX_S_ISLNK(inode.i_mode) && + (fs->flags & EXT2_FLAG_SWAP_BYTES) && + (inode.i_blocks == fs->blocksize >> 9)) + inode.i_block[0] = ext2fs_swab32(inode.i_block[0]); +#endif + inode_modified++; + } else + not_fixed++; + + if (!LINUX_S_ISDIR(inode.i_mode) && !LINUX_S_ISREG(inode.i_mode) && + !LINUX_S_ISCHR(inode.i_mode) && !LINUX_S_ISBLK(inode.i_mode) && + !LINUX_S_ISLNK(inode.i_mode) && !LINUX_S_ISFIFO(inode.i_mode) && + !(LINUX_S_ISSOCK(inode.i_mode))) + problem = PR_2_BAD_MODE; + else if (LINUX_S_ISCHR(inode.i_mode) + && !e2fsck_pass1_check_device_inode(fs, &inode)) + problem = PR_2_BAD_CHAR_DEV; + else if (LINUX_S_ISBLK(inode.i_mode) + && !e2fsck_pass1_check_device_inode(fs, &inode)) + problem = PR_2_BAD_BLOCK_DEV; + else if (LINUX_S_ISFIFO(inode.i_mode) + && !e2fsck_pass1_check_device_inode(fs, &inode)) + problem = PR_2_BAD_FIFO; + else if (LINUX_S_ISSOCK(inode.i_mode) + && !e2fsck_pass1_check_device_inode(fs, &inode)) + problem = PR_2_BAD_SOCKET; + else if (LINUX_S_ISLNK(inode.i_mode) + && !e2fsck_pass1_check_symlink(fs, &inode, buf)) { + problem = PR_2_INVALID_SYMLINK; + } + + if (problem) { + if (fix_problem(ctx, problem, &pctx)) { + deallocate_inode(ctx, ino, 0); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return 0; + return 1; + } else + not_fixed++; + problem = 0; + } + + if (inode.i_faddr) { + if (fix_problem(ctx, PR_2_FADDR_ZERO, &pctx)) { + inode.i_faddr = 0; + inode_modified++; + } else + not_fixed++; + } + + switch (fs->super->s_creator_os) { + case EXT2_OS_LINUX: + frag = &inode.osd2.linux2.l_i_frag; + fsize = &inode.osd2.linux2.l_i_fsize; + break; + case EXT2_OS_HURD: + frag = &inode.osd2.hurd2.h_i_frag; + fsize = &inode.osd2.hurd2.h_i_fsize; + break; + case EXT2_OS_MASIX: + frag = &inode.osd2.masix2.m_i_frag; + fsize = &inode.osd2.masix2.m_i_fsize; + break; + default: + frag = fsize = 0; + } + if (frag && *frag) { + pctx.num = *frag; + if (fix_problem(ctx, PR_2_FRAG_ZERO, &pctx)) { + *frag = 0; + inode_modified++; + } else + not_fixed++; + pctx.num = 0; + } + if (fsize && *fsize) { + pctx.num = *fsize; + if (fix_problem(ctx, PR_2_FSIZE_ZERO, &pctx)) { + *fsize = 0; + inode_modified++; + } else + not_fixed++; + pctx.num = 0; + } + + if (inode.i_file_acl && + ((inode.i_file_acl < fs->super->s_first_data_block) || + (inode.i_file_acl >= fs->super->s_blocks_count))) { + if (fix_problem(ctx, PR_2_FILE_ACL_BAD, &pctx)) { + inode.i_file_acl = 0; + inode_modified++; + } else + not_fixed++; + } + if (inode.i_dir_acl && + LINUX_S_ISDIR(inode.i_mode)) { + if (fix_problem(ctx, PR_2_DIR_ACL_ZERO, &pctx)) { + inode.i_dir_acl = 0; + inode_modified++; + } else + not_fixed++; + } + + if (inode_modified) + e2fsck_write_inode(ctx, ino, &inode, "process_bad_inode"); + if (!not_fixed) + ext2fs_unmark_inode_bitmap(ctx->inode_bad_map, ino); + return 0; +} + + +/* + * allocate_dir_block --- this function allocates a new directory + * block for a particular inode; this is done if a directory has + * a "hole" in it, or if a directory has a illegal block number + * that was zeroed out and now needs to be replaced. + */ +static int allocate_dir_block(e2fsck_t ctx, struct ext2_db_entry *db, + struct problem_context *pctx) +{ + ext2_filsys fs = ctx->fs; + blk_t blk; + char *block; + struct ext2_inode inode; + + if (fix_problem(ctx, PR_2_DIRECTORY_HOLE, pctx) == 0) + return 1; + + /* + * Read the inode and block bitmaps in; we'll be messing with + * them. + */ + e2fsck_read_bitmaps(ctx); + + /* + * First, find a free block + */ + pctx->errcode = ext2fs_new_block(fs, 0, ctx->block_found_map, &blk); + if (pctx->errcode) { + pctx->str = "ext2fs_new_block"; + fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx); + return 1; + } + ext2fs_mark_block_bitmap(ctx->block_found_map, blk); + ext2fs_mark_block_bitmap(fs->block_map, blk); + ext2fs_mark_bb_dirty(fs); + + /* + * Now let's create the actual data block for the inode + */ + if (db->blockcnt) + pctx->errcode = ext2fs_new_dir_block(fs, 0, 0, &block); + else + pctx->errcode = ext2fs_new_dir_block(fs, db->ino, + EXT2_ROOT_INO, &block); + + if (pctx->errcode) { + pctx->str = "ext2fs_new_dir_block"; + fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx); + return 1; + } + + pctx->errcode = ext2fs_write_dir_block(fs, blk, block); + ext2fs_free_mem(&block); + if (pctx->errcode) { + pctx->str = "ext2fs_write_dir_block"; + fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx); + return 1; + } + + /* + * Update the inode block count + */ + e2fsck_read_inode(ctx, db->ino, &inode, "allocate_dir_block"); + inode.i_blocks += fs->blocksize / 512; + if (inode.i_size < (db->blockcnt+1) * fs->blocksize) + inode.i_size = (db->blockcnt+1) * fs->blocksize; + e2fsck_write_inode(ctx, db->ino, &inode, "allocate_dir_block"); + + /* + * Finally, update the block pointers for the inode + */ + db->blk = blk; + pctx->errcode = ext2fs_block_iterate2(fs, db->ino, BLOCK_FLAG_HOLE, + 0, update_dir_block, db); + if (pctx->errcode) { + pctx->str = "ext2fs_block_iterate"; + fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx); + return 1; + } + + return 0; +} + +/* + * This is a helper function for allocate_dir_block(). + */ +static int update_dir_block(ext2_filsys fs FSCK_ATTR((unused)), + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct ext2_db_entry *db; + + db = (struct ext2_db_entry *) priv_data; + if (db->blockcnt == (int) blockcnt) { + *block_nr = db->blk; + return BLOCK_CHANGED; + } + return 0; +} + +/* + * pass3.c -- pass #3 of e2fsck: Check for directory connectivity + * + * Pass #3 assures that all directories are connected to the + * filesystem tree, using the following algorithm: + * + * First, the root directory is checked to make sure it exists; if + * not, e2fsck will offer to create a new one. It is then marked as + * "done". + * + * Then, pass3 interates over all directory inodes; for each directory + * it attempts to trace up the filesystem tree, using dirinfo.parent + * until it reaches a directory which has been marked "done". If it + * cannot do so, then the directory must be disconnected, and e2fsck + * will offer to reconnect it to /lost+found. While it is chasing + * parent pointers up the filesystem tree, if pass3 sees a directory + * twice, then it has detected a filesystem loop, and it will again + * offer to reconnect the directory to /lost+found in to break the + * filesystem loop. + * + * Pass 3 also contains the subroutine, e2fsck_reconnect_file() to + * reconnect inodes to /lost+found; this subroutine is also used by + * pass 4. e2fsck_reconnect_file() calls get_lost_and_found(), which + * is responsible for creating /lost+found if it does not exist. + * + * Pass 3 frees the following data structures: + * - The dirinfo directory information cache. + */ + +static void check_root(e2fsck_t ctx); +static int check_directory(e2fsck_t ctx, struct dir_info *dir, + struct problem_context *pctx); +static void fix_dotdot(e2fsck_t ctx, struct dir_info *dir, ext2_ino_t parent); + +static ext2fs_inode_bitmap inode_loop_detect; +static ext2fs_inode_bitmap inode_done_map; + +static void e2fsck_pass3(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + int i; + struct problem_context pctx; + struct dir_info *dir; + unsigned long maxdirs, count; + + clear_problem_context(&pctx); + + /* Pass 3 */ + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_3_PASS_HEADER, &pctx); + + /* + * Allocate some bitmaps to do loop detection. + */ + pctx.errcode = ext2fs_allocate_inode_bitmap(fs, _("inode done bitmap"), + &inode_done_map); + if (pctx.errcode) { + pctx.num = 2; + fix_problem(ctx, PR_3_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + goto abort_exit; + } + check_root(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + goto abort_exit; + + ext2fs_mark_inode_bitmap(inode_done_map, EXT2_ROOT_INO); + + maxdirs = e2fsck_get_num_dirinfo(ctx); + count = 1; + + if (ctx->progress) + if ((ctx->progress)(ctx, 3, 0, maxdirs)) + goto abort_exit; + + for (i=0; (dir = e2fsck_dir_info_iter(ctx, &i)) != 0;) { + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + goto abort_exit; + if (ctx->progress && (ctx->progress)(ctx, 3, count++, maxdirs)) + goto abort_exit; + if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, dir->ino)) + if (check_directory(ctx, dir, &pctx)) + goto abort_exit; + } + + /* + * Force the creation of /lost+found if not present + */ + if ((ctx->flags & E2F_OPT_READONLY) == 0) + e2fsck_get_lost_and_found(ctx, 1); + + /* + * If there are any directories that need to be indexed or + * optimized, do it here. + */ + e2fsck_rehash_directories(ctx); + +abort_exit: + e2fsck_free_dir_info(ctx); + ext2fs_free_inode_bitmap(inode_loop_detect); + inode_loop_detect = 0; + ext2fs_free_inode_bitmap(inode_done_map); + inode_done_map = 0; +} + +/* + * This makes sure the root inode is present; if not, we ask if the + * user wants us to create it. Not creating it is a fatal error. + */ +static void check_root(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk_t blk; + struct ext2_inode inode; + char * block; + struct problem_context pctx; + + clear_problem_context(&pctx); + + if (ext2fs_test_inode_bitmap(ctx->inode_used_map, EXT2_ROOT_INO)) { + /* + * If the root inode is not a directory, die here. The + * user must have answered 'no' in pass1 when we + * offered to clear it. + */ + if (!(ext2fs_test_inode_bitmap(ctx->inode_dir_map, + EXT2_ROOT_INO))) { + fix_problem(ctx, PR_3_ROOT_NOT_DIR_ABORT, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + } + return; + } + + if (!fix_problem(ctx, PR_3_NO_ROOT_INODE, &pctx)) { + fix_problem(ctx, PR_3_NO_ROOT_INODE_ABORT, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + e2fsck_read_bitmaps(ctx); + + /* + * First, find a free block + */ + pctx.errcode = ext2fs_new_block(fs, 0, ctx->block_found_map, &blk); + if (pctx.errcode) { + pctx.str = "ext2fs_new_block"; + fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_mark_block_bitmap(ctx->block_found_map, blk); + ext2fs_mark_block_bitmap(fs->block_map, blk); + ext2fs_mark_bb_dirty(fs); + + /* + * Now let's create the actual data block for the inode + */ + pctx.errcode = ext2fs_new_dir_block(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, + &block); + if (pctx.errcode) { + pctx.str = "ext2fs_new_dir_block"; + fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + pctx.errcode = ext2fs_write_dir_block(fs, blk, block); + if (pctx.errcode) { + pctx.str = "ext2fs_write_dir_block"; + fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_free_mem(&block); + + /* + * Set up the inode structure + */ + memset(&inode, 0, sizeof(inode)); + inode.i_mode = 040755; + inode.i_size = fs->blocksize; + inode.i_atime = inode.i_ctime = inode.i_mtime = time(0); + inode.i_links_count = 2; + inode.i_blocks = fs->blocksize / 512; + inode.i_block[0] = blk; + + /* + * Write out the inode. + */ + pctx.errcode = ext2fs_write_new_inode(fs, EXT2_ROOT_INO, &inode); + if (pctx.errcode) { + pctx.str = "ext2fs_write_inode"; + fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + /* + * Miscellaneous bookkeeping... + */ + e2fsck_add_dir_info(ctx, EXT2_ROOT_INO, EXT2_ROOT_INO); + ext2fs_icount_store(ctx->inode_count, EXT2_ROOT_INO, 2); + ext2fs_icount_store(ctx->inode_link_info, EXT2_ROOT_INO, 2); + + ext2fs_mark_inode_bitmap(ctx->inode_used_map, EXT2_ROOT_INO); + ext2fs_mark_inode_bitmap(ctx->inode_dir_map, EXT2_ROOT_INO); + ext2fs_mark_inode_bitmap(fs->inode_map, EXT2_ROOT_INO); + ext2fs_mark_ib_dirty(fs); +} + +/* + * This subroutine is responsible for making sure that a particular + * directory is connected to the root; if it isn't we trace it up as + * far as we can go, and then offer to connect the resulting parent to + * the lost+found. We have to do loop detection; if we ever discover + * a loop, we treat that as a disconnected directory and offer to + * reparent it to lost+found. + * + * However, loop detection is expensive, because for very large + * filesystems, the inode_loop_detect bitmap is huge, and clearing it + * is non-trivial. Loops in filesystems are also a rare error case, + * and we shouldn't optimize for error cases. So we try two passes of + * the algorithm. The first time, we ignore loop detection and merely + * increment a counter; if the counter exceeds some extreme threshold, + * then we try again with the loop detection bitmap enabled. + */ +static int check_directory(e2fsck_t ctx, struct dir_info *dir, + struct problem_context *pctx) +{ + ext2_filsys fs = ctx->fs; + struct dir_info *p = dir; + int loop_pass = 0, parent_count = 0; + + if (!p) + return 0; + + while (1) { + /* + * Mark this inode as being "done"; by the time we + * return from this function, the inode we either be + * verified as being connected to the directory tree, + * or we will have offered to reconnect this to + * lost+found. + * + * If it was marked done already, then we've reached a + * parent we've already checked. + */ + if (ext2fs_mark_inode_bitmap(inode_done_map, p->ino)) + break; + + /* + * If this directory doesn't have a parent, or we've + * seen the parent once already, then offer to + * reparent it to lost+found + */ + if (!p->parent || + (loop_pass && + (ext2fs_test_inode_bitmap(inode_loop_detect, + p->parent)))) { + pctx->ino = p->ino; + if (fix_problem(ctx, PR_3_UNCONNECTED_DIR, pctx)) { + if (e2fsck_reconnect_file(ctx, pctx->ino)) + ext2fs_unmark_valid(fs); + else { + p = e2fsck_get_dir_info(ctx, pctx->ino); + p->parent = ctx->lost_and_found; + fix_dotdot(ctx, p, ctx->lost_and_found); + } + } + break; + } + p = e2fsck_get_dir_info(ctx, p->parent); + if (!p) { + fix_problem(ctx, PR_3_NO_DIRINFO, pctx); + return 0; + } + if (loop_pass) { + ext2fs_mark_inode_bitmap(inode_loop_detect, + p->ino); + } else if (parent_count++ > 2048) { + /* + * If we've run into a path depth that's + * greater than 2048, try again with the inode + * loop bitmap turned on and start from the + * top. + */ + loop_pass = 1; + if (inode_loop_detect) + ext2fs_clear_inode_bitmap(inode_loop_detect); + else { + pctx->errcode = ext2fs_allocate_inode_bitmap(fs, _("inode loop detection bitmap"), &inode_loop_detect); + if (pctx->errcode) { + pctx->num = 1; + fix_problem(ctx, + PR_3_ALLOCATE_IBITMAP_ERROR, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return -1; + } + } + p = dir; + } + } + + /* + * Make sure that .. and the parent directory are the same; + * offer to fix it if not. + */ + if (dir->parent != dir->dotdot) { + pctx->ino = dir->ino; + pctx->ino2 = dir->dotdot; + pctx->dir = dir->parent; + if (fix_problem(ctx, PR_3_BAD_DOT_DOT, pctx)) + fix_dotdot(ctx, dir, dir->parent); + } + return 0; +} + +/* + * This routine gets the lost_and_found inode, making it a directory + * if necessary + */ +ext2_ino_t e2fsck_get_lost_and_found(e2fsck_t ctx, int fix) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t ino; + blk_t blk; + errcode_t retval; + struct ext2_inode inode; + char * block; + static const char name[] = "lost+found"; + struct problem_context pctx; + struct dir_info *dirinfo; + + if (ctx->lost_and_found) + return ctx->lost_and_found; + + clear_problem_context(&pctx); + + retval = ext2fs_lookup(fs, EXT2_ROOT_INO, name, + sizeof(name)-1, 0, &ino); + if (retval && !fix) + return 0; + if (!retval) { + if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, ino)) { + ctx->lost_and_found = ino; + return ino; + } + + /* Lost+found isn't a directory! */ + if (!fix) + return 0; + pctx.ino = ino; + if (!fix_problem(ctx, PR_3_LPF_NOTDIR, &pctx)) + return 0; + + /* OK, unlink the old /lost+found file. */ + pctx.errcode = ext2fs_unlink(fs, EXT2_ROOT_INO, name, ino, 0); + if (pctx.errcode) { + pctx.str = "ext2fs_unlink"; + fix_problem(ctx, PR_3_CREATE_LPF_ERROR, &pctx); + return 0; + } + dirinfo = e2fsck_get_dir_info(ctx, ino); + if (dirinfo) + dirinfo->parent = 0; + e2fsck_adjust_inode_count(ctx, ino, -1); + } else if (retval != EXT2_ET_FILE_NOT_FOUND) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_ERR_FIND_LPF, &pctx); + } + if (!fix_problem(ctx, PR_3_NO_LF_DIR, 0)) + return 0; + + /* + * Read the inode and block bitmaps in; we'll be messing with + * them. + */ + e2fsck_read_bitmaps(ctx); + + /* + * First, find a free block + */ + retval = ext2fs_new_block(fs, 0, ctx->block_found_map, &blk); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_ERR_LPF_NEW_BLOCK, &pctx); + return 0; + } + ext2fs_mark_block_bitmap(ctx->block_found_map, blk); + ext2fs_block_alloc_stats(fs, blk, +1); + + /* + * Next find a free inode. + */ + retval = ext2fs_new_inode(fs, EXT2_ROOT_INO, 040700, + ctx->inode_used_map, &ino); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_ERR_LPF_NEW_INODE, &pctx); + return 0; + } + ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); + ext2fs_mark_inode_bitmap(ctx->inode_dir_map, ino); + ext2fs_inode_alloc_stats2(fs, ino, +1, 1); + + /* + * Now let's create the actual data block for the inode + */ + retval = ext2fs_new_dir_block(fs, ino, EXT2_ROOT_INO, &block); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_ERR_LPF_NEW_DIR_BLOCK, &pctx); + return 0; + } + + retval = ext2fs_write_dir_block(fs, blk, block); + ext2fs_free_mem(&block); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_ERR_LPF_WRITE_BLOCK, &pctx); + return 0; + } + + /* + * Set up the inode structure + */ + memset(&inode, 0, sizeof(inode)); + inode.i_mode = 040700; + inode.i_size = fs->blocksize; + inode.i_atime = inode.i_ctime = inode.i_mtime = time(0); + inode.i_links_count = 2; + inode.i_blocks = fs->blocksize / 512; + inode.i_block[0] = blk; + + /* + * Next, write out the inode. + */ + pctx.errcode = ext2fs_write_new_inode(fs, ino, &inode); + if (pctx.errcode) { + pctx.str = "ext2fs_write_inode"; + fix_problem(ctx, PR_3_CREATE_LPF_ERROR, &pctx); + return 0; + } + /* + * Finally, create the directory link + */ + pctx.errcode = ext2fs_link(fs, EXT2_ROOT_INO, name, ino, EXT2_FT_DIR); + if (pctx.errcode) { + pctx.str = "ext2fs_link"; + fix_problem(ctx, PR_3_CREATE_LPF_ERROR, &pctx); + return 0; + } + + /* + * Miscellaneous bookkeeping that needs to be kept straight. + */ + e2fsck_add_dir_info(ctx, ino, EXT2_ROOT_INO); + e2fsck_adjust_inode_count(ctx, EXT2_ROOT_INO, 1); + ext2fs_icount_store(ctx->inode_count, ino, 2); + ext2fs_icount_store(ctx->inode_link_info, ino, 2); + ctx->lost_and_found = ino; + return ino; +} + +/* + * This routine will connect a file to lost+found + */ +int e2fsck_reconnect_file(e2fsck_t ctx, ext2_ino_t ino) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + char name[80]; + struct problem_context pctx; + struct ext2_inode inode; + int file_type = 0; + + clear_problem_context(&pctx); + pctx.ino = ino; + + if (!ctx->bad_lost_and_found && !ctx->lost_and_found) { + if (e2fsck_get_lost_and_found(ctx, 1) == 0) + ctx->bad_lost_and_found++; + } + if (ctx->bad_lost_and_found) { + fix_problem(ctx, PR_3_NO_LPF, &pctx); + return 1; + } + + sprintf(name, "#%u", ino); + if (ext2fs_read_inode(fs, ino, &inode) == 0) + file_type = ext2_file_type(inode.i_mode); + retval = ext2fs_link(fs, ctx->lost_and_found, name, ino, file_type); + if (retval == EXT2_ET_DIR_NO_SPACE) { + if (!fix_problem(ctx, PR_3_EXPAND_LF_DIR, &pctx)) + return 1; + retval = e2fsck_expand_directory(ctx, ctx->lost_and_found, + 1, 0); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_CANT_EXPAND_LPF, &pctx); + return 1; + } + retval = ext2fs_link(fs, ctx->lost_and_found, name, + ino, file_type); + } + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_CANT_RECONNECT, &pctx); + return 1; + } + e2fsck_adjust_inode_count(ctx, ino, 1); + + return 0; +} + +/* + * Utility routine to adjust the inode counts on an inode. + */ +errcode_t e2fsck_adjust_inode_count(e2fsck_t ctx, ext2_ino_t ino, int adj) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + struct ext2_inode inode; + + if (!ino) + return 0; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + + if (adj == 1) { + ext2fs_icount_increment(ctx->inode_count, ino, 0); + if (inode.i_links_count == (__u16) ~0) + return 0; + ext2fs_icount_increment(ctx->inode_link_info, ino, 0); + inode.i_links_count++; + } else if (adj == -1) { + ext2fs_icount_decrement(ctx->inode_count, ino, 0); + if (inode.i_links_count == 0) + return 0; + ext2fs_icount_decrement(ctx->inode_link_info, ino, 0); + inode.i_links_count--; + } + + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) + return retval; + + return 0; +} + +/* + * Fix parent --- this routine fixes up the parent of a directory. + */ +struct fix_dotdot_struct { + ext2_filsys fs; + ext2_ino_t parent; + int done; + e2fsck_t ctx; +}; + +static int fix_dotdot_proc(struct ext2_dir_entry *dirent, + int offset FSCK_ATTR((unused)), + int blocksize FSCK_ATTR((unused)), + char *buf FSCK_ATTR((unused)), + void *priv_data) +{ + struct fix_dotdot_struct *fp = (struct fix_dotdot_struct *) priv_data; + errcode_t retval; + struct problem_context pctx; + + if ((dirent->name_len & 0xFF) != 2) + return 0; + if (strncmp(dirent->name, "..", 2)) + return 0; + + clear_problem_context(&pctx); + + retval = e2fsck_adjust_inode_count(fp->ctx, dirent->inode, -1); + if (retval) { + pctx.errcode = retval; + fix_problem(fp->ctx, PR_3_ADJUST_INODE, &pctx); + } + retval = e2fsck_adjust_inode_count(fp->ctx, fp->parent, 1); + if (retval) { + pctx.errcode = retval; + fix_problem(fp->ctx, PR_3_ADJUST_INODE, &pctx); + } + dirent->inode = fp->parent; + + fp->done++; + return DIRENT_ABORT | DIRENT_CHANGED; +} + +static void fix_dotdot(e2fsck_t ctx, struct dir_info *dir, ext2_ino_t parent) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + struct fix_dotdot_struct fp; + struct problem_context pctx; + + fp.fs = fs; + fp.parent = parent; + fp.done = 0; + fp.ctx = ctx; + + retval = ext2fs_dir_iterate(fs, dir->ino, DIRENT_FLAG_INCLUDE_EMPTY, + 0, fix_dotdot_proc, &fp); + if (retval || !fp.done) { + clear_problem_context(&pctx); + pctx.ino = dir->ino; + pctx.errcode = retval; + fix_problem(ctx, retval ? PR_3_FIX_PARENT_ERR : + PR_3_FIX_PARENT_NOFIND, &pctx); + ext2fs_unmark_valid(fs); + } + dir->dotdot = parent; + + return; +} + +/* + * These routines are responsible for expanding a /lost+found if it is + * too small. + */ + +struct expand_dir_struct { + int num; + int guaranteed_size; + int newblocks; + int last_block; + errcode_t err; + e2fsck_t ctx; +}; + +static int expand_dir_proc(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct expand_dir_struct *es = (struct expand_dir_struct *) priv_data; + blk_t new_blk; + static blk_t last_blk = 0; + char *block; + errcode_t retval; + e2fsck_t ctx; + + ctx = es->ctx; + + if (es->guaranteed_size && blockcnt >= es->guaranteed_size) + return BLOCK_ABORT; + + if (blockcnt > 0) + es->last_block = blockcnt; + if (*blocknr) { + last_blk = *blocknr; + return 0; + } + retval = ext2fs_new_block(fs, last_blk, ctx->block_found_map, + &new_blk); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + if (blockcnt > 0) { + retval = ext2fs_new_dir_block(fs, 0, 0, &block); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + es->num--; + retval = ext2fs_write_dir_block(fs, new_blk, block); + } else { + retval = ext2fs_get_mem(fs->blocksize, &block); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + memset(block, 0, fs->blocksize); + retval = io_channel_write_blk(fs->io, new_blk, 1, block); + } + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + ext2fs_free_mem(&block); + *blocknr = new_blk; + ext2fs_mark_block_bitmap(ctx->block_found_map, new_blk); + ext2fs_block_alloc_stats(fs, new_blk, +1); + es->newblocks++; + + if (es->num == 0) + return (BLOCK_CHANGED | BLOCK_ABORT); + else + return BLOCK_CHANGED; +} + +errcode_t e2fsck_expand_directory(e2fsck_t ctx, ext2_ino_t dir, + int num, int guaranteed_size) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + struct expand_dir_struct es; + struct ext2_inode inode; + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + /* + * Read the inode and block bitmaps in; we'll be messing with + * them. + */ + e2fsck_read_bitmaps(ctx); + + retval = ext2fs_check_directory(fs, dir); + if (retval) + return retval; + + es.num = num; + es.guaranteed_size = guaranteed_size; + es.last_block = 0; + es.err = 0; + es.newblocks = 0; + es.ctx = ctx; + + retval = ext2fs_block_iterate2(fs, dir, BLOCK_FLAG_APPEND, + 0, expand_dir_proc, &es); + + if (es.err) + return es.err; + + /* + * Update the size and block count fields in the inode. + */ + retval = ext2fs_read_inode(fs, dir, &inode); + if (retval) + return retval; + + inode.i_size = (es.last_block + 1) * fs->blocksize; + inode.i_blocks += (fs->blocksize / 512) * es.newblocks; + + e2fsck_write_inode(ctx, dir, &inode, "expand_directory"); + + return 0; +} + +/* + * pass4.c -- pass #4 of e2fsck: Check reference counts + * + * Pass 4 frees the following data structures: + * - A bitmap of which inodes are imagic inodes. (inode_imagic_map) + */ + +/* + * This routine is called when an inode is not connected to the + * directory tree. + * + * This subroutine returns 1 then the caller shouldn't bother with the + * rest of the pass 4 tests. + */ +static int disconnect_inode(e2fsck_t ctx, ext2_ino_t i) +{ + ext2_filsys fs = ctx->fs; + struct ext2_inode inode; + struct problem_context pctx; + + e2fsck_read_inode(ctx, i, &inode, "pass4: disconnect_inode"); + clear_problem_context(&pctx); + pctx.ino = i; + pctx.inode = &inode; + + /* + * Offer to delete any zero-length files that does not have + * blocks. If there is an EA block, it might have useful + * information, so we won't prompt to delete it, but let it be + * reconnected to lost+found. + */ + if (!inode.i_blocks && (LINUX_S_ISREG(inode.i_mode) || + LINUX_S_ISDIR(inode.i_mode))) { + if (fix_problem(ctx, PR_4_ZERO_LEN_INODE, &pctx)) { + ext2fs_icount_store(ctx->inode_link_info, i, 0); + inode.i_links_count = 0; + inode.i_dtime = time(0); + e2fsck_write_inode(ctx, i, &inode, + "disconnect_inode"); + /* + * Fix up the bitmaps... + */ + e2fsck_read_bitmaps(ctx); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, i); + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, i); + ext2fs_inode_alloc_stats2(fs, i, -1, + LINUX_S_ISDIR(inode.i_mode)); + return 0; + } + } + + /* + * Prompt to reconnect. + */ + if (fix_problem(ctx, PR_4_UNATTACHED_INODE, &pctx)) { + if (e2fsck_reconnect_file(ctx, i)) + ext2fs_unmark_valid(fs); + } else { + /* + * If we don't attach the inode, then skip the + * i_links_test since there's no point in trying to + * force i_links_count to zero. + */ + ext2fs_unmark_valid(fs); + return 1; + } + return 0; +} + + +static void e2fsck_pass4(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t i; + struct ext2_inode inode; + struct problem_context pctx; + __u16 link_count, link_counted; + char *buf = 0; + int group, maxgroup; + + /* Pass 4 */ + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_4_PASS_HEADER, &pctx); + + group = 0; + maxgroup = fs->group_desc_count; + if (ctx->progress) + if ((ctx->progress)(ctx, 4, 0, maxgroup)) + return; + + for (i=1; i <= fs->super->s_inodes_count; i++) { + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + if ((i % fs->super->s_inodes_per_group) == 0) { + group++; + if (ctx->progress) + if ((ctx->progress)(ctx, 4, group, maxgroup)) + return; + } + if (i == EXT2_BAD_INO || + (i > EXT2_ROOT_INO && i < EXT2_FIRST_INODE(fs->super))) + continue; + if (!(ext2fs_test_inode_bitmap(ctx->inode_used_map, i)) || + (ctx->inode_imagic_map && + ext2fs_test_inode_bitmap(ctx->inode_imagic_map, i))) + continue; + ext2fs_icount_fetch(ctx->inode_link_info, i, &link_count); + ext2fs_icount_fetch(ctx->inode_count, i, &link_counted); + if (link_counted == 0) { + if (!buf) + buf = e2fsck_allocate_memory(ctx, + fs->blocksize, "bad_inode buffer"); + if (e2fsck_process_bad_inode(ctx, 0, i, buf)) + continue; + if (disconnect_inode(ctx, i)) + continue; + ext2fs_icount_fetch(ctx->inode_link_info, i, + &link_count); + ext2fs_icount_fetch(ctx->inode_count, i, + &link_counted); + } + if (link_counted != link_count) { + e2fsck_read_inode(ctx, i, &inode, "pass4"); + pctx.ino = i; + pctx.inode = &inode; + if (link_count != inode.i_links_count) { + pctx.num = link_count; + fix_problem(ctx, + PR_4_INCONSISTENT_COUNT, &pctx); + } + pctx.num = link_counted; + if (fix_problem(ctx, PR_4_BAD_REF_COUNT, &pctx)) { + inode.i_links_count = link_counted; + e2fsck_write_inode(ctx, i, &inode, "pass4"); + } + } + } + ext2fs_free_icount(ctx->inode_link_info); ctx->inode_link_info = 0; + ext2fs_free_icount(ctx->inode_count); ctx->inode_count = 0; + ext2fs_free_inode_bitmap(ctx->inode_imagic_map); + ctx->inode_imagic_map = 0; + ext2fs_free_mem(&buf); +} + +/* + * pass5.c --- check block and inode bitmaps against on-disk bitmaps + */ + +#define NO_BLK ((blk_t) -1) + +static void print_bitmap_problem(e2fsck_t ctx, int problem, + struct problem_context *pctx) +{ + switch (problem) { + case PR_5_BLOCK_UNUSED: + if (pctx->blk == pctx->blk2) + pctx->blk2 = 0; + else + problem = PR_5_BLOCK_RANGE_UNUSED; + break; + case PR_5_BLOCK_USED: + if (pctx->blk == pctx->blk2) + pctx->blk2 = 0; + else + problem = PR_5_BLOCK_RANGE_USED; + break; + case PR_5_INODE_UNUSED: + if (pctx->ino == pctx->ino2) + pctx->ino2 = 0; + else + problem = PR_5_INODE_RANGE_UNUSED; + break; + case PR_5_INODE_USED: + if (pctx->ino == pctx->ino2) + pctx->ino2 = 0; + else + problem = PR_5_INODE_RANGE_USED; + break; + } + fix_problem(ctx, problem, pctx); + pctx->blk = pctx->blk2 = NO_BLK; + pctx->ino = pctx->ino2 = 0; +} + +static void check_block_bitmaps(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk_t i; + int *free_array; + int group = 0; + unsigned int blocks = 0; + unsigned int free_blocks = 0; + int group_free = 0; + int actual, bitmap; + struct problem_context pctx; + int problem, save_problem, fixit, had_problem; + errcode_t retval; + + clear_problem_context(&pctx); + free_array = (int *) e2fsck_allocate_memory(ctx, + fs->group_desc_count * sizeof(int), "free block count array"); + + if ((fs->super->s_first_data_block < + ext2fs_get_block_bitmap_start(ctx->block_found_map)) || + (fs->super->s_blocks_count-1 > + ext2fs_get_block_bitmap_end(ctx->block_found_map))) { + pctx.num = 1; + pctx.blk = fs->super->s_first_data_block; + pctx.blk2 = fs->super->s_blocks_count -1; + pctx.ino = ext2fs_get_block_bitmap_start(ctx->block_found_map); + pctx.ino2 = ext2fs_get_block_bitmap_end(ctx->block_found_map); + fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx); + + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + + if ((fs->super->s_first_data_block < + ext2fs_get_block_bitmap_start(fs->block_map)) || + (fs->super->s_blocks_count-1 > + ext2fs_get_block_bitmap_end(fs->block_map))) { + pctx.num = 2; + pctx.blk = fs->super->s_first_data_block; + pctx.blk2 = fs->super->s_blocks_count -1; + pctx.ino = ext2fs_get_block_bitmap_start(fs->block_map); + pctx.ino2 = ext2fs_get_block_bitmap_end(fs->block_map); + fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx); + + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + +redo_counts: + had_problem = 0; + save_problem = 0; + pctx.blk = pctx.blk2 = NO_BLK; + for (i = fs->super->s_first_data_block; + i < fs->super->s_blocks_count; + i++) { + actual = ext2fs_fast_test_block_bitmap(ctx->block_found_map, i); + bitmap = ext2fs_fast_test_block_bitmap(fs->block_map, i); + + if (actual == bitmap) + goto do_counts; + + if (!actual && bitmap) { + /* + * Block not used, but marked in use in the bitmap. + */ + problem = PR_5_BLOCK_UNUSED; + } else { + /* + * Block used, but not marked in use in the bitmap. + */ + problem = PR_5_BLOCK_USED; + } + if (pctx.blk == NO_BLK) { + pctx.blk = pctx.blk2 = i; + save_problem = problem; + } else { + if ((problem == save_problem) && + (pctx.blk2 == i-1)) + pctx.blk2++; + else { + print_bitmap_problem(ctx, save_problem, &pctx); + pctx.blk = pctx.blk2 = i; + save_problem = problem; + } + } + ctx->flags |= E2F_FLAG_PROG_SUPPRESS; + had_problem++; + + do_counts: + if (!bitmap) { + group_free++; + free_blocks++; + } + blocks ++; + if ((blocks == fs->super->s_blocks_per_group) || + (i == fs->super->s_blocks_count-1)) { + free_array[group] = group_free; + group ++; + blocks = 0; + group_free = 0; + if (ctx->progress) + if ((ctx->progress)(ctx, 5, group, + fs->group_desc_count*2)) + return; + } + } + if (pctx.blk != NO_BLK) + print_bitmap_problem(ctx, save_problem, &pctx); + if (had_problem) + fixit = end_problem_latch(ctx, PR_LATCH_BBITMAP); + else + fixit = -1; + ctx->flags &= ~E2F_FLAG_PROG_SUPPRESS; + + if (fixit == 1) { + ext2fs_free_block_bitmap(fs->block_map); + retval = ext2fs_copy_bitmap(ctx->block_found_map, + &fs->block_map); + if (retval) { + clear_problem_context(&pctx); + fix_problem(ctx, PR_5_COPY_BBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_set_bitmap_padding(fs->block_map); + ext2fs_mark_bb_dirty(fs); + + /* Redo the counts */ + blocks = 0; free_blocks = 0; group_free = 0; group = 0; + memset(free_array, 0, fs->group_desc_count * sizeof(int)); + goto redo_counts; + } else if (fixit == 0) + ext2fs_unmark_valid(fs); + + for (i = 0; i < fs->group_desc_count; i++) { + if (free_array[i] != fs->group_desc[i].bg_free_blocks_count) { + pctx.group = i; + pctx.blk = fs->group_desc[i].bg_free_blocks_count; + pctx.blk2 = free_array[i]; + + if (fix_problem(ctx, PR_5_FREE_BLOCK_COUNT_GROUP, + &pctx)) { + fs->group_desc[i].bg_free_blocks_count = + free_array[i]; + ext2fs_mark_super_dirty(fs); + } else + ext2fs_unmark_valid(fs); + } + } + if (free_blocks != fs->super->s_free_blocks_count) { + pctx.group = 0; + pctx.blk = fs->super->s_free_blocks_count; + pctx.blk2 = free_blocks; + + if (fix_problem(ctx, PR_5_FREE_BLOCK_COUNT, &pctx)) { + fs->super->s_free_blocks_count = free_blocks; + ext2fs_mark_super_dirty(fs); + } else + ext2fs_unmark_valid(fs); + } + ext2fs_free_mem(&free_array); +} + +static void check_inode_bitmaps(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t i; + unsigned int free_inodes = 0; + int group_free = 0; + int dirs_count = 0; + int group = 0; + unsigned int inodes = 0; + int *free_array; + int *dir_array; + int actual, bitmap; + errcode_t retval; + struct problem_context pctx; + int problem, save_problem, fixit, had_problem; + + clear_problem_context(&pctx); + free_array = (int *) e2fsck_allocate_memory(ctx, + fs->group_desc_count * sizeof(int), "free inode count array"); + + dir_array = (int *) e2fsck_allocate_memory(ctx, + fs->group_desc_count * sizeof(int), "directory count array"); + + if ((1 < ext2fs_get_inode_bitmap_start(ctx->inode_used_map)) || + (fs->super->s_inodes_count > + ext2fs_get_inode_bitmap_end(ctx->inode_used_map))) { + pctx.num = 3; + pctx.blk = 1; + pctx.blk2 = fs->super->s_inodes_count; + pctx.ino = ext2fs_get_inode_bitmap_start(ctx->inode_used_map); + pctx.ino2 = ext2fs_get_inode_bitmap_end(ctx->inode_used_map); + fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx); + + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + if ((1 < ext2fs_get_inode_bitmap_start(fs->inode_map)) || + (fs->super->s_inodes_count > + ext2fs_get_inode_bitmap_end(fs->inode_map))) { + pctx.num = 4; + pctx.blk = 1; + pctx.blk2 = fs->super->s_inodes_count; + pctx.ino = ext2fs_get_inode_bitmap_start(fs->inode_map); + pctx.ino2 = ext2fs_get_inode_bitmap_end(fs->inode_map); + fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx); + + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + +redo_counts: + had_problem = 0; + save_problem = 0; + pctx.ino = pctx.ino2 = 0; + for (i = 1; i <= fs->super->s_inodes_count; i++) { + actual = ext2fs_fast_test_inode_bitmap(ctx->inode_used_map, i); + bitmap = ext2fs_fast_test_inode_bitmap(fs->inode_map, i); + + if (actual == bitmap) + goto do_counts; + + if (!actual && bitmap) { + /* + * Inode wasn't used, but marked in bitmap + */ + problem = PR_5_INODE_UNUSED; + } else /* if (actual && !bitmap) */ { + /* + * Inode used, but not in bitmap + */ + problem = PR_5_INODE_USED; + } + if (pctx.ino == 0) { + pctx.ino = pctx.ino2 = i; + save_problem = problem; + } else { + if ((problem == save_problem) && + (pctx.ino2 == i-1)) + pctx.ino2++; + else { + print_bitmap_problem(ctx, save_problem, &pctx); + pctx.ino = pctx.ino2 = i; + save_problem = problem; + } + } + ctx->flags |= E2F_FLAG_PROG_SUPPRESS; + had_problem++; + +do_counts: + if (!bitmap) { + group_free++; + free_inodes++; + } else { + if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, i)) + dirs_count++; + } + inodes++; + if ((inodes == fs->super->s_inodes_per_group) || + (i == fs->super->s_inodes_count)) { + free_array[group] = group_free; + dir_array[group] = dirs_count; + group ++; + inodes = 0; + group_free = 0; + dirs_count = 0; + if (ctx->progress) + if ((ctx->progress)(ctx, 5, + group + fs->group_desc_count, + fs->group_desc_count*2)) + return; + } + } + if (pctx.ino) + print_bitmap_problem(ctx, save_problem, &pctx); + + if (had_problem) + fixit = end_problem_latch(ctx, PR_LATCH_IBITMAP); + else + fixit = -1; + ctx->flags &= ~E2F_FLAG_PROG_SUPPRESS; + + if (fixit == 1) { + ext2fs_free_inode_bitmap(fs->inode_map); + retval = ext2fs_copy_bitmap(ctx->inode_used_map, + &fs->inode_map); + if (retval) { + clear_problem_context(&pctx); + fix_problem(ctx, PR_5_COPY_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_set_bitmap_padding(fs->inode_map); + ext2fs_mark_ib_dirty(fs); + + /* redo counts */ + inodes = 0; free_inodes = 0; group_free = 0; + dirs_count = 0; group = 0; + memset(free_array, 0, fs->group_desc_count * sizeof(int)); + memset(dir_array, 0, fs->group_desc_count * sizeof(int)); + goto redo_counts; + } else if (fixit == 0) + ext2fs_unmark_valid(fs); + + for (i = 0; i < fs->group_desc_count; i++) { + if (free_array[i] != fs->group_desc[i].bg_free_inodes_count) { + pctx.group = i; + pctx.ino = fs->group_desc[i].bg_free_inodes_count; + pctx.ino2 = free_array[i]; + if (fix_problem(ctx, PR_5_FREE_INODE_COUNT_GROUP, + &pctx)) { + fs->group_desc[i].bg_free_inodes_count = + free_array[i]; + ext2fs_mark_super_dirty(fs); + } else + ext2fs_unmark_valid(fs); + } + if (dir_array[i] != fs->group_desc[i].bg_used_dirs_count) { + pctx.group = i; + pctx.ino = fs->group_desc[i].bg_used_dirs_count; + pctx.ino2 = dir_array[i]; + + if (fix_problem(ctx, PR_5_FREE_DIR_COUNT_GROUP, + &pctx)) { + fs->group_desc[i].bg_used_dirs_count = + dir_array[i]; + ext2fs_mark_super_dirty(fs); + } else + ext2fs_unmark_valid(fs); + } + } + if (free_inodes != fs->super->s_free_inodes_count) { + pctx.group = -1; + pctx.ino = fs->super->s_free_inodes_count; + pctx.ino2 = free_inodes; + + if (fix_problem(ctx, PR_5_FREE_INODE_COUNT, &pctx)) { + fs->super->s_free_inodes_count = free_inodes; + ext2fs_mark_super_dirty(fs); + } else + ext2fs_unmark_valid(fs); + } + ext2fs_free_mem(&free_array); + ext2fs_free_mem(&dir_array); +} + +static void check_inode_end(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t end, save_inodes_count, i; + struct problem_context pctx; + + clear_problem_context(&pctx); + + end = EXT2_INODES_PER_GROUP(fs->super) * fs->group_desc_count; + pctx.errcode = ext2fs_fudge_inode_bitmap_end(fs->inode_map, end, + &save_inodes_count); + if (pctx.errcode) { + pctx.num = 1; + fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + if (save_inodes_count == end) + return; + + for (i = save_inodes_count + 1; i <= end; i++) { + if (!ext2fs_test_inode_bitmap(fs->inode_map, i)) { + if (fix_problem(ctx, PR_5_INODE_BMAP_PADDING, &pctx)) { + for (i = save_inodes_count + 1; i <= end; i++) + ext2fs_mark_inode_bitmap(fs->inode_map, + i); + ext2fs_mark_ib_dirty(fs); + } else + ext2fs_unmark_valid(fs); + break; + } + } + + pctx.errcode = ext2fs_fudge_inode_bitmap_end(fs->inode_map, + save_inodes_count, 0); + if (pctx.errcode) { + pctx.num = 2; + fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } +} + +static void check_block_end(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk_t end, save_blocks_count, i; + struct problem_context pctx; + + clear_problem_context(&pctx); + + end = fs->block_map->start + + (EXT2_BLOCKS_PER_GROUP(fs->super) * fs->group_desc_count) - 1; + pctx.errcode = ext2fs_fudge_block_bitmap_end(fs->block_map, end, + &save_blocks_count); + if (pctx.errcode) { + pctx.num = 3; + fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + if (save_blocks_count == end) + return; + + for (i = save_blocks_count + 1; i <= end; i++) { + if (!ext2fs_test_block_bitmap(fs->block_map, i)) { + if (fix_problem(ctx, PR_5_BLOCK_BMAP_PADDING, &pctx)) { + for (i = save_blocks_count + 1; i <= end; i++) + ext2fs_mark_block_bitmap(fs->block_map, + i); + ext2fs_mark_bb_dirty(fs); + } else + ext2fs_unmark_valid(fs); + break; + } + } + + pctx.errcode = ext2fs_fudge_block_bitmap_end(fs->block_map, + save_blocks_count, 0); + if (pctx.errcode) { + pctx.num = 4; + fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } +} + +static void e2fsck_pass5(e2fsck_t ctx) +{ + struct problem_context pctx; + + /* Pass 5 */ + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_5_PASS_HEADER, &pctx); + + if (ctx->progress) + if ((ctx->progress)(ctx, 5, 0, ctx->fs->group_desc_count*2)) + return; + + e2fsck_read_bitmaps(ctx); + + check_block_bitmaps(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + check_inode_bitmaps(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + check_inode_end(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + check_block_end(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + + ext2fs_free_inode_bitmap(ctx->inode_used_map); + ctx->inode_used_map = 0; + ext2fs_free_inode_bitmap(ctx->inode_dir_map); + ctx->inode_dir_map = 0; + ext2fs_free_block_bitmap(ctx->block_found_map); + ctx->block_found_map = 0; +} + +/* + * problem.c --- report filesystem problems to the user + */ + +#define PR_PREEN_OK 0x000001 /* Don't need to do preenhalt */ +#define PR_NO_OK 0x000002 /* If user answers no, don't make fs invalid */ +#define PR_NO_DEFAULT 0x000004 /* Default to no */ +#define PR_MSG_ONLY 0x000008 /* Print message only */ + +/* Bit positions 0x000ff0 are reserved for the PR_LATCH flags */ + +#define PR_FATAL 0x001000 /* Fatal error */ +#define PR_AFTER_CODE 0x002000 /* After asking the first question, */ + /* ask another */ +#define PR_PREEN_NOMSG 0x004000 /* Don't print a message if we're preening */ +#define PR_NOCOLLATE 0x008000 /* Don't collate answers for this latch */ +#define PR_NO_NOMSG 0x010000 /* Don't print a message if e2fsck -n */ +#define PR_PREEN_NO 0x020000 /* Use No as an answer if preening */ +#define PR_PREEN_NOHDR 0x040000 /* Don't print the preen header */ + + +#define PROMPT_NONE 0 +#define PROMPT_FIX 1 +#define PROMPT_CLEAR 2 +#define PROMPT_RELOCATE 3 +#define PROMPT_ALLOCATE 4 +#define PROMPT_EXPAND 5 +#define PROMPT_CONNECT 6 +#define PROMPT_CREATE 7 +#define PROMPT_SALVAGE 8 +#define PROMPT_TRUNCATE 9 +#define PROMPT_CLEAR_INODE 10 +#define PROMPT_ABORT 11 +#define PROMPT_SPLIT 12 +#define PROMPT_CONTINUE 13 +#define PROMPT_CLONE 14 +#define PROMPT_DELETE 15 +#define PROMPT_SUPPRESS 16 +#define PROMPT_UNLINK 17 +#define PROMPT_CLEAR_HTREE 18 +#define PROMPT_RECREATE 19 +#define PROMPT_NULL 20 + +struct e2fsck_problem { + problem_t e2p_code; + const char * e2p_description; + char prompt; + int flags; + problem_t second_code; +}; + +struct latch_descr { + int latch_code; + problem_t question; + problem_t end_message; + int flags; +}; + +/* + * These are the prompts which are used to ask the user if they want + * to fix a problem. + */ +static const char * const prompt[] = { + N_("(no prompt)"), /* 0 */ + N_("Fix"), /* 1 */ + N_("Clear"), /* 2 */ + N_("Relocate"), /* 3 */ + N_("Allocate"), /* 4 */ + N_("Expand"), /* 5 */ + N_("Connect to /lost+found"), /* 6 */ + N_("Create"), /* 7 */ + N_("Salvage"), /* 8 */ + N_("Truncate"), /* 9 */ + N_("Clear inode"), /* 10 */ + N_("Abort"), /* 11 */ + N_("Split"), /* 12 */ + N_("Continue"), /* 13 */ + N_("Clone multiply-claimed blocks"), /* 14 */ + N_("Delete file"), /* 15 */ + N_("Suppress messages"),/* 16 */ + N_("Unlink"), /* 17 */ + N_("Clear HTree index"),/* 18 */ + N_("Recreate"), /* 19 */ + "", /* 20 */ +}; + +/* + * These messages are printed when we are preen mode and we will be + * automatically fixing the problem. + */ +static const char * const preen_msg[] = { + N_("(NONE)"), /* 0 */ + N_("FIXED"), /* 1 */ + N_("CLEARED"), /* 2 */ + N_("RELOCATED"), /* 3 */ + N_("ALLOCATED"), /* 4 */ + N_("EXPANDED"), /* 5 */ + N_("RECONNECTED"), /* 6 */ + N_("CREATED"), /* 7 */ + N_("SALVAGED"), /* 8 */ + N_("TRUNCATED"), /* 9 */ + N_("INODE CLEARED"), /* 10 */ + N_("ABORTED"), /* 11 */ + N_("SPLIT"), /* 12 */ + N_("CONTINUING"), /* 13 */ + N_("MULTIPLY-CLAIMED BLOCKS CLONED"), /* 14 */ + N_("FILE DELETED"), /* 15 */ + N_("SUPPRESSED"), /* 16 */ + N_("UNLINKED"), /* 17 */ + N_("HTREE INDEX CLEARED"),/* 18 */ + N_("WILL RECREATE"), /* 19 */ + "", /* 20 */ +}; + +static const struct e2fsck_problem problem_table[] = { + + /* Pre-Pass 1 errors */ + + /* Block bitmap not in group */ + { PR_0_BB_NOT_GROUP, N_("@b @B for @g %g is not in @g. (@b %b)\n"), + PROMPT_RELOCATE, PR_LATCH_RELOC }, + + /* Inode bitmap not in group */ + { PR_0_IB_NOT_GROUP, N_("@i @B for @g %g is not in @g. (@b %b)\n"), + PROMPT_RELOCATE, PR_LATCH_RELOC }, + + /* Inode table not in group */ + { PR_0_ITABLE_NOT_GROUP, + N_("@i table for @g %g is not in @g. (@b %b)\n" + "WARNING: SEVERE DATA LOSS POSSIBLE.\n"), + PROMPT_RELOCATE, PR_LATCH_RELOC }, + + /* Superblock corrupt */ + { PR_0_SB_CORRUPT, + N_("\nThe @S could not be read or does not describe a correct ext2\n" + "@f. If the @v is valid and it really contains an ext2\n" + "@f (and not swap or ufs or something else), then the @S\n" + "is corrupt, and you might try running e2fsck with an alternate @S:\n" + " e2fsck -b %S <@v>\n\n"), + PROMPT_NONE, PR_FATAL }, + + /* Filesystem size is wrong */ + { PR_0_FS_SIZE_WRONG, + N_("The @f size (according to the @S) is %b @bs\n" + "The physical size of the @v is %c @bs\n" + "Either the @S or the partition table is likely to be corrupt!\n"), + PROMPT_ABORT, 0 }, + + /* Fragments not supported */ + { PR_0_NO_FRAGMENTS, + N_("@S @b_size = %b, fragsize = %c.\n" + "This version of e2fsck does not support fragment sizes different\n" + "from the @b size.\n"), + PROMPT_NONE, PR_FATAL }, + + /* Bad blocks_per_group */ + { PR_0_BLOCKS_PER_GROUP, + N_("@S @bs_per_group = %b, should have been %c\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_0_SB_CORRUPT }, + + /* Bad first_data_block */ + { PR_0_FIRST_DATA_BLOCK, + N_("@S first_data_@b = %b, should have been %c\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_0_SB_CORRUPT }, + + /* Adding UUID to filesystem */ + { PR_0_ADD_UUID, + N_("@f did not have a UUID; generating one.\n\n"), + PROMPT_NONE, 0 }, + + /* Relocate hint */ + { PR_0_RELOCATE_HINT, + N_("Note: if several inode or block bitmap blocks or part\n" + "of the inode table require relocation, you may wish to try\n" + "running e2fsck with the '-b %S' option first. The problem\n" + "may lie only with the primary block group descriptors, and\n" + "the backup block group descriptors may be OK.\n\n"), + PROMPT_NONE, PR_PREEN_OK | PR_NOCOLLATE }, + + /* Miscellaneous superblock corruption */ + { PR_0_MISC_CORRUPT_SUPER, + N_("Corruption found in @S. (%s = %N).\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_0_SB_CORRUPT }, + + /* Error determing physical device size of filesystem */ + { PR_0_GETSIZE_ERROR, + N_("Error determining size of the physical @v: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Inode count in superblock is incorrect */ + { PR_0_INODE_COUNT_WRONG, + N_("@i count in @S is %i, @s %j.\n"), + PROMPT_FIX, 0 }, + + { PR_0_HURD_CLEAR_FILETYPE, + N_("The Hurd does not support the filetype feature.\n"), + PROMPT_CLEAR, 0 }, + + /* Journal inode is invalid */ + { PR_0_JOURNAL_BAD_INODE, + N_("@S has an @n ext3 @j (@i %i).\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* The external journal has (unsupported) multiple filesystems */ + { PR_0_JOURNAL_UNSUPP_MULTIFS, + N_("External @j has multiple @f users (unsupported).\n"), + PROMPT_NONE, PR_FATAL }, + + /* Can't find external journal */ + { PR_0_CANT_FIND_JOURNAL, + N_("Can't find external @j\n"), + PROMPT_NONE, PR_FATAL }, + + /* External journal has bad superblock */ + { PR_0_EXT_JOURNAL_BAD_SUPER, + N_("External @j has bad @S\n"), + PROMPT_NONE, PR_FATAL }, + + /* Superblock has a bad journal UUID */ + { PR_0_JOURNAL_BAD_UUID, + N_("External @j does not support this @f\n"), + PROMPT_NONE, PR_FATAL }, + + /* Journal has an unknown superblock type */ + { PR_0_JOURNAL_UNSUPP_SUPER, + N_("Ext3 @j @S is unknown type %N (unsupported).\n" + "It is likely that your copy of e2fsck is old and/or doesn't " + "support this @j format.\n" + "It is also possible the @j @S is corrupt.\n"), + PROMPT_ABORT, PR_NO_OK | PR_AFTER_CODE, PR_0_JOURNAL_BAD_SUPER }, + + /* Journal superblock is corrupt */ + { PR_0_JOURNAL_BAD_SUPER, + N_("Ext3 @j @S is corrupt.\n"), + PROMPT_FIX, PR_PREEN_OK }, + + /* Superblock flag should be cleared */ + { PR_0_JOURNAL_HAS_JOURNAL, + N_("@S doesn't have has_@j flag, but has ext3 @j %s.\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Superblock flag is incorrect */ + { PR_0_JOURNAL_RECOVER_SET, + N_("@S has ext3 needs_recovery flag set, but no @j.\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Journal has data, but recovery flag is clear */ + { PR_0_JOURNAL_RECOVERY_CLEAR, + N_("ext3 recovery flag is clear, but @j has data.\n"), + PROMPT_NONE, 0 }, + + /* Ask if we should clear the journal */ + { PR_0_JOURNAL_RESET_JOURNAL, + N_("Clear @j"), + PROMPT_NULL, PR_PREEN_NOMSG }, + + /* Ask if we should run the journal anyway */ + { PR_0_JOURNAL_RUN, + N_("Run @j anyway"), + PROMPT_NULL, 0 }, + + /* Run the journal by default */ + { PR_0_JOURNAL_RUN_DEFAULT, + N_("Recovery flag not set in backup @S, so running @j anyway.\n"), + PROMPT_NONE, 0 }, + + /* Clearing orphan inode */ + { PR_0_ORPHAN_CLEAR_INODE, + N_("%s @o @i %i (uid=%Iu, gid=%Ig, mode=%Im, size=%Is)\n"), + PROMPT_NONE, 0 }, + + /* Illegal block found in orphaned inode */ + { PR_0_ORPHAN_ILLEGAL_BLOCK_NUM, + N_("@I @b #%B (%b) found in @o @i %i.\n"), + PROMPT_NONE, 0 }, + + /* Already cleared block found in orphaned inode */ + { PR_0_ORPHAN_ALREADY_CLEARED_BLOCK, + N_("Already cleared @b #%B (%b) found in @o @i %i.\n"), + PROMPT_NONE, 0 }, + + /* Illegal orphan inode in superblock */ + { PR_0_ORPHAN_ILLEGAL_HEAD_INODE, + N_("@I @o @i %i in @S.\n"), + PROMPT_NONE, 0 }, + + /* Illegal inode in orphaned inode list */ + { PR_0_ORPHAN_ILLEGAL_INODE, + N_("@I @i %i in @o @i list.\n"), + PROMPT_NONE, 0 }, + + /* Filesystem revision is 0, but feature flags are set */ + { PR_0_FS_REV_LEVEL, + N_("@f has feature flag(s) set, but is a revision 0 @f. "), + PROMPT_FIX, PR_PREEN_OK | PR_NO_OK }, + + /* Journal superblock has an unknown read-only feature flag set */ + { PR_0_JOURNAL_UNSUPP_ROCOMPAT, + N_("Ext3 @j @S has an unknown read-only feature flag set.\n"), + PROMPT_ABORT, 0 }, + + /* Journal superblock has an unknown incompatible feature flag set */ + { PR_0_JOURNAL_UNSUPP_INCOMPAT, + N_("Ext3 @j @S has an unknown incompatible feature flag set.\n"), + PROMPT_ABORT, 0 }, + + /* Journal has unsupported version number */ + { PR_0_JOURNAL_UNSUPP_VERSION, + N_("@j version not supported by this e2fsck.\n"), + PROMPT_ABORT, 0 }, + + /* Moving journal to hidden file */ + { PR_0_MOVE_JOURNAL, + N_("Moving @j from /%s to hidden @i.\n\n"), + PROMPT_NONE, 0 }, + + /* Error moving journal to hidden file */ + { PR_0_ERR_MOVE_JOURNAL, + N_("Error moving @j: %m\n\n"), + PROMPT_NONE, 0 }, + + /* Clearing V2 journal superblock */ + { PR_0_CLEAR_V2_JOURNAL, + N_("Found @n V2 @j @S fields (from V1 @j).\n" + "Clearing fields beyond the V1 @j @S...\n\n"), + PROMPT_NONE, 0 }, + + /* Backup journal inode blocks */ + { PR_0_BACKUP_JNL, + N_("Backing up @j @i @b information.\n\n"), + PROMPT_NONE, 0 }, + + /* Reserved blocks w/o resize_inode */ + { PR_0_NONZERO_RESERVED_GDT_BLOCKS, + N_("@f does not have resize_@i enabled, but s_reserved_gdt_@bs\n" + "is %N; @s zero. "), + PROMPT_FIX, 0 }, + + /* Resize_inode not enabled, but resize inode is non-zero */ + { PR_0_CLEAR_RESIZE_INODE, + N_("Resize_@i not enabled, but the resize @i is non-zero. "), + PROMPT_CLEAR, 0 }, + + /* Resize inode invalid */ + { PR_0_RESIZE_INODE_INVALID, + N_("Resize @i not valid. "), + PROMPT_RECREATE, 0 }, + + /* Pass 1 errors */ + + /* Pass 1: Checking inodes, blocks, and sizes */ + { PR_1_PASS_HEADER, + N_("Pass 1: Checking @is, @bs, and sizes\n"), + PROMPT_NONE, 0 }, + + /* Root directory is not an inode */ + { PR_1_ROOT_NO_DIR, N_("@r is not a @d. "), + PROMPT_CLEAR, 0 }, + + /* Root directory has dtime set */ + { PR_1_ROOT_DTIME, + N_("@r has dtime set (probably due to old mke2fs). "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Reserved inode has bad mode */ + { PR_1_RESERVED_BAD_MODE, + N_("Reserved @i %i (%Q) has @n mode. "), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Deleted inode has zero dtime */ + { PR_1_ZERO_DTIME, + N_("@D @i %i has zero dtime. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Inode in use, but dtime set */ + { PR_1_SET_DTIME, + N_("@i %i is in use, but has dtime set. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Zero-length directory */ + { PR_1_ZERO_LENGTH_DIR, + N_("@i %i is a @z @d. "), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Block bitmap conflicts with some other fs block */ + { PR_1_BB_CONFLICT, + N_("@g %g's @b @B at %b @C.\n"), + PROMPT_RELOCATE, 0 }, + + /* Inode bitmap conflicts with some other fs block */ + { PR_1_IB_CONFLICT, + N_("@g %g's @i @B at %b @C.\n"), + PROMPT_RELOCATE, 0 }, + + /* Inode table conflicts with some other fs block */ + { PR_1_ITABLE_CONFLICT, + N_("@g %g's @i table at %b @C.\n"), + PROMPT_RELOCATE, 0 }, + + /* Block bitmap is on a bad block */ + { PR_1_BB_BAD_BLOCK, + N_("@g %g's @b @B (%b) is bad. "), + PROMPT_RELOCATE, 0 }, + + /* Inode bitmap is on a bad block */ + { PR_1_IB_BAD_BLOCK, + N_("@g %g's @i @B (%b) is bad. "), + PROMPT_RELOCATE, 0 }, + + /* Inode has incorrect i_size */ + { PR_1_BAD_I_SIZE, + N_("@i %i, i_size is %Is, @s %N. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Inode has incorrect i_blocks */ + { PR_1_BAD_I_BLOCKS, + N_("@i %i, i_@bs is %Ib, @s %N. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Illegal blocknumber in inode */ + { PR_1_ILLEGAL_BLOCK_NUM, + N_("@I @b #%B (%b) in @i %i. "), + PROMPT_CLEAR, PR_LATCH_BLOCK }, + + /* Block number overlaps fs metadata */ + { PR_1_BLOCK_OVERLAPS_METADATA, + N_("@b #%B (%b) overlaps @f metadata in @i %i. "), + PROMPT_CLEAR, PR_LATCH_BLOCK }, + + /* Inode has illegal blocks (latch question) */ + { PR_1_INODE_BLOCK_LATCH, + N_("@i %i has illegal @b(s). "), + PROMPT_CLEAR, 0 }, + + /* Too many bad blocks in inode */ + { PR_1_TOO_MANY_BAD_BLOCKS, + N_("Too many illegal @bs in @i %i.\n"), + PROMPT_CLEAR_INODE, PR_NO_OK }, + + /* Illegal block number in bad block inode */ + { PR_1_BB_ILLEGAL_BLOCK_NUM, + N_("@I @b #%B (%b) in bad @b @i. "), + PROMPT_CLEAR, PR_LATCH_BBLOCK }, + + /* Bad block inode has illegal blocks (latch question) */ + { PR_1_INODE_BBLOCK_LATCH, + N_("Bad @b @i has illegal @b(s). "), + PROMPT_CLEAR, 0 }, + + /* Duplicate or bad blocks in use! */ + { PR_1_DUP_BLOCKS_PREENSTOP, + N_("Duplicate or bad @b in use!\n"), + PROMPT_NONE, 0 }, + + /* Bad block used as bad block indirect block */ + { PR_1_BBINODE_BAD_METABLOCK, + N_("Bad @b %b used as bad @b @i indirect @b. "), + PROMPT_CLEAR, PR_LATCH_BBLOCK }, + + /* Inconsistency can't be fixed prompt */ + { PR_1_BBINODE_BAD_METABLOCK_PROMPT, + N_("\nThe bad @b @i has probably been corrupted. You probably\n" + "should stop now and run ""e2fsck -c"" to scan for bad blocks\n" + "in the @f.\n"), + PROMPT_CONTINUE, PR_PREEN_NOMSG }, + + /* Bad primary block */ + { PR_1_BAD_PRIMARY_BLOCK, + N_("\nIf the @b is really bad, the @f cannot be fixed.\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_1_BAD_PRIMARY_BLOCK_PROMPT }, + + /* Bad primary block prompt */ + { PR_1_BAD_PRIMARY_BLOCK_PROMPT, + N_("You can remove this @b from the bad @b list and hope\n" + "that the @b is really OK. But there are no guarantees.\n\n"), + PROMPT_CLEAR, PR_PREEN_NOMSG }, + + /* Bad primary superblock */ + { PR_1_BAD_PRIMARY_SUPERBLOCK, + N_("The primary @S (%b) is on the bad @b list.\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_1_BAD_PRIMARY_BLOCK }, + + /* Bad primary block group descriptors */ + { PR_1_BAD_PRIMARY_GROUP_DESCRIPTOR, + N_("Block %b in the primary @g descriptors " + "is on the bad @b list\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_1_BAD_PRIMARY_BLOCK }, + + /* Bad superblock in group */ + { PR_1_BAD_SUPERBLOCK, + N_("Warning: Group %g's @S (%b) is bad.\n"), + PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Bad block group descriptors in group */ + { PR_1_BAD_GROUP_DESCRIPTORS, + N_("Warning: Group %g's copy of the @g descriptors has a bad " + "@b (%b).\n"), + PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Block claimed for no reason */ + { PR_1_PROGERR_CLAIMED_BLOCK, + N_("Programming error? @b #%b claimed for no reason in " + "process_bad_@b.\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Error allocating blocks for relocating metadata */ + { PR_1_RELOC_BLOCK_ALLOCATE, + N_("@A %N contiguous @b(s) in @b @g %g for %s: %m\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Error allocating block buffer during relocation process */ + { PR_1_RELOC_MEMORY_ALLOCATE, + N_("@A @b buffer for relocating %s\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Relocating metadata group information from X to Y */ + { PR_1_RELOC_FROM_TO, + N_("Relocating @g %g's %s from %b to %c...\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Relocating metatdata group information to X */ + { PR_1_RELOC_TO, + N_("Relocating @g %g's %s to %c...\n"), /* xgettext:no-c-format */ + PROMPT_NONE, PR_PREEN_OK }, + + /* Block read error during relocation process */ + { PR_1_RELOC_READ_ERR, + N_("Warning: could not read @b %b of %s: %m\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Block write error during relocation process */ + { PR_1_RELOC_WRITE_ERR, + N_("Warning: could not write @b %b for %s: %m\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Error allocating inode bitmap */ + { PR_1_ALLOCATE_IBITMAP_ERROR, + N_("@A @i @B (%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error allocating block bitmap */ + { PR_1_ALLOCATE_BBITMAP_ERROR, + N_("@A @b @B (%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error allocating icount structure */ + { PR_1_ALLOCATE_ICOUNT, + N_("@A icount link information: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error allocating dbcount */ + { PR_1_ALLOCATE_DBCOUNT, + N_("@A @d @b array: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while scanning inodes */ + { PR_1_ISCAN_ERROR, + N_("Error while scanning @is (%i): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while iterating over blocks */ + { PR_1_BLOCK_ITERATE, + N_("Error while iterating over @bs in @i %i: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while storing inode count information */ + { PR_1_ICOUNT_STORE, + N_("Error storing @i count information (@i=%i, count=%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while storing directory block information */ + { PR_1_ADD_DBLOCK, + N_("Error storing @d @b information " + "(@i=%i, @b=%b, num=%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while reading inode (for clearing) */ + { PR_1_READ_INODE, + N_("Error reading @i %i: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Suppress messages prompt */ + { PR_1_SUPPRESS_MESSAGES, "", PROMPT_SUPPRESS, PR_NO_OK }, + + /* Imagic flag set on an inode when filesystem doesn't support it */ + { PR_1_SET_IMAGIC, + N_("@i %i has imagic flag set. "), + PROMPT_CLEAR, 0 }, + + /* Immutable flag set on a device or socket inode */ + { PR_1_SET_IMMUTABLE, + N_("Special (@v/socket/fifo/symlink) file (@i %i) has immutable\n" + "or append-only flag set. "), + PROMPT_CLEAR, PR_PREEN_OK | PR_PREEN_NO | PR_NO_OK }, + + /* Compression flag set on an inode when filesystem doesn't support it */ + { PR_1_COMPR_SET, + N_("@i %i has @cion flag set on @f without @cion support. "), + PROMPT_CLEAR, 0 }, + + /* Non-zero size for device, fifo or socket inode */ + { PR_1_SET_NONZSIZE, + N_("Special (@v/socket/fifo) @i %i has non-zero size. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Filesystem revision is 0, but feature flags are set */ + { PR_1_FS_REV_LEVEL, + N_("@f has feature flag(s) set, but is a revision 0 @f. "), + PROMPT_FIX, PR_PREEN_OK | PR_NO_OK }, + + /* Journal inode is not in use, but contains data */ + { PR_1_JOURNAL_INODE_NOT_CLEAR, + N_("@j @i is not in use, but contains data. "), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Journal has bad mode */ + { PR_1_JOURNAL_BAD_MODE, + N_("@j is not regular file. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Deal with inodes that were part of orphan linked list */ + { PR_1_LOW_DTIME, + N_("@i %i was part of the @o @i list. "), + PROMPT_FIX, PR_LATCH_LOW_DTIME, 0 }, + + /* Deal with inodes that were part of corrupted orphan linked + list (latch question) */ + { PR_1_ORPHAN_LIST_REFUGEES, + N_("@is that were part of a corrupted orphan linked list found. "), + PROMPT_FIX, 0 }, + + /* Error allocating refcount structure */ + { PR_1_ALLOCATE_REFCOUNT, + N_("@A refcount structure (%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error reading extended attribute block */ + { PR_1_READ_EA_BLOCK, + N_("Error reading @a @b %b for @i %i. "), + PROMPT_CLEAR, 0 }, + + /* Invalid extended attribute block */ + { PR_1_BAD_EA_BLOCK, + N_("@i %i has a bad @a @b %b. "), + PROMPT_CLEAR, 0 }, + + /* Error reading Extended Attribute block while fixing refcount */ + { PR_1_EXTATTR_READ_ABORT, + N_("Error reading @a @b %b (%m). "), + PROMPT_ABORT, 0 }, + + /* Extended attribute reference count incorrect */ + { PR_1_EXTATTR_REFCOUNT, + N_("@a @b %b has reference count %B, @s %N. "), + PROMPT_FIX, 0 }, + + /* Error writing Extended Attribute block while fixing refcount */ + { PR_1_EXTATTR_WRITE, + N_("Error writing @a @b %b (%m). "), + PROMPT_ABORT, 0 }, + + /* Multiple EA blocks not supported */ + { PR_1_EA_MULTI_BLOCK, + N_("@a @b %b has h_@bs > 1. "), + PROMPT_CLEAR, 0}, + + /* Error allocating EA region allocation structure */ + { PR_1_EA_ALLOC_REGION, + N_("@A @a @b %b. "), + PROMPT_ABORT, 0}, + + /* Error EA allocation collision */ + { PR_1_EA_ALLOC_COLLISION, + N_("@a @b %b is corrupt (allocation collision). "), + PROMPT_CLEAR, 0}, + + /* Bad extended attribute name */ + { PR_1_EA_BAD_NAME, + N_("@a @b %b is corrupt (@n name). "), + PROMPT_CLEAR, 0}, + + /* Bad extended attribute value */ + { PR_1_EA_BAD_VALUE, + N_("@a @b %b is corrupt (@n value). "), + PROMPT_CLEAR, 0}, + + /* Inode too big (latch question) */ + { PR_1_INODE_TOOBIG, + N_("@i %i is too big. "), PROMPT_TRUNCATE, 0 }, + + /* Directory too big */ + { PR_1_TOOBIG_DIR, + N_("@b #%B (%b) causes @d to be too big. "), + PROMPT_CLEAR, PR_LATCH_TOOBIG }, + + /* Regular file too big */ + { PR_1_TOOBIG_REG, + N_("@b #%B (%b) causes file to be too big. "), + PROMPT_CLEAR, PR_LATCH_TOOBIG }, + + /* Symlink too big */ + { PR_1_TOOBIG_SYMLINK, + N_("@b #%B (%b) causes symlink to be too big. "), + PROMPT_CLEAR, PR_LATCH_TOOBIG }, + + /* INDEX_FL flag set on a non-HTREE filesystem */ + { PR_1_HTREE_SET, + N_("@i %i has INDEX_FL flag set on @f without htree support.\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* INDEX_FL flag set on a non-directory */ + { PR_1_HTREE_NODIR, + N_("@i %i has INDEX_FL flag set but is not a @d.\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Invalid root node in HTREE directory */ + { PR_1_HTREE_BADROOT, + N_("@h %i has an @n root node.\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Unsupported hash version in HTREE directory */ + { PR_1_HTREE_HASHV, + N_("@h %i has an unsupported hash version (%N)\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Incompatible flag in HTREE root node */ + { PR_1_HTREE_INCOMPAT, + N_("@h %i uses an incompatible htree root node flag.\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* HTREE too deep */ + { PR_1_HTREE_DEPTH, + N_("@h %i has a tree depth (%N) which is too big\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Bad block has indirect block that conflicts with filesystem block */ + { PR_1_BB_FS_BLOCK, + N_("Bad @b @i has an indirect @b (%b) that conflicts with\n" + "@f metadata. "), + PROMPT_CLEAR, PR_LATCH_BBLOCK }, + + /* Resize inode failed */ + { PR_1_RESIZE_INODE_CREATE, + N_("Resize @i (re)creation failed: %m."), + PROMPT_ABORT, 0 }, + + /* invalid inode->i_extra_isize */ + { PR_1_EXTRA_ISIZE, + N_("@i %i has a extra size (%IS) which is @n\n"), + PROMPT_FIX, PR_PREEN_OK }, + + /* invalid ea entry->e_name_len */ + { PR_1_ATTR_NAME_LEN, + N_("@a in @i %i has a namelen (%N) which is @n\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* invalid ea entry->e_value_size */ + { PR_1_ATTR_VALUE_SIZE, + N_("@a in @i %i has a value size (%N) which is @n\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* invalid ea entry->e_value_offs */ + { PR_1_ATTR_VALUE_OFFSET, + N_("@a in @i %i has a value offset (%N) which is @n\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* invalid ea entry->e_value_block */ + { PR_1_ATTR_VALUE_BLOCK, + N_("@a in @i %i has a value @b (%N) which is @n (must be 0)\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* invalid ea entry->e_hash */ + { PR_1_ATTR_HASH, + N_("@a in @i %i has a hash (%N) which is @n (must be 0)\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Pass 1b errors */ + + /* Pass 1B: Rescan for duplicate/bad blocks */ + { PR_1B_PASS_HEADER, + N_("\nRunning additional passes to resolve @bs claimed by more than one @i...\n" + "Pass 1B: Rescanning for @m @bs\n"), + PROMPT_NONE, 0 }, + + /* Duplicate/bad block(s) header */ + { PR_1B_DUP_BLOCK_HEADER, + N_("@m @b(s) in @i %i:"), + PROMPT_NONE, 0 }, + + /* Duplicate/bad block(s) in inode */ + { PR_1B_DUP_BLOCK, + " %b", + PROMPT_NONE, PR_LATCH_DBLOCK | PR_PREEN_NOHDR }, + + /* Duplicate/bad block(s) end */ + { PR_1B_DUP_BLOCK_END, + "\n", + PROMPT_NONE, PR_PREEN_NOHDR }, + + /* Error while scanning inodes */ + { PR_1B_ISCAN_ERROR, + N_("Error while scanning inodes (%i): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error allocating inode bitmap */ + { PR_1B_ALLOCATE_IBITMAP_ERROR, + N_("@A @i @B (@i_dup_map): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while iterating over blocks */ + { PR_1B_BLOCK_ITERATE, + N_("Error while iterating over @bs in @i %i (%s): %m\n"), + PROMPT_NONE, 0 }, + + /* Error adjusting EA refcount */ + { PR_1B_ADJ_EA_REFCOUNT, + N_("Error adjusting refcount for @a @b %b (@i %i): %m\n"), + PROMPT_NONE, 0 }, + + + /* Pass 1C: Scan directories for inodes with multiply-claimed blocks. */ + { PR_1C_PASS_HEADER, + N_("Pass 1C: Scanning directories for @is with @m @bs.\n"), + PROMPT_NONE, 0 }, + + + /* Pass 1D: Reconciling multiply-claimed blocks */ + { PR_1D_PASS_HEADER, + N_("Pass 1D: Reconciling @m @bs\n"), + PROMPT_NONE, 0 }, + + /* File has duplicate blocks */ + { PR_1D_DUP_FILE, + N_("File %Q (@i #%i, mod time %IM)\n" + " has %B @m @b(s), shared with %N file(s):\n"), + PROMPT_NONE, 0 }, + + /* List of files sharing duplicate blocks */ + { PR_1D_DUP_FILE_LIST, + N_("\t%Q (@i #%i, mod time %IM)\n"), + PROMPT_NONE, 0 }, + + /* File sharing blocks with filesystem metadata */ + { PR_1D_SHARE_METADATA, + N_("\t<@f metadata>\n"), + PROMPT_NONE, 0 }, + + /* Report of how many duplicate/bad inodes */ + { PR_1D_NUM_DUP_INODES, + N_("(There are %N @is containing @m @bs.)\n\n"), + PROMPT_NONE, 0 }, + + /* Duplicated blocks already reassigned or cloned. */ + { PR_1D_DUP_BLOCKS_DEALT, + N_("@m @bs already reassigned or cloned.\n\n"), + PROMPT_NONE, 0 }, + + /* Clone duplicate/bad blocks? */ + { PR_1D_CLONE_QUESTION, + "", PROMPT_CLONE, PR_NO_OK }, + + /* Delete file? */ + { PR_1D_DELETE_QUESTION, + "", PROMPT_DELETE, 0 }, + + /* Couldn't clone file (error) */ + { PR_1D_CLONE_ERROR, + N_("Couldn't clone file: %m\n"), PROMPT_NONE, 0 }, + + /* Pass 2 errors */ + + /* Pass 2: Checking directory structure */ + { PR_2_PASS_HEADER, + N_("Pass 2: Checking @d structure\n"), + PROMPT_NONE, 0 }, + + /* Bad inode number for '.' */ + { PR_2_BAD_INODE_DOT, + N_("@n @i number for '.' in @d @i %i.\n"), + PROMPT_FIX, 0 }, + + /* Directory entry has bad inode number */ + { PR_2_BAD_INO, + N_("@E has @n @i #: %Di.\n"), + PROMPT_CLEAR, 0 }, + + /* Directory entry has deleted or unused inode */ + { PR_2_UNUSED_INODE, + N_("@E has @D/unused @i %Di. "), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Directry entry is link to '.' */ + { PR_2_LINK_DOT, + N_("@E @L to '.' "), + PROMPT_CLEAR, 0 }, + + /* Directory entry points to inode now located in a bad block */ + { PR_2_BB_INODE, + N_("@E points to @i (%Di) located in a bad @b.\n"), + PROMPT_CLEAR, 0 }, + + /* Directory entry contains a link to a directory */ + { PR_2_LINK_DIR, + N_("@E @L to @d %P (%Di).\n"), + PROMPT_CLEAR, 0 }, + + /* Directory entry contains a link to the root directry */ + { PR_2_LINK_ROOT, + N_("@E @L to the @r.\n"), + PROMPT_CLEAR, 0 }, + + /* Directory entry has illegal characters in its name */ + { PR_2_BAD_NAME, + N_("@E has illegal characters in its name.\n"), + PROMPT_FIX, 0 }, + + /* Missing '.' in directory inode */ + { PR_2_MISSING_DOT, + N_("Missing '.' in @d @i %i.\n"), + PROMPT_FIX, 0 }, + + /* Missing '..' in directory inode */ + { PR_2_MISSING_DOT_DOT, + N_("Missing '..' in @d @i %i.\n"), + PROMPT_FIX, 0 }, + + /* First entry in directory inode doesn't contain '.' */ + { PR_2_1ST_NOT_DOT, + N_("First @e '%Dn' (@i=%Di) in @d @i %i (%p) @s '.'\n"), + PROMPT_FIX, 0 }, + + /* Second entry in directory inode doesn't contain '..' */ + { PR_2_2ND_NOT_DOT_DOT, + N_("Second @e '%Dn' (@i=%Di) in @d @i %i @s '..'\n"), + PROMPT_FIX, 0 }, + + /* i_faddr should be zero */ + { PR_2_FADDR_ZERO, + N_("i_faddr @F %IF, @s zero.\n"), + PROMPT_CLEAR, 0 }, + + /* i_file_acl should be zero */ + { PR_2_FILE_ACL_ZERO, + N_("i_file_acl @F %If, @s zero.\n"), + PROMPT_CLEAR, 0 }, + + /* i_dir_acl should be zero */ + { PR_2_DIR_ACL_ZERO, + N_("i_dir_acl @F %Id, @s zero.\n"), + PROMPT_CLEAR, 0 }, + + /* i_frag should be zero */ + { PR_2_FRAG_ZERO, + N_("i_frag @F %N, @s zero.\n"), + PROMPT_CLEAR, 0 }, + + /* i_fsize should be zero */ + { PR_2_FSIZE_ZERO, + N_("i_fsize @F %N, @s zero.\n"), + PROMPT_CLEAR, 0 }, + + /* inode has bad mode */ + { PR_2_BAD_MODE, + N_("@i %i (%Q) has @n mode (%Im).\n"), + PROMPT_CLEAR, 0 }, + + /* directory corrupted */ + { PR_2_DIR_CORRUPTED, + N_("@d @i %i, @b %B, offset %N: @d corrupted\n"), + PROMPT_SALVAGE, 0 }, + + /* filename too long */ + { PR_2_FILENAME_LONG, + N_("@d @i %i, @b %B, offset %N: filename too long\n"), + PROMPT_TRUNCATE, 0 }, + + /* Directory inode has a missing block (hole) */ + { PR_2_DIRECTORY_HOLE, + N_("@d @i %i has an unallocated @b #%B. "), + PROMPT_ALLOCATE, 0 }, + + /* '.' is not NULL terminated */ + { PR_2_DOT_NULL_TERM, + N_("'.' @d @e in @d @i %i is not NULL terminated\n"), + PROMPT_FIX, 0 }, + + /* '..' is not NULL terminated */ + { PR_2_DOT_DOT_NULL_TERM, + N_("'..' @d @e in @d @i %i is not NULL terminated\n"), + PROMPT_FIX, 0 }, + + /* Illegal character device inode */ + { PR_2_BAD_CHAR_DEV, + N_("@i %i (%Q) is an @I character @v.\n"), + PROMPT_CLEAR, 0 }, + + /* Illegal block device inode */ + { PR_2_BAD_BLOCK_DEV, + N_("@i %i (%Q) is an @I @b @v.\n"), + PROMPT_CLEAR, 0 }, + + /* Duplicate '.' entry */ + { PR_2_DUP_DOT, + N_("@E is duplicate '.' @e.\n"), + PROMPT_FIX, 0 }, + + /* Duplicate '..' entry */ + { PR_2_DUP_DOT_DOT, + N_("@E is duplicate '..' @e.\n"), + PROMPT_FIX, 0 }, + + /* Internal error: couldn't find dir_info */ + { PR_2_NO_DIRINFO, + N_("Internal error: cannot find dir_info for %i.\n"), + PROMPT_NONE, PR_FATAL }, + + /* Final rec_len is wrong */ + { PR_2_FINAL_RECLEN, + N_("@E has rec_len of %Dr, @s %N.\n"), + PROMPT_FIX, 0 }, + + /* Error allocating icount structure */ + { PR_2_ALLOCATE_ICOUNT, + N_("@A icount structure: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error iterating over directory blocks */ + { PR_2_DBLIST_ITERATE, + N_("Error iterating over @d @bs: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error reading directory block */ + { PR_2_READ_DIRBLOCK, + N_("Error reading @d @b %b (@i %i): %m\n"), + PROMPT_CONTINUE, 0 }, + + /* Error writing directory block */ + { PR_2_WRITE_DIRBLOCK, + N_("Error writing @d @b %b (@i %i): %m\n"), + PROMPT_CONTINUE, 0 }, + + /* Error allocating new directory block */ + { PR_2_ALLOC_DIRBOCK, + N_("@A new @d @b for @i %i (%s): %m\n"), + PROMPT_NONE, 0 }, + + /* Error deallocating inode */ + { PR_2_DEALLOC_INODE, + N_("Error deallocating @i %i: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Directory entry for '.' is big. Split? */ + { PR_2_SPLIT_DOT, + N_("@d @e for '.' is big. "), + PROMPT_SPLIT, PR_NO_OK }, + + /* Illegal FIFO inode */ + { PR_2_BAD_FIFO, + N_("@i %i (%Q) is an @I FIFO.\n"), + PROMPT_CLEAR, 0 }, + + /* Illegal socket inode */ + { PR_2_BAD_SOCKET, + N_("@i %i (%Q) is an @I socket.\n"), + PROMPT_CLEAR, 0 }, + + /* Directory filetype not set */ + { PR_2_SET_FILETYPE, + N_("Setting filetype for @E to %N.\n"), + PROMPT_NONE, PR_PREEN_OK | PR_NO_OK | PR_NO_NOMSG }, + + /* Directory filetype incorrect */ + { PR_2_BAD_FILETYPE, + N_("@E has an incorrect filetype (was %Dt, @s %N).\n"), + PROMPT_FIX, 0 }, + + /* Directory filetype set on filesystem */ + { PR_2_CLEAR_FILETYPE, + N_("@E has filetype set.\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Directory filename is null */ + { PR_2_NULL_NAME, + N_("@E has a @z name.\n"), + PROMPT_CLEAR, 0 }, + + /* Invalid symlink */ + { PR_2_INVALID_SYMLINK, + N_("Symlink %Q (@i #%i) is @n.\n"), + PROMPT_CLEAR, 0 }, + + /* i_file_acl (extended attribute block) is bad */ + { PR_2_FILE_ACL_BAD, + N_("@a @b @F @n (%If).\n"), + PROMPT_CLEAR, 0 }, + + /* Filesystem contains large files, but has no such flag in sb */ + { PR_2_FEATURE_LARGE_FILES, + N_("@f contains large files, but lacks LARGE_FILE flag in @S.\n"), + PROMPT_FIX, 0 }, + + /* Node in HTREE directory not referenced */ + { PR_2_HTREE_NOTREF, + N_("@p @h %d: node (%B) not referenced\n"), + PROMPT_NONE, 0 }, + + /* Node in HTREE directory referenced twice */ + { PR_2_HTREE_DUPREF, + N_("@p @h %d: node (%B) referenced twice\n"), + PROMPT_NONE, 0 }, + + /* Node in HTREE directory has bad min hash */ + { PR_2_HTREE_MIN_HASH, + N_("@p @h %d: node (%B) has bad min hash\n"), + PROMPT_NONE, 0 }, + + /* Node in HTREE directory has bad max hash */ + { PR_2_HTREE_MAX_HASH, + N_("@p @h %d: node (%B) has bad max hash\n"), + PROMPT_NONE, 0 }, + + /* Clear invalid HTREE directory */ + { PR_2_HTREE_CLEAR, + N_("@n @h %d (%q). "), PROMPT_CLEAR, 0 }, + + /* Bad block in htree interior node */ + { PR_2_HTREE_BADBLK, + N_("@p @h %d (%q): bad @b number %b.\n"), + PROMPT_CLEAR_HTREE, 0 }, + + /* Error adjusting EA refcount */ + { PR_2_ADJ_EA_REFCOUNT, + N_("Error adjusting refcount for @a @b %b (@i %i): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Invalid HTREE root node */ + { PR_2_HTREE_BAD_ROOT, + N_("@p @h %d: root node is @n\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Invalid HTREE limit */ + { PR_2_HTREE_BAD_LIMIT, + N_("@p @h %d: node (%B) has @n limit (%N)\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Invalid HTREE count */ + { PR_2_HTREE_BAD_COUNT, + N_("@p @h %d: node (%B) has @n count (%N)\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* HTREE interior node has out-of-order hashes in table */ + { PR_2_HTREE_HASH_ORDER, + N_("@p @h %d: node (%B) has an unordered hash table\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Node in HTREE directory has invalid depth */ + { PR_2_HTREE_BAD_DEPTH, + N_("@p @h %d: node (%B) has @n depth\n"), + PROMPT_NONE, 0 }, + + /* Duplicate directory entry found */ + { PR_2_DUPLICATE_DIRENT, + N_("Duplicate @E found. "), + PROMPT_CLEAR, 0 }, + + /* Non-unique filename found */ + { PR_2_NON_UNIQUE_FILE, /* xgettext: no-c-format */ + N_("@E has a non-unique filename.\nRename to %s"), + PROMPT_NULL, 0 }, + + /* Duplicate directory entry found */ + { PR_2_REPORT_DUP_DIRENT, + N_("Duplicate @e '%Dn' found.\n\tMarking %p (%i) to be rebuilt.\n\n"), + PROMPT_NONE, 0 }, + + /* Pass 3 errors */ + + /* Pass 3: Checking directory connectivity */ + { PR_3_PASS_HEADER, + N_("Pass 3: Checking @d connectivity\n"), + PROMPT_NONE, 0 }, + + /* Root inode not allocated */ + { PR_3_NO_ROOT_INODE, + N_("@r not allocated. "), + PROMPT_ALLOCATE, 0 }, + + /* No room in lost+found */ + { PR_3_EXPAND_LF_DIR, + N_("No room in @l @d. "), + PROMPT_EXPAND, 0 }, + + /* Unconnected directory inode */ + { PR_3_UNCONNECTED_DIR, + N_("Unconnected @d @i %i (%p)\n"), + PROMPT_CONNECT, 0 }, + + /* /lost+found not found */ + { PR_3_NO_LF_DIR, + N_("/@l not found. "), + PROMPT_CREATE, PR_PREEN_OK }, + + /* .. entry is incorrect */ + { PR_3_BAD_DOT_DOT, + N_("'..' in %Q (%i) is %P (%j), @s %q (%d).\n"), + PROMPT_FIX, 0 }, + + /* Bad or non-existent /lost+found. Cannot reconnect */ + { PR_3_NO_LPF, + N_("Bad or non-existent /@l. Cannot reconnect.\n"), + PROMPT_NONE, 0 }, + + /* Could not expand /lost+found */ + { PR_3_CANT_EXPAND_LPF, + N_("Could not expand /@l: %m\n"), + PROMPT_NONE, 0 }, + + /* Could not reconnect inode */ + { PR_3_CANT_RECONNECT, + N_("Could not reconnect %i: %m\n"), + PROMPT_NONE, 0 }, + + /* Error while trying to find /lost+found */ + { PR_3_ERR_FIND_LPF, + N_("Error while trying to find /@l: %m\n"), + PROMPT_NONE, 0 }, + + /* Error in ext2fs_new_block while creating /lost+found */ + { PR_3_ERR_LPF_NEW_BLOCK, + N_("ext2fs_new_@b: %m while trying to create /@l @d\n"), + PROMPT_NONE, 0 }, + + /* Error in ext2fs_new_inode while creating /lost+found */ + { PR_3_ERR_LPF_NEW_INODE, + N_("ext2fs_new_@i: %m while trying to create /@l @d\n"), + PROMPT_NONE, 0 }, + + /* Error in ext2fs_new_dir_block while creating /lost+found */ + { PR_3_ERR_LPF_NEW_DIR_BLOCK, + N_("ext2fs_new_dir_@b: %m while creating new @d @b\n"), + PROMPT_NONE, 0 }, + + /* Error while writing directory block for /lost+found */ + { PR_3_ERR_LPF_WRITE_BLOCK, + N_("ext2fs_write_dir_@b: %m while writing the @d @b for /@l\n"), + PROMPT_NONE, 0 }, + + /* Error while adjusting inode count */ + { PR_3_ADJUST_INODE, + N_("Error while adjusting @i count on @i %i\n"), + PROMPT_NONE, 0 }, + + /* Couldn't fix parent directory -- error */ + { PR_3_FIX_PARENT_ERR, + N_("Couldn't fix parent of @i %i: %m\n\n"), + PROMPT_NONE, 0 }, + + /* Couldn't fix parent directory -- couldn't find it */ + { PR_3_FIX_PARENT_NOFIND, + N_("Couldn't fix parent of @i %i: Couldn't find parent @d @e\n\n"), + PROMPT_NONE, 0 }, + + /* Error allocating inode bitmap */ + { PR_3_ALLOCATE_IBITMAP_ERROR, + N_("@A @i @B (%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error creating root directory */ + { PR_3_CREATE_ROOT_ERROR, + N_("Error creating root @d (%s): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error creating lost and found directory */ + { PR_3_CREATE_LPF_ERROR, + N_("Error creating /@l @d (%s): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Root inode is not directory; aborting */ + { PR_3_ROOT_NOT_DIR_ABORT, + N_("@r is not a @d; aborting.\n"), + PROMPT_NONE, PR_FATAL }, + + /* Cannot proceed without a root inode. */ + { PR_3_NO_ROOT_INODE_ABORT, + N_("Cannot proceed without a @r.\n"), + PROMPT_NONE, PR_FATAL }, + + /* Internal error: couldn't find dir_info */ + { PR_3_NO_DIRINFO, + N_("Internal error: cannot find dir_info for %i.\n"), + PROMPT_NONE, PR_FATAL }, + + /* Lost+found not a directory */ + { PR_3_LPF_NOTDIR, + N_("/@l is not a @d (ino=%i)\n"), + PROMPT_UNLINK, 0 }, + + /* Pass 3A Directory Optimization */ + + /* Pass 3A: Optimizing directories */ + { PR_3A_PASS_HEADER, + N_("Pass 3A: Optimizing directories\n"), + PROMPT_NONE, PR_PREEN_NOMSG }, + + /* Error iterating over directories */ + { PR_3A_OPTIMIZE_ITER, + N_("Failed to create dirs_to_hash iterator: %m"), + PROMPT_NONE, 0 }, + + /* Error rehash directory */ + { PR_3A_OPTIMIZE_DIR_ERR, + N_("Failed to optimize directory %q (%d): %m"), + PROMPT_NONE, 0 }, + + /* Rehashing dir header */ + { PR_3A_OPTIMIZE_DIR_HEADER, + N_("Optimizing directories: "), + PROMPT_NONE, PR_MSG_ONLY }, + + /* Rehashing directory %d */ + { PR_3A_OPTIMIZE_DIR, + " %d", + PROMPT_NONE, PR_LATCH_OPTIMIZE_DIR | PR_PREEN_NOHDR}, + + /* Rehashing dir end */ + { PR_3A_OPTIMIZE_DIR_END, + "\n", + PROMPT_NONE, PR_PREEN_NOHDR }, + + /* Pass 4 errors */ + + /* Pass 4: Checking reference counts */ + { PR_4_PASS_HEADER, + N_("Pass 4: Checking reference counts\n"), + PROMPT_NONE, 0 }, + + /* Unattached zero-length inode */ + { PR_4_ZERO_LEN_INODE, + N_("@u @z @i %i. "), + PROMPT_CLEAR, PR_PREEN_OK|PR_NO_OK }, + + /* Unattached inode */ + { PR_4_UNATTACHED_INODE, + N_("@u @i %i\n"), + PROMPT_CONNECT, 0 }, + + /* Inode ref count wrong */ + { PR_4_BAD_REF_COUNT, + N_("@i %i ref count is %Il, @s %N. "), + PROMPT_FIX, PR_PREEN_OK }, + + { PR_4_INCONSISTENT_COUNT, + N_("WARNING: PROGRAMMING BUG IN E2FSCK!\n" + "\tOR SOME BONEHEAD (YOU) IS CHECKING A MOUNTED (LIVE) FILESYSTEM.\n" + "@i_link_info[%i] is %N, @i.i_links_count is %Il. " + "They @s the same!\n"), + PROMPT_NONE, 0 }, + + /* Pass 5 errors */ + + /* Pass 5: Checking group summary information */ + { PR_5_PASS_HEADER, + N_("Pass 5: Checking @g summary information\n"), + PROMPT_NONE, 0 }, + + /* Padding at end of inode bitmap is not set. */ + { PR_5_INODE_BMAP_PADDING, + N_("Padding at end of @i @B is not set. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Padding at end of block bitmap is not set. */ + { PR_5_BLOCK_BMAP_PADDING, + N_("Padding at end of @b @B is not set. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Block bitmap differences header */ + { PR_5_BLOCK_BITMAP_HEADER, + N_("@b @B differences: "), + PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG}, + + /* Block not used, but marked in bitmap */ + { PR_5_BLOCK_UNUSED, + " -%b", + PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Block used, but not marked used in bitmap */ + { PR_5_BLOCK_USED, + " +%b", + PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Block bitmap differences end */ + { PR_5_BLOCK_BITMAP_END, + "\n", + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode bitmap differences header */ + { PR_5_INODE_BITMAP_HEADER, + N_("@i @B differences: "), + PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode not used, but marked in bitmap */ + { PR_5_INODE_UNUSED, + " -%i", + PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode used, but not marked used in bitmap */ + { PR_5_INODE_USED, + " +%i", + PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode bitmap differences end */ + { PR_5_INODE_BITMAP_END, + "\n", + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Free inodes count for group wrong */ + { PR_5_FREE_INODE_COUNT_GROUP, + N_("Free @is count wrong for @g #%g (%i, counted=%j).\n"), + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Directories count for group wrong */ + { PR_5_FREE_DIR_COUNT_GROUP, + N_("Directories count wrong for @g #%g (%i, counted=%j).\n"), + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Free inodes count wrong */ + { PR_5_FREE_INODE_COUNT, + N_("Free @is count wrong (%i, counted=%j).\n"), + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Free blocks count for group wrong */ + { PR_5_FREE_BLOCK_COUNT_GROUP, + N_("Free @bs count wrong for @g #%g (%b, counted=%c).\n"), + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Free blocks count wrong */ + { PR_5_FREE_BLOCK_COUNT, + N_("Free @bs count wrong (%b, counted=%c).\n"), + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Programming error: bitmap endpoints don't match */ + { PR_5_BMAP_ENDPOINTS, + N_("PROGRAMMING ERROR: @f (#%N) @B endpoints (%b, %c) don't " + "match calculated @B endpoints (%i, %j)\n"), + PROMPT_NONE, PR_FATAL }, + + /* Internal error: fudging end of bitmap */ + { PR_5_FUDGE_BITMAP_ERROR, + N_("Internal error: fudging end of bitmap (%N)\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error copying in replacement inode bitmap */ + { PR_5_COPY_IBITMAP_ERROR, + N_("Error copying in replacement @i @B: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error copying in replacement block bitmap */ + { PR_5_COPY_BBITMAP_ERROR, + N_("Error copying in replacement @b @B: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Block range not used, but marked in bitmap */ + { PR_5_BLOCK_RANGE_UNUSED, + " -(%b--%c)", + PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Block range used, but not marked used in bitmap */ + { PR_5_BLOCK_RANGE_USED, + " +(%b--%c)", + PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode range not used, but marked in bitmap */ + { PR_5_INODE_RANGE_UNUSED, + " -(%i--%j)", + PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode range used, but not marked used in bitmap */ + { PR_5_INODE_RANGE_USED, + " +(%i--%j)", + PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + { 0 } +}; + +/* + * This is the latch flags register. It allows several problems to be + * "latched" together. This means that the user has to answer but one + * question for the set of problems, and all of the associated + * problems will be either fixed or not fixed. + */ +static struct latch_descr pr_latch_info[] = { + { PR_LATCH_BLOCK, PR_1_INODE_BLOCK_LATCH, 0 }, + { PR_LATCH_BBLOCK, PR_1_INODE_BBLOCK_LATCH, 0 }, + { PR_LATCH_IBITMAP, PR_5_INODE_BITMAP_HEADER, PR_5_INODE_BITMAP_END }, + { PR_LATCH_BBITMAP, PR_5_BLOCK_BITMAP_HEADER, PR_5_BLOCK_BITMAP_END }, + { PR_LATCH_RELOC, PR_0_RELOCATE_HINT, 0 }, + { PR_LATCH_DBLOCK, PR_1B_DUP_BLOCK_HEADER, PR_1B_DUP_BLOCK_END }, + { PR_LATCH_LOW_DTIME, PR_1_ORPHAN_LIST_REFUGEES, 0 }, + { PR_LATCH_TOOBIG, PR_1_INODE_TOOBIG, 0 }, + { PR_LATCH_OPTIMIZE_DIR, PR_3A_OPTIMIZE_DIR_HEADER, PR_3A_OPTIMIZE_DIR_END }, + { -1, 0, 0 }, +}; + +static const struct e2fsck_problem *find_problem(problem_t code) +{ + int i; + + for (i=0; problem_table[i].e2p_code; i++) { + if (problem_table[i].e2p_code == code) + return &problem_table[i]; + } + return 0; +} + +static struct latch_descr *find_latch(int code) +{ + int i; + + for (i=0; pr_latch_info[i].latch_code >= 0; i++) { + if (pr_latch_info[i].latch_code == code) + return &pr_latch_info[i]; + } + return 0; +} + +int end_problem_latch(e2fsck_t ctx, int mask) +{ + struct latch_descr *ldesc; + struct problem_context pctx; + int answer = -1; + + ldesc = find_latch(mask); + if (ldesc->end_message && (ldesc->flags & PRL_LATCHED)) { + clear_problem_context(&pctx); + answer = fix_problem(ctx, ldesc->end_message, &pctx); + } + ldesc->flags &= ~(PRL_VARIABLE); + return answer; +} + +int set_latch_flags(int mask, int setflags, int clearflags) +{ + struct latch_descr *ldesc; + + ldesc = find_latch(mask); + if (!ldesc) + return -1; + ldesc->flags |= setflags; + ldesc->flags &= ~clearflags; + return 0; +} + +void clear_problem_context(struct problem_context *ctx) +{ + memset(ctx, 0, sizeof(struct problem_context)); + ctx->blkcount = -1; + ctx->group = -1; +} + +int fix_problem(e2fsck_t ctx, problem_t code, struct problem_context *pctx) +{ + ext2_filsys fs = ctx->fs; + const struct e2fsck_problem *ptr; + struct latch_descr *ldesc = 0; + const char *message; + int def_yn, answer, ans; + int print_answer = 0; + int suppress = 0; + + ptr = find_problem(code); + if (!ptr) { + printf(_("Unhandled error code (0x%x)!\n"), code); + return 0; + } + def_yn = 1; + if ((ptr->flags & PR_NO_DEFAULT) || + ((ptr->flags & PR_PREEN_NO) && (ctx->options & E2F_OPT_PREEN)) || + (ctx->options & E2F_OPT_NO)) + def_yn= 0; + + /* + * Do special latch processing. This is where we ask the + * latch question, if it exists + */ + if (ptr->flags & PR_LATCH_MASK) { + ldesc = find_latch(ptr->flags & PR_LATCH_MASK); + if (ldesc->question && !(ldesc->flags & PRL_LATCHED)) { + ans = fix_problem(ctx, ldesc->question, pctx); + if (ans == 1) + ldesc->flags |= PRL_YES; + if (ans == 0) + ldesc->flags |= PRL_NO; + ldesc->flags |= PRL_LATCHED; + } + if (ldesc->flags & PRL_SUPPRESS) + suppress++; + } + if ((ptr->flags & PR_PREEN_NOMSG) && + (ctx->options & E2F_OPT_PREEN)) + suppress++; + if ((ptr->flags & PR_NO_NOMSG) && + (ctx->options & E2F_OPT_NO)) + suppress++; + if (!suppress) { + message = ptr->e2p_description; + if ((ctx->options & E2F_OPT_PREEN) && + !(ptr->flags & PR_PREEN_NOHDR)) { + printf("%s: ", ctx->device_name ? + ctx->device_name : ctx->filesystem_name); + } + if (*message) + print_e2fsck_message(ctx, _(message), pctx, 1); + } + if (!(ptr->flags & PR_PREEN_OK) && (ptr->prompt != PROMPT_NONE)) + preenhalt(ctx); + + if (ptr->flags & PR_FATAL) + bb_error_msg_and_die(0); + + if (ptr->prompt == PROMPT_NONE) { + if (ptr->flags & PR_NOCOLLATE) + answer = -1; + else + answer = def_yn; + } else { + if (ctx->options & E2F_OPT_PREEN) { + answer = def_yn; + if (!(ptr->flags & PR_PREEN_NOMSG)) + print_answer = 1; + } else if ((ptr->flags & PR_LATCH_MASK) && + (ldesc->flags & (PRL_YES | PRL_NO))) { + if (!suppress) + print_answer = 1; + if (ldesc->flags & PRL_YES) + answer = 1; + else + answer = 0; + } else + answer = ask(ctx, _(prompt[(int) ptr->prompt]), def_yn); + if (!answer && !(ptr->flags & PR_NO_OK)) + ext2fs_unmark_valid(fs); + + if (print_answer) + printf("%s.\n", answer ? + _(preen_msg[(int) ptr->prompt]) : _("IGNORED")); + + } + + if ((ptr->prompt == PROMPT_ABORT) && answer) + bb_error_msg_and_die(0); + + if (ptr->flags & PR_AFTER_CODE) + answer = fix_problem(ctx, ptr->second_code, pctx); + + return answer; +} + +/* + * linux/fs/recovery.c + * + * Written by Stephen C. Tweedie , 1999 + */ + +/* + * Maintain information about the progress of the recovery job, so that + * the different passes can carry information between them. + */ +struct recovery_info +{ + tid_t start_transaction; + tid_t end_transaction; + + int nr_replays; + int nr_revokes; + int nr_revoke_hits; +}; + +enum passtype {PASS_SCAN, PASS_REVOKE, PASS_REPLAY}; +static int do_one_pass(journal_t *journal, + struct recovery_info *info, enum passtype pass); +static int scan_revoke_records(journal_t *, struct buffer_head *, + tid_t, struct recovery_info *); + +/* + * Read a block from the journal + */ + +static int jread(struct buffer_head **bhp, journal_t *journal, + unsigned int offset) +{ + int err; + unsigned long blocknr; + struct buffer_head *bh; + + *bhp = NULL; + + err = journal_bmap(journal, offset, &blocknr); + + if (err) { + printf("JBD: bad block at offset %u\n", offset); + return err; + } + + bh = getblk(journal->j_dev, blocknr, journal->j_blocksize); + if (!bh) + return -ENOMEM; + + if (!buffer_uptodate(bh)) { + /* If this is a brand new buffer, start readahead. + Otherwise, we assume we are already reading it. */ + if (!buffer_req(bh)) + do_readahead(journal, offset); + wait_on_buffer(bh); + } + + if (!buffer_uptodate(bh)) { + printf("JBD: Failed to read block at offset %u\n", offset); + brelse(bh); + return -EIO; + } + + *bhp = bh; + return 0; +} + + +/* + * Count the number of in-use tags in a journal descriptor block. + */ + +static int count_tags(struct buffer_head *bh, int size) +{ + char * tagp; + journal_block_tag_t * tag; + int nr = 0; + + tagp = &bh->b_data[sizeof(journal_header_t)]; + + while ((tagp - bh->b_data + sizeof(journal_block_tag_t)) <= size) { + tag = (journal_block_tag_t *) tagp; + + nr++; + tagp += sizeof(journal_block_tag_t); + if (!(tag->t_flags & htonl(JFS_FLAG_SAME_UUID))) + tagp += 16; + + if (tag->t_flags & htonl(JFS_FLAG_LAST_TAG)) + break; + } + + return nr; +} + + +/* Make sure we wrap around the log correctly! */ +#define wrap(journal, var) \ +do { \ + if (var >= (journal)->j_last) \ + var -= ((journal)->j_last - (journal)->j_first); \ +} while (0) + +/** + * int journal_recover(journal_t *journal) - recovers a on-disk journal + * @journal: the journal to recover + * + * The primary function for recovering the log contents when mounting a + * journaled device. + * + * Recovery is done in three passes. In the first pass, we look for the + * end of the log. In the second, we assemble the list of revoke + * blocks. In the third and final pass, we replay any un-revoked blocks + * in the log. + */ +int journal_recover(journal_t *journal) +{ + int err; + journal_superblock_t * sb; + + struct recovery_info info; + + memset(&info, 0, sizeof(info)); + sb = journal->j_superblock; + + /* + * The journal superblock's s_start field (the current log head) + * is always zero if, and only if, the journal was cleanly + * unmounted. + */ + + if (!sb->s_start) { + journal->j_transaction_sequence = ntohl(sb->s_sequence) + 1; + return 0; + } + + err = do_one_pass(journal, &info, PASS_SCAN); + if (!err) + err = do_one_pass(journal, &info, PASS_REVOKE); + if (!err) + err = do_one_pass(journal, &info, PASS_REPLAY); + + /* Restart the log at the next transaction ID, thus invalidating + * any existing commit records in the log. */ + journal->j_transaction_sequence = ++info.end_transaction; + + journal_clear_revoke(journal); + sync_blockdev(journal->j_fs_dev); + return err; +} + +static int do_one_pass(journal_t *journal, + struct recovery_info *info, enum passtype pass) +{ + unsigned int first_commit_ID, next_commit_ID; + unsigned long next_log_block; + int err, success = 0; + journal_superblock_t * sb; + journal_header_t * tmp; + struct buffer_head * bh; + unsigned int sequence; + int blocktype; + + /* Precompute the maximum metadata descriptors in a descriptor block */ + int MAX_BLOCKS_PER_DESC; + MAX_BLOCKS_PER_DESC = ((journal->j_blocksize-sizeof(journal_header_t)) + / sizeof(journal_block_tag_t)); + + /* + * First thing is to establish what we expect to find in the log + * (in terms of transaction IDs), and where (in terms of log + * block offsets): query the superblock. + */ + + sb = journal->j_superblock; + next_commit_ID = ntohl(sb->s_sequence); + next_log_block = ntohl(sb->s_start); + + first_commit_ID = next_commit_ID; + if (pass == PASS_SCAN) + info->start_transaction = first_commit_ID; + + /* + * Now we walk through the log, transaction by transaction, + * making sure that each transaction has a commit block in the + * expected place. Each complete transaction gets replayed back + * into the main filesystem. + */ + + while (1) { + int flags; + char * tagp; + journal_block_tag_t * tag; + struct buffer_head * obh; + struct buffer_head * nbh; + + /* If we already know where to stop the log traversal, + * check right now that we haven't gone past the end of + * the log. */ + + if (pass != PASS_SCAN) + if (tid_geq(next_commit_ID, info->end_transaction)) + break; + + /* Skip over each chunk of the transaction looking + * either the next descriptor block or the final commit + * record. */ + + err = jread(&bh, journal, next_log_block); + if (err) + goto failed; + + next_log_block++; + wrap(journal, next_log_block); + + /* What kind of buffer is it? + * + * If it is a descriptor block, check that it has the + * expected sequence number. Otherwise, we're all done + * here. */ + + tmp = (journal_header_t *)bh->b_data; + + if (tmp->h_magic != htonl(JFS_MAGIC_NUMBER)) { + brelse(bh); + break; + } + + blocktype = ntohl(tmp->h_blocktype); + sequence = ntohl(tmp->h_sequence); + + if (sequence != next_commit_ID) { + brelse(bh); + break; + } + + /* OK, we have a valid descriptor block which matches + * all of the sequence number checks. What are we going + * to do with it? That depends on the pass... */ + + switch(blocktype) { + case JFS_DESCRIPTOR_BLOCK: + /* If it is a valid descriptor block, replay it + * in pass REPLAY; otherwise, just skip over the + * blocks it describes. */ + if (pass != PASS_REPLAY) { + next_log_block += + count_tags(bh, journal->j_blocksize); + wrap(journal, next_log_block); + brelse(bh); + continue; + } + + /* A descriptor block: we can now write all of + * the data blocks. Yay, useful work is finally + * getting done here! */ + + tagp = &bh->b_data[sizeof(journal_header_t)]; + while ((tagp - bh->b_data +sizeof(journal_block_tag_t)) + <= journal->j_blocksize) { + unsigned long io_block; + + tag = (journal_block_tag_t *) tagp; + flags = ntohl(tag->t_flags); + + io_block = next_log_block++; + wrap(journal, next_log_block); + err = jread(&obh, journal, io_block); + if (err) { + /* Recover what we can, but + * report failure at the end. */ + success = err; + printf("JBD: IO error %d recovering " + "block %ld in log\n", + err, io_block); + } else { + unsigned long blocknr; + + blocknr = ntohl(tag->t_blocknr); + + /* If the block has been + * revoked, then we're all done + * here. */ + if (journal_test_revoke + (journal, blocknr, + next_commit_ID)) { + brelse(obh); + ++info->nr_revoke_hits; + goto skip_write; + } + + /* Find a buffer for the new + * data being restored */ + nbh = getblk(journal->j_fs_dev, + blocknr, + journal->j_blocksize); + if (nbh == NULL) { + printf("JBD: Out of memory " + "during recovery.\n"); + err = -ENOMEM; + brelse(bh); + brelse(obh); + goto failed; + } + + lock_buffer(nbh); + memcpy(nbh->b_data, obh->b_data, + journal->j_blocksize); + if (flags & JFS_FLAG_ESCAPE) { + *((unsigned int *)bh->b_data) = + htonl(JFS_MAGIC_NUMBER); + } + + mark_buffer_uptodate(nbh, 1); + mark_buffer_dirty(nbh); + ++info->nr_replays; + /* ll_rw_block(WRITE, 1, &nbh); */ + unlock_buffer(nbh); + brelse(obh); + brelse(nbh); + } + + skip_write: + tagp += sizeof(journal_block_tag_t); + if (!(flags & JFS_FLAG_SAME_UUID)) + tagp += 16; + + if (flags & JFS_FLAG_LAST_TAG) + break; + } + + brelse(bh); + continue; + + case JFS_COMMIT_BLOCK: + /* Found an expected commit block: not much to + * do other than move on to the next sequence + * number. */ + brelse(bh); + next_commit_ID++; + continue; + + case JFS_REVOKE_BLOCK: + /* If we aren't in the REVOKE pass, then we can + * just skip over this block. */ + if (pass != PASS_REVOKE) { + brelse(bh); + continue; + } + + err = scan_revoke_records(journal, bh, + next_commit_ID, info); + brelse(bh); + if (err) + goto failed; + continue; + + default: + goto done; + } + } + + done: + /* + * We broke out of the log scan loop: either we came to the + * known end of the log or we found an unexpected block in the + * log. If the latter happened, then we know that the "current" + * transaction marks the end of the valid log. + */ + + if (pass == PASS_SCAN) + info->end_transaction = next_commit_ID; + else { + /* It's really bad news if different passes end up at + * different places (but possible due to IO errors). */ + if (info->end_transaction != next_commit_ID) { + printf("JBD: recovery pass %d ended at " + "transaction %u, expected %u\n", + pass, next_commit_ID, info->end_transaction); + if (!success) + success = -EIO; + } + } + + return success; + + failed: + return err; +} + + +/* Scan a revoke record, marking all blocks mentioned as revoked. */ + +static int scan_revoke_records(journal_t *journal, struct buffer_head *bh, + tid_t sequence, struct recovery_info *info) +{ + journal_revoke_header_t *header; + int offset, max; + + header = (journal_revoke_header_t *) bh->b_data; + offset = sizeof(journal_revoke_header_t); + max = ntohl(header->r_count); + + while (offset < max) { + unsigned long blocknr; + int err; + + blocknr = ntohl(* ((unsigned int *) (bh->b_data+offset))); + offset += 4; + err = journal_set_revoke(journal, blocknr, sequence); + if (err) + return err; + ++info->nr_revokes; + } + return 0; +} + + +/* + * rehash.c --- rebuild hash tree directories + * + * This algorithm is designed for simplicity of implementation and to + * pack the directory as much as possible. It however requires twice + * as much memory as the size of the directory. The maximum size + * directory supported using a 4k blocksize is roughly a gigabyte, and + * so there may very well be problems with machines that don't have + * virtual memory, and obscenely large directories. + * + * An alternate algorithm which is much more disk intensive could be + * written, and probably will need to be written in the future. The + * design goals of such an algorithm are: (a) use (roughly) constant + * amounts of memory, no matter how large the directory, (b) the + * directory must be safe at all times, even if e2fsck is interrupted + * in the middle, (c) we must use minimal amounts of extra disk + * blocks. This pretty much requires an incremental approach, where + * we are reading from one part of the directory, and inserting into + * the front half. So the algorithm will have to keep track of a + * moving block boundary between the new tree and the old tree, and + * files will need to be moved from the old directory and inserted + * into the new tree. If the new directory requires space which isn't + * yet available, blocks from the beginning part of the old directory + * may need to be moved to the end of the directory to make room for + * the new tree: + * + * -------------------------------------------------------- + * | new tree | | old tree | + * -------------------------------------------------------- + * ^ ptr ^ptr + * tail new head old + * + * This is going to be a pain in the tuckus to implement, and will + * require a lot more disk accesses. So I'm going to skip it for now; + * it's only really going to be an issue for really, really big + * filesystems (when we reach the level of tens of millions of files + * in a single directory). It will probably be easier to simply + * require that e2fsck use VM first. + */ + +struct fill_dir_struct { + char *buf; + struct ext2_inode *inode; + int err; + e2fsck_t ctx; + struct hash_entry *harray; + int max_array, num_array; + int dir_size; + int compress; + ino_t parent; +}; + +struct hash_entry { + ext2_dirhash_t hash; + ext2_dirhash_t minor_hash; + struct ext2_dir_entry *dir; +}; + +struct out_dir { + int num; + int max; + char *buf; + ext2_dirhash_t *hashes; +}; + +static int fill_dir_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct fill_dir_struct *fd = (struct fill_dir_struct *) priv_data; + struct hash_entry *new_array, *ent; + struct ext2_dir_entry *dirent; + char *dir; + unsigned int offset, dir_offset; + + if (blockcnt < 0) + return 0; + + offset = blockcnt * fs->blocksize; + if (offset + fs->blocksize > fd->inode->i_size) { + fd->err = EXT2_ET_DIR_CORRUPTED; + return BLOCK_ABORT; + } + dir = (fd->buf+offset); + if (HOLE_BLKADDR(*block_nr)) { + memset(dir, 0, fs->blocksize); + dirent = (struct ext2_dir_entry *) dir; + dirent->rec_len = fs->blocksize; + } else { + fd->err = ext2fs_read_dir_block(fs, *block_nr, dir); + if (fd->err) + return BLOCK_ABORT; + } + /* While the directory block is "hot", index it. */ + dir_offset = 0; + while (dir_offset < fs->blocksize) { + dirent = (struct ext2_dir_entry *) (dir + dir_offset); + if (((dir_offset + dirent->rec_len) > fs->blocksize) || + (dirent->rec_len < 8) || + ((dirent->rec_len % 4) != 0) || + (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) { + fd->err = EXT2_ET_DIR_CORRUPTED; + return BLOCK_ABORT; + } + dir_offset += dirent->rec_len; + if (dirent->inode == 0) + continue; + if (!fd->compress && ((dirent->name_len&0xFF) == 1) && + (dirent->name[0] == '.')) + continue; + if (!fd->compress && ((dirent->name_len&0xFF) == 2) && + (dirent->name[0] == '.') && (dirent->name[1] == '.')) { + fd->parent = dirent->inode; + continue; + } + if (fd->num_array >= fd->max_array) { + new_array = realloc(fd->harray, + sizeof(struct hash_entry) * (fd->max_array+500)); + if (!new_array) { + fd->err = ENOMEM; + return BLOCK_ABORT; + } + fd->harray = new_array; + fd->max_array += 500; + } + ent = fd->harray + fd->num_array++; + ent->dir = dirent; + fd->dir_size += EXT2_DIR_REC_LEN(dirent->name_len & 0xFF); + if (fd->compress) + ent->hash = ent->minor_hash = 0; + else { + fd->err = ext2fs_dirhash(fs->super->s_def_hash_version, + dirent->name, + dirent->name_len & 0xFF, + fs->super->s_hash_seed, + &ent->hash, &ent->minor_hash); + if (fd->err) + return BLOCK_ABORT; + } + } + + return 0; +} + +/* Used for sorting the hash entry */ +static int name_cmp(const void *a, const void *b) +{ + const struct hash_entry *he_a = (const struct hash_entry *) a; + const struct hash_entry *he_b = (const struct hash_entry *) b; + int ret; + int min_len; + + min_len = he_a->dir->name_len; + if (min_len > he_b->dir->name_len) + min_len = he_b->dir->name_len; + + ret = strncmp(he_a->dir->name, he_b->dir->name, min_len); + if (ret == 0) { + if (he_a->dir->name_len > he_b->dir->name_len) + ret = 1; + else if (he_a->dir->name_len < he_b->dir->name_len) + ret = -1; + else + ret = he_b->dir->inode - he_a->dir->inode; + } + return ret; +} + +/* Used for sorting the hash entry */ +static int hash_cmp(const void *a, const void *b) +{ + const struct hash_entry *he_a = (const struct hash_entry *) a; + const struct hash_entry *he_b = (const struct hash_entry *) b; + int ret; + + if (he_a->hash > he_b->hash) + ret = 1; + else if (he_a->hash < he_b->hash) + ret = -1; + else { + if (he_a->minor_hash > he_b->minor_hash) + ret = 1; + else if (he_a->minor_hash < he_b->minor_hash) + ret = -1; + else + ret = name_cmp(a, b); + } + return ret; +} + +static errcode_t alloc_size_dir(ext2_filsys fs, struct out_dir *outdir, + int blocks) +{ + void *new_mem; + + if (outdir->max) { + new_mem = realloc(outdir->buf, blocks * fs->blocksize); + if (!new_mem) + return ENOMEM; + outdir->buf = new_mem; + new_mem = realloc(outdir->hashes, + blocks * sizeof(ext2_dirhash_t)); + if (!new_mem) + return ENOMEM; + outdir->hashes = new_mem; + } else { + outdir->buf = malloc(blocks * fs->blocksize); + outdir->hashes = malloc(blocks * sizeof(ext2_dirhash_t)); + outdir->num = 0; + } + outdir->max = blocks; + return 0; +} + +static void free_out_dir(struct out_dir *outdir) +{ + free(outdir->buf); + free(outdir->hashes); + outdir->max = 0; + outdir->num =0; +} + +static errcode_t get_next_block(ext2_filsys fs, struct out_dir *outdir, + char ** ret) +{ + errcode_t retval; + + if (outdir->num >= outdir->max) { + retval = alloc_size_dir(fs, outdir, outdir->max + 50); + if (retval) + return retval; + } + *ret = outdir->buf + (outdir->num++ * fs->blocksize); + memset(*ret, 0, fs->blocksize); + return 0; +} + +/* + * This function is used to make a unique filename. We do this by + * appending ~0, and then incrementing the number. However, we cannot + * expand the length of the filename beyond the padding available in + * the directory entry. + */ +static void mutate_name(char *str, __u16 *len) +{ + int i; + __u16 l = *len & 0xFF, h = *len & 0xff00; + + /* + * First check to see if it looks the name has been mutated + * already + */ + for (i = l-1; i > 0; i--) { + if (!isdigit(str[i])) + break; + } + if ((i == l-1) || (str[i] != '~')) { + if (((l-1) & 3) < 2) + l += 2; + else + l = (l+3) & ~3; + str[l-2] = '~'; + str[l-1] = '0'; + *len = l | h; + return; + } + for (i = l-1; i >= 0; i--) { + if (isdigit(str[i])) { + if (str[i] == '9') + str[i] = '0'; + else { + str[i]++; + return; + } + continue; + } + if (i == 1) { + if (str[0] == 'z') + str[0] = 'A'; + else if (str[0] == 'Z') { + str[0] = '~'; + str[1] = '0'; + } else + str[0]++; + } else if (i > 0) { + str[i] = '1'; + str[i-1] = '~'; + } else { + if (str[0] == '~') + str[0] = 'a'; + else + str[0]++; + } + break; + } +} + +static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs, + ext2_ino_t ino, + struct fill_dir_struct *fd) +{ + struct problem_context pctx; + struct hash_entry *ent, *prev; + int i, j; + int fixed = 0; + char new_name[256]; + __u16 new_len; + + clear_problem_context(&pctx); + pctx.ino = ino; + + for (i=1; i < fd->num_array; i++) { + ent = fd->harray + i; + prev = ent - 1; + if (!ent->dir->inode || + ((ent->dir->name_len & 0xFF) != + (prev->dir->name_len & 0xFF)) || + (strncmp(ent->dir->name, prev->dir->name, + ent->dir->name_len & 0xFF))) + continue; + pctx.dirent = ent->dir; + if ((ent->dir->inode == prev->dir->inode) && + fix_problem(ctx, PR_2_DUPLICATE_DIRENT, &pctx)) { + e2fsck_adjust_inode_count(ctx, ent->dir->inode, -1); + ent->dir->inode = 0; + fixed++; + continue; + } + memcpy(new_name, ent->dir->name, ent->dir->name_len & 0xFF); + new_len = ent->dir->name_len; + mutate_name(new_name, &new_len); + for (j=0; j < fd->num_array; j++) { + if ((i==j) || + ((ent->dir->name_len & 0xFF) != + (fd->harray[j].dir->name_len & 0xFF)) || + (strncmp(new_name, fd->harray[j].dir->name, + new_len & 0xFF))) + continue; + mutate_name(new_name, &new_len); + + j = -1; + } + new_name[new_len & 0xFF] = 0; + pctx.str = new_name; + if (fix_problem(ctx, PR_2_NON_UNIQUE_FILE, &pctx)) { + memcpy(ent->dir->name, new_name, new_len & 0xFF); + ent->dir->name_len = new_len; + ext2fs_dirhash(fs->super->s_def_hash_version, + ent->dir->name, + ent->dir->name_len & 0xFF, + fs->super->s_hash_seed, + &ent->hash, &ent->minor_hash); + fixed++; + } + } + return fixed; +} + + +static errcode_t copy_dir_entries(ext2_filsys fs, + struct fill_dir_struct *fd, + struct out_dir *outdir) +{ + errcode_t retval; + char *block_start; + struct hash_entry *ent; + struct ext2_dir_entry *dirent; + int i, rec_len, left; + ext2_dirhash_t prev_hash; + int offset; + + outdir->max = 0; + retval = alloc_size_dir(fs, outdir, + (fd->dir_size / fs->blocksize) + 2); + if (retval) + return retval; + outdir->num = fd->compress ? 0 : 1; + offset = 0; + outdir->hashes[0] = 0; + prev_hash = 1; + if ((retval = get_next_block(fs, outdir, &block_start))) + return retval; + dirent = (struct ext2_dir_entry *) block_start; + left = fs->blocksize; + for (i=0; i < fd->num_array; i++) { + ent = fd->harray + i; + if (ent->dir->inode == 0) + continue; + rec_len = EXT2_DIR_REC_LEN(ent->dir->name_len & 0xFF); + if (rec_len > left) { + if (left) + dirent->rec_len += left; + if ((retval = get_next_block(fs, outdir, + &block_start))) + return retval; + offset = 0; + } + left = fs->blocksize - offset; + dirent = (struct ext2_dir_entry *) (block_start + offset); + if (offset == 0) { + if (ent->hash == prev_hash) + outdir->hashes[outdir->num-1] = ent->hash | 1; + else + outdir->hashes[outdir->num-1] = ent->hash; + } + dirent->inode = ent->dir->inode; + dirent->name_len = ent->dir->name_len; + dirent->rec_len = rec_len; + memcpy(dirent->name, ent->dir->name, dirent->name_len & 0xFF); + offset += rec_len; + left -= rec_len; + if (left < 12) { + dirent->rec_len += left; + offset += left; + left = 0; + } + prev_hash = ent->hash; + } + if (left) + dirent->rec_len += left; + + return 0; +} + + +static struct ext2_dx_root_info *set_root_node(ext2_filsys fs, char *buf, + ext2_ino_t ino, ext2_ino_t parent) +{ + struct ext2_dir_entry *dir; + struct ext2_dx_root_info *root; + struct ext2_dx_countlimit *limits; + int filetype = 0; + + if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) + filetype = EXT2_FT_DIR << 8; + + memset(buf, 0, fs->blocksize); + dir = (struct ext2_dir_entry *) buf; + dir->inode = ino; + dir->name[0] = '.'; + dir->name_len = 1 | filetype; + dir->rec_len = 12; + dir = (struct ext2_dir_entry *) (buf + 12); + dir->inode = parent; + dir->name[0] = '.'; + dir->name[1] = '.'; + dir->name_len = 2 | filetype; + dir->rec_len = fs->blocksize - 12; + + root = (struct ext2_dx_root_info *) (buf+24); + root->reserved_zero = 0; + root->hash_version = fs->super->s_def_hash_version; + root->info_length = 8; + root->indirect_levels = 0; + root->unused_flags = 0; + + limits = (struct ext2_dx_countlimit *) (buf+32); + limits->limit = (fs->blocksize - 32) / sizeof(struct ext2_dx_entry); + limits->count = 0; + + return root; +} + + +static struct ext2_dx_entry *set_int_node(ext2_filsys fs, char *buf) +{ + struct ext2_dir_entry *dir; + struct ext2_dx_countlimit *limits; + + memset(buf, 0, fs->blocksize); + dir = (struct ext2_dir_entry *) buf; + dir->inode = 0; + dir->rec_len = fs->blocksize; + + limits = (struct ext2_dx_countlimit *) (buf+8); + limits->limit = (fs->blocksize - 8) / sizeof(struct ext2_dx_entry); + limits->count = 0; + + return (struct ext2_dx_entry *) limits; +} + +/* + * This function takes the leaf nodes which have been written in + * outdir, and populates the root node and any necessary interior nodes. + */ +static errcode_t calculate_tree(ext2_filsys fs, + struct out_dir *outdir, + ext2_ino_t ino, + ext2_ino_t parent) +{ + struct ext2_dx_root_info *root_info; + struct ext2_dx_entry *root, *dx_ent = 0; + struct ext2_dx_countlimit *root_limit, *limit; + errcode_t retval; + char * block_start; + int i, c1, c2, nblks; + int limit_offset, root_offset; + + root_info = set_root_node(fs, outdir->buf, ino, parent); + root_offset = limit_offset = ((char *) root_info - outdir->buf) + + root_info->info_length; + root_limit = (struct ext2_dx_countlimit *) (outdir->buf + limit_offset); + c1 = root_limit->limit; + nblks = outdir->num; + + /* Write out the pointer blocks */ + if (nblks-1 <= c1) { + /* Just write out the root block, and we're done */ + root = (struct ext2_dx_entry *) (outdir->buf + root_offset); + for (i=1; i < nblks; i++) { + root->block = ext2fs_cpu_to_le32(i); + if (i != 1) + root->hash = + ext2fs_cpu_to_le32(outdir->hashes[i]); + root++; + c1--; + } + } else { + c2 = 0; + limit = 0; + root_info->indirect_levels = 1; + for (i=1; i < nblks; i++) { + if (c1 == 0) + return ENOSPC; + if (c2 == 0) { + if (limit) + limit->limit = limit->count = + ext2fs_cpu_to_le16(limit->limit); + root = (struct ext2_dx_entry *) + (outdir->buf + root_offset); + root->block = ext2fs_cpu_to_le32(outdir->num); + if (i != 1) + root->hash = + ext2fs_cpu_to_le32(outdir->hashes[i]); + if ((retval = get_next_block(fs, outdir, + &block_start))) + return retval; + dx_ent = set_int_node(fs, block_start); + limit = (struct ext2_dx_countlimit *) dx_ent; + c2 = limit->limit; + root_offset += sizeof(struct ext2_dx_entry); + c1--; + } + dx_ent->block = ext2fs_cpu_to_le32(i); + if (c2 != limit->limit) + dx_ent->hash = + ext2fs_cpu_to_le32(outdir->hashes[i]); + dx_ent++; + c2--; + } + limit->count = ext2fs_cpu_to_le16(limit->limit - c2); + limit->limit = ext2fs_cpu_to_le16(limit->limit); + } + root_limit = (struct ext2_dx_countlimit *) (outdir->buf + limit_offset); + root_limit->count = ext2fs_cpu_to_le16(root_limit->limit - c1); + root_limit->limit = ext2fs_cpu_to_le16(root_limit->limit); + + return 0; +} + +struct write_dir_struct { + struct out_dir *outdir; + errcode_t err; + e2fsck_t ctx; + int cleared; +}; + +/* + * Helper function which writes out a directory block. + */ +static int write_dir_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct write_dir_struct *wd = (struct write_dir_struct *) priv_data; + blk_t blk; + char *dir; + + if (*block_nr == 0) + return 0; + if (blockcnt >= wd->outdir->num) { + e2fsck_read_bitmaps(wd->ctx); + blk = *block_nr; + ext2fs_unmark_block_bitmap(wd->ctx->block_found_map, blk); + ext2fs_block_alloc_stats(fs, blk, -1); + *block_nr = 0; + wd->cleared++; + return BLOCK_CHANGED; + } + if (blockcnt < 0) + return 0; + + dir = wd->outdir->buf + (blockcnt * fs->blocksize); + wd->err = ext2fs_write_dir_block(fs, *block_nr, dir); + if (wd->err) + return BLOCK_ABORT; + return 0; +} + +static errcode_t write_directory(e2fsck_t ctx, ext2_filsys fs, + struct out_dir *outdir, + ext2_ino_t ino, int compress) +{ + struct write_dir_struct wd; + errcode_t retval; + struct ext2_inode inode; + + retval = e2fsck_expand_directory(ctx, ino, -1, outdir->num); + if (retval) + return retval; + + wd.outdir = outdir; + wd.err = 0; + wd.ctx = ctx; + wd.cleared = 0; + + retval = ext2fs_block_iterate2(fs, ino, 0, 0, + write_dir_block, &wd); + if (retval) + return retval; + if (wd.err) + return wd.err; + + e2fsck_read_inode(ctx, ino, &inode, "rehash_dir"); + if (compress) + inode.i_flags &= ~EXT2_INDEX_FL; + else + inode.i_flags |= EXT2_INDEX_FL; + inode.i_size = outdir->num * fs->blocksize; + inode.i_blocks -= (fs->blocksize / 512) * wd.cleared; + e2fsck_write_inode(ctx, ino, &inode, "rehash_dir"); + + return 0; +} + +static errcode_t e2fsck_rehash_dir(e2fsck_t ctx, ext2_ino_t ino) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + struct ext2_inode inode; + char *dir_buf = 0; + struct fill_dir_struct fd; + struct out_dir outdir; + + outdir.max = outdir.num = 0; + outdir.buf = 0; + outdir.hashes = 0; + e2fsck_read_inode(ctx, ino, &inode, "rehash_dir"); + + retval = ENOMEM; + fd.harray = 0; + dir_buf = malloc(inode.i_size); + if (!dir_buf) + goto errout; + + fd.max_array = inode.i_size / 32; + fd.num_array = 0; + fd.harray = malloc(fd.max_array * sizeof(struct hash_entry)); + if (!fd.harray) + goto errout; + + fd.ctx = ctx; + fd.buf = dir_buf; + fd.inode = &inode; + fd.err = 0; + fd.dir_size = 0; + fd.compress = 0; + if (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) || + (inode.i_size / fs->blocksize) < 2) + fd.compress = 1; + fd.parent = 0; + + /* Read in the entire directory into memory */ + retval = ext2fs_block_iterate2(fs, ino, 0, 0, + fill_dir_block, &fd); + if (fd.err) { + retval = fd.err; + goto errout; + } + + /* Sort the list */ +resort: + if (fd.compress) + qsort(fd.harray+2, fd.num_array-2, + sizeof(struct hash_entry), name_cmp); + else + qsort(fd.harray, fd.num_array, + sizeof(struct hash_entry), hash_cmp); + + /* + * Look for duplicates + */ + if (duplicate_search_and_fix(ctx, fs, ino, &fd)) + goto resort; + + if (ctx->options & E2F_OPT_NO) { + retval = 0; + goto errout; + } + + /* + * Copy the directory entries. In a htree directory these + * will become the leaf nodes. + */ + retval = copy_dir_entries(fs, &fd, &outdir); + if (retval) + goto errout; + + free(dir_buf); dir_buf = 0; + + if (!fd.compress) { + /* Calculate the interior nodes */ + retval = calculate_tree(fs, &outdir, ino, fd.parent); + if (retval) + goto errout; + } + + retval = write_directory(ctx, fs, &outdir, ino, fd.compress); + +errout: + free(dir_buf); + free(fd.harray); + + free_out_dir(&outdir); + return retval; +} + +void e2fsck_rehash_directories(e2fsck_t ctx) +{ + struct problem_context pctx; + struct dir_info *dir; + ext2_u32_iterate iter; + ext2_ino_t ino; + errcode_t retval; + int i, cur, max, all_dirs, dir_index, first = 1; + + all_dirs = ctx->options & E2F_OPT_COMPRESS_DIRS; + + if (!ctx->dirs_to_hash && !all_dirs) + return; + + e2fsck_get_lost_and_found(ctx, 0); + + clear_problem_context(&pctx); + + dir_index = ctx->fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX; + cur = 0; + if (all_dirs) { + i = 0; + max = e2fsck_get_num_dirinfo(ctx); + } else { + retval = ext2fs_u32_list_iterate_begin(ctx->dirs_to_hash, + &iter); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3A_OPTIMIZE_ITER, &pctx); + return; + } + max = ext2fs_u32_list_count(ctx->dirs_to_hash); + } + while (1) { + if (all_dirs) { + if ((dir = e2fsck_dir_info_iter(ctx, &i)) == 0) + break; + ino = dir->ino; + } else { + if (!ext2fs_u32_list_iterate(iter, &ino)) + break; + } + if (ino == ctx->lost_and_found) + continue; + pctx.dir = ino; + if (first) { + fix_problem(ctx, PR_3A_PASS_HEADER, &pctx); + first = 0; + } + pctx.errcode = e2fsck_rehash_dir(ctx, ino); + if (pctx.errcode) { + end_problem_latch(ctx, PR_LATCH_OPTIMIZE_DIR); + fix_problem(ctx, PR_3A_OPTIMIZE_DIR_ERR, &pctx); + } + if (ctx->progress && !ctx->progress_fd) + e2fsck_simple_progress(ctx, "Rebuilding directory", + 100.0 * (float) (++cur) / (float) max, ino); + } + end_problem_latch(ctx, PR_LATCH_OPTIMIZE_DIR); + if (!all_dirs) + ext2fs_u32_list_iterate_end(iter); + + ext2fs_u32_list_free(ctx->dirs_to_hash); + ctx->dirs_to_hash = 0; +} + +/* + * linux/fs/revoke.c + * + * Journal revoke routines for the generic filesystem journaling code; + * part of the ext2fs journaling system. + * + * Revoke is the mechanism used to prevent old log records for deleted + * metadata from being replayed on top of newer data using the same + * blocks. The revoke mechanism is used in two separate places: + * + * + Commit: during commit we write the entire list of the current + * transaction's revoked blocks to the journal + * + * + Recovery: during recovery we record the transaction ID of all + * revoked blocks. If there are multiple revoke records in the log + * for a single block, only the last one counts, and if there is a log + * entry for a block beyond the last revoke, then that log entry still + * gets replayed. + * + * We can get interactions between revokes and new log data within a + * single transaction: + * + * Block is revoked and then journaled: + * The desired end result is the journaling of the new block, so we + * cancel the revoke before the transaction commits. + * + * Block is journaled and then revoked: + * The revoke must take precedence over the write of the block, so we + * need either to cancel the journal entry or to write the revoke + * later in the log than the log block. In this case, we choose the + * latter: journaling a block cancels any revoke record for that block + * in the current transaction, so any revoke for that block in the + * transaction must have happened after the block was journaled and so + * the revoke must take precedence. + * + * Block is revoked and then written as data: + * The data write is allowed to succeed, but the revoke is _not_ + * cancelled. We still need to prevent old log records from + * overwriting the new data. We don't even need to clear the revoke + * bit here. + * + * Revoke information on buffers is a tri-state value: + * + * RevokeValid clear: no cached revoke status, need to look it up + * RevokeValid set, Revoked clear: + * buffer has not been revoked, and cancel_revoke + * need do nothing. + * RevokeValid set, Revoked set: + * buffer has been revoked. + */ + +static kmem_cache_t *revoke_record_cache; +static kmem_cache_t *revoke_table_cache; + +/* Each revoke record represents one single revoked block. During + journal replay, this involves recording the transaction ID of the + last transaction to revoke this block. */ + +struct jbd_revoke_record_s +{ + struct list_head hash; + tid_t sequence; /* Used for recovery only */ + unsigned long blocknr; +}; + + +/* The revoke table is just a simple hash table of revoke records. */ +struct jbd_revoke_table_s +{ + /* It is conceivable that we might want a larger hash table + * for recovery. Must be a power of two. */ + int hash_size; + int hash_shift; + struct list_head *hash_table; +}; + + +/* Utility functions to maintain the revoke table */ + +/* Borrowed from buffer.c: this is a tried and tested block hash function */ +static int hash(journal_t *journal, unsigned long block) +{ + struct jbd_revoke_table_s *table = journal->j_revoke; + int hash_shift = table->hash_shift; + + return ((block << (hash_shift - 6)) ^ + (block >> 13) ^ + (block << (hash_shift - 12))) & (table->hash_size - 1); +} + +static int insert_revoke_hash(journal_t *journal, unsigned long blocknr, + tid_t seq) +{ + struct list_head *hash_list; + struct jbd_revoke_record_s *record; + + record = kmem_cache_alloc(revoke_record_cache, GFP_NOFS); + if (!record) + goto oom; + + record->sequence = seq; + record->blocknr = blocknr; + hash_list = &journal->j_revoke->hash_table[hash(journal, blocknr)]; + list_add(&record->hash, hash_list); + return 0; + +oom: + return -ENOMEM; +} + +/* Find a revoke record in the journal's hash table. */ + +static struct jbd_revoke_record_s *find_revoke_record(journal_t *journal, + unsigned long blocknr) +{ + struct list_head *hash_list; + struct jbd_revoke_record_s *record; + + hash_list = &journal->j_revoke->hash_table[hash(journal, blocknr)]; + + record = (struct jbd_revoke_record_s *) hash_list->next; + while (&(record->hash) != hash_list) { + if (record->blocknr == blocknr) + return record; + record = (struct jbd_revoke_record_s *) record->hash.next; + } + return NULL; +} + +int journal_init_revoke_caches(void) +{ + revoke_record_cache = do_cache_create(sizeof(struct jbd_revoke_record_s)); + if (revoke_record_cache == 0) + return -ENOMEM; + + revoke_table_cache = do_cache_create(sizeof(struct jbd_revoke_table_s)); + if (revoke_table_cache == 0) { + do_cache_destroy(revoke_record_cache); + revoke_record_cache = NULL; + return -ENOMEM; + } + return 0; +} + +void journal_destroy_revoke_caches(void) +{ + do_cache_destroy(revoke_record_cache); + revoke_record_cache = 0; + do_cache_destroy(revoke_table_cache); + revoke_table_cache = 0; +} + +/* Initialise the revoke table for a given journal to a given size. */ + +int journal_init_revoke(journal_t *journal, int hash_size) +{ + int shift, tmp; + + journal->j_revoke = kmem_cache_alloc(revoke_table_cache, GFP_KERNEL); + if (!journal->j_revoke) + return -ENOMEM; + + /* Check that the hash_size is a power of two */ + journal->j_revoke->hash_size = hash_size; + + shift = 0; + tmp = hash_size; + while((tmp >>= 1UL) != 0UL) + shift++; + journal->j_revoke->hash_shift = shift; + + journal->j_revoke->hash_table = malloc(hash_size * sizeof(struct list_head)); + if (!journal->j_revoke->hash_table) { + free(journal->j_revoke); + journal->j_revoke = NULL; + return -ENOMEM; + } + + for (tmp = 0; tmp < hash_size; tmp++) + INIT_LIST_HEAD(&journal->j_revoke->hash_table[tmp]); + + return 0; +} + +/* Destoy a journal's revoke table. The table must already be empty! */ + +void journal_destroy_revoke(journal_t *journal) +{ + struct jbd_revoke_table_s *table; + struct list_head *hash_list; + int i; + + table = journal->j_revoke; + if (!table) + return; + + for (i=0; ihash_size; i++) { + hash_list = &table->hash_table[i]; + } + + free(table->hash_table); + free(table); + journal->j_revoke = NULL; +} + +/* + * Revoke support for recovery. + * + * Recovery needs to be able to: + * + * record all revoke records, including the tid of the latest instance + * of each revoke in the journal + * + * check whether a given block in a given transaction should be replayed + * (ie. has not been revoked by a revoke record in that or a subsequent + * transaction) + * + * empty the revoke table after recovery. + */ + +/* + * First, setting revoke records. We create a new revoke record for + * every block ever revoked in the log as we scan it for recovery, and + * we update the existing records if we find multiple revokes for a + * single block. + */ + +int journal_set_revoke(journal_t *journal, unsigned long blocknr, + tid_t sequence) +{ + struct jbd_revoke_record_s *record; + + record = find_revoke_record(journal, blocknr); + if (record) { + /* If we have multiple occurences, only record the + * latest sequence number in the hashed record */ + if (tid_gt(sequence, record->sequence)) + record->sequence = sequence; + return 0; + } + return insert_revoke_hash(journal, blocknr, sequence); +} + +/* + * Test revoke records. For a given block referenced in the log, has + * that block been revoked? A revoke record with a given transaction + * sequence number revokes all blocks in that transaction and earlier + * ones, but later transactions still need replayed. + */ + +int journal_test_revoke(journal_t *journal, unsigned long blocknr, + tid_t sequence) +{ + struct jbd_revoke_record_s *record; + + record = find_revoke_record(journal, blocknr); + if (!record) + return 0; + if (tid_gt(sequence, record->sequence)) + return 0; + return 1; +} + +/* + * Finally, once recovery is over, we need to clear the revoke table so + * that it can be reused by the running filesystem. + */ + +void journal_clear_revoke(journal_t *journal) +{ + int i; + struct list_head *hash_list; + struct jbd_revoke_record_s *record; + struct jbd_revoke_table_s *revoke_var; + + revoke_var = journal->j_revoke; + + for (i = 0; i < revoke_var->hash_size; i++) { + hash_list = &revoke_var->hash_table[i]; + while (!list_empty(hash_list)) { + record = (struct jbd_revoke_record_s*) hash_list->next; + list_del(&record->hash); + free(record); + } + } +} + +/* + * e2fsck.c - superblock checks + */ + +#define MIN_CHECK 1 +#define MAX_CHECK 2 + +static void check_super_value(e2fsck_t ctx, const char *descr, + unsigned long value, int flags, + unsigned long min_val, unsigned long max_val) +{ + struct problem_context pctx; + + if (((flags & MIN_CHECK) && (value < min_val)) || + ((flags & MAX_CHECK) && (value > max_val))) { + clear_problem_context(&pctx); + pctx.num = value; + pctx.str = descr; + fix_problem(ctx, PR_0_MISC_CORRUPT_SUPER, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* never get here! */ + } +} + +/* + * This routine may get stubbed out in special compilations of the + * e2fsck code.. + */ +#ifndef EXT2_SPECIAL_DEVICE_SIZE +static errcode_t e2fsck_get_device_size(e2fsck_t ctx) +{ + return (ext2fs_get_device_size(ctx->filesystem_name, + EXT2_BLOCK_SIZE(ctx->fs->super), + &ctx->num_blocks)); +} +#endif + +/* + * helper function to release an inode + */ +struct process_block_struct { + e2fsck_t ctx; + char *buf; + struct problem_context *pctx; + int truncating; + int truncate_offset; + e2_blkcnt_t truncate_block; + int truncated_blocks; + int abort; + errcode_t errcode; +}; + +static int release_inode_block(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_blk FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct process_block_struct *pb; + e2fsck_t ctx; + struct problem_context *pctx; + blk_t blk = *block_nr; + int retval = 0; + + pb = (struct process_block_struct *) priv_data; + ctx = pb->ctx; + pctx = pb->pctx; + + pctx->blk = blk; + pctx->blkcount = blockcnt; + + if (HOLE_BLKADDR(blk)) + return 0; + + if ((blk < fs->super->s_first_data_block) || + (blk >= fs->super->s_blocks_count)) { + fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_BLOCK_NUM, pctx); + return_abort: + pb->abort = 1; + return BLOCK_ABORT; + } + + if (!ext2fs_test_block_bitmap(fs->block_map, blk)) { + fix_problem(ctx, PR_0_ORPHAN_ALREADY_CLEARED_BLOCK, pctx); + goto return_abort; + } + + /* + * If we are deleting an orphan, then we leave the fields alone. + * If we are truncating an orphan, then update the inode fields + * and clean up any partial block data. + */ + if (pb->truncating) { + /* + * We only remove indirect blocks if they are + * completely empty. + */ + if (blockcnt < 0) { + int i, limit; + blk_t *bp; + + pb->errcode = io_channel_read_blk(fs->io, blk, 1, + pb->buf); + if (pb->errcode) + goto return_abort; + + limit = fs->blocksize >> 2; + for (i = 0, bp = (blk_t *) pb->buf; + i < limit; i++, bp++) + if (*bp) + return 0; + } + /* + * We don't remove direct blocks until we've reached + * the truncation block. + */ + if (blockcnt >= 0 && blockcnt < pb->truncate_block) + return 0; + /* + * If part of the last block needs truncating, we do + * it here. + */ + if ((blockcnt == pb->truncate_block) && pb->truncate_offset) { + pb->errcode = io_channel_read_blk(fs->io, blk, 1, + pb->buf); + if (pb->errcode) + goto return_abort; + memset(pb->buf + pb->truncate_offset, 0, + fs->blocksize - pb->truncate_offset); + pb->errcode = io_channel_write_blk(fs->io, blk, 1, + pb->buf); + if (pb->errcode) + goto return_abort; + } + pb->truncated_blocks++; + *block_nr = 0; + retval |= BLOCK_CHANGED; + } + + ext2fs_block_alloc_stats(fs, blk, -1); + return retval; +} + +/* + * This function releases an inode. Returns 1 if an inconsistency was + * found. If the inode has a link count, then it is being truncated and + * not deleted. + */ +static int release_inode_blocks(e2fsck_t ctx, ext2_ino_t ino, + struct ext2_inode *inode, char *block_buf, + struct problem_context *pctx) +{ + struct process_block_struct pb; + ext2_filsys fs = ctx->fs; + errcode_t retval; + __u32 count; + + if (!ext2fs_inode_has_valid_blocks(inode)) + return 0; + + pb.buf = block_buf + 3 * ctx->fs->blocksize; + pb.ctx = ctx; + pb.abort = 0; + pb.errcode = 0; + pb.pctx = pctx; + if (inode->i_links_count) { + pb.truncating = 1; + pb.truncate_block = (e2_blkcnt_t) + ((((long long)inode->i_size_high << 32) + + inode->i_size + fs->blocksize - 1) / + fs->blocksize); + pb.truncate_offset = inode->i_size % fs->blocksize; + } else { + pb.truncating = 0; + pb.truncate_block = 0; + pb.truncate_offset = 0; + } + pb.truncated_blocks = 0; + retval = ext2fs_block_iterate2(fs, ino, BLOCK_FLAG_DEPTH_TRAVERSE, + block_buf, release_inode_block, &pb); + if (retval) { + bb_error_msg(_("while calling ext2fs_block_iterate for inode %d"), + ino); + return 1; + } + if (pb.abort) + return 1; + + /* Refresh the inode since ext2fs_block_iterate may have changed it */ + e2fsck_read_inode(ctx, ino, inode, "release_inode_blocks"); + + if (pb.truncated_blocks) + inode->i_blocks -= pb.truncated_blocks * + (fs->blocksize / 512); + + if (inode->i_file_acl) { + retval = ext2fs_adjust_ea_refcount(fs, inode->i_file_acl, + block_buf, -1, &count); + if (retval == EXT2_ET_BAD_EA_BLOCK_NUM) { + retval = 0; + count = 1; + } + if (retval) { + bb_error_msg(_("while calling ext2fs_adjust_ea_refocunt for inode %d"), + ino); + return 1; + } + if (count == 0) + ext2fs_block_alloc_stats(fs, inode->i_file_acl, -1); + inode->i_file_acl = 0; + } + return 0; +} + +/* + * This function releases all of the orphan inodes. It returns 1 if + * it hit some error, and 0 on success. + */ +static int release_orphan_inodes(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t ino, next_ino; + struct ext2_inode inode; + struct problem_context pctx; + char *block_buf; + + if ((ino = fs->super->s_last_orphan) == 0) + return 0; + + /* + * Win or lose, we won't be using the head of the orphan inode + * list again. + */ + fs->super->s_last_orphan = 0; + ext2fs_mark_super_dirty(fs); + + /* + * If the filesystem contains errors, don't run the orphan + * list, since the orphan list can't be trusted; and we're + * going to be running a full e2fsck run anyway... + */ + if (fs->super->s_state & EXT2_ERROR_FS) + return 0; + + if ((ino < EXT2_FIRST_INODE(fs->super)) || + (ino > fs->super->s_inodes_count)) { + clear_problem_context(&pctx); + pctx.ino = ino; + fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_HEAD_INODE, &pctx); + return 1; + } + + block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 4, + "block iterate buffer"); + e2fsck_read_bitmaps(ctx); + + while (ino) { + e2fsck_read_inode(ctx, ino, &inode, "release_orphan_inodes"); + clear_problem_context(&pctx); + pctx.ino = ino; + pctx.inode = &inode; + pctx.str = inode.i_links_count ? _("Truncating") : + _("Clearing"); + + fix_problem(ctx, PR_0_ORPHAN_CLEAR_INODE, &pctx); + + next_ino = inode.i_dtime; + if (next_ino && + ((next_ino < EXT2_FIRST_INODE(fs->super)) || + (next_ino > fs->super->s_inodes_count))) { + pctx.ino = next_ino; + fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_INODE, &pctx); + goto return_abort; + } + + if (release_inode_blocks(ctx, ino, &inode, block_buf, &pctx)) + goto return_abort; + + if (!inode.i_links_count) { + ext2fs_inode_alloc_stats2(fs, ino, -1, + LINUX_S_ISDIR(inode.i_mode)); + inode.i_dtime = time(0); + } else { + inode.i_dtime = 0; + } + e2fsck_write_inode(ctx, ino, &inode, "delete_file"); + ino = next_ino; + } + ext2fs_free_mem(&block_buf); + return 0; +return_abort: + ext2fs_free_mem(&block_buf); + return 1; +} + +/* + * Check the resize inode to make sure it is sane. We check both for + * the case where on-line resizing is not enabled (in which case the + * resize inode should be cleared) as well as the case where on-line + * resizing is enabled. + */ +static void check_resize_inode(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + struct ext2_inode inode; + struct problem_context pctx; + int i, j, gdt_off, ind_off; + blk_t blk, pblk, expect; + __u32 *dind_buf = 0, *ind_buf; + errcode_t retval; + + clear_problem_context(&pctx); + + /* + * If the resize inode feature isn't set, then + * s_reserved_gdt_blocks must be zero. + */ + if (!(fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_RESIZE_INODE)) { + if (fs->super->s_reserved_gdt_blocks) { + pctx.num = fs->super->s_reserved_gdt_blocks; + if (fix_problem(ctx, PR_0_NONZERO_RESERVED_GDT_BLOCKS, + &pctx)) { + fs->super->s_reserved_gdt_blocks = 0; + ext2fs_mark_super_dirty(fs); + } + } + } + + /* Read the resize inode */ + pctx.ino = EXT2_RESIZE_INO; + retval = ext2fs_read_inode(fs, EXT2_RESIZE_INO, &inode); + if (retval) { + if (fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_RESIZE_INODE) + ctx->flags |= E2F_FLAG_RESIZE_INODE; + return; + } + + /* + * If the resize inode feature isn't set, check to make sure + * the resize inode is cleared; then we're done. + */ + if (!(fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_RESIZE_INODE)) { + for (i=0; i < EXT2_N_BLOCKS; i++) { + if (inode.i_block[i]) + break; + } + if ((i < EXT2_N_BLOCKS) && + fix_problem(ctx, PR_0_CLEAR_RESIZE_INODE, &pctx)) { + memset(&inode, 0, sizeof(inode)); + e2fsck_write_inode(ctx, EXT2_RESIZE_INO, &inode, + "clear_resize"); + } + return; + } + + /* + * The resize inode feature is enabled; check to make sure the + * only block in use is the double indirect block + */ + blk = inode.i_block[EXT2_DIND_BLOCK]; + for (i=0; i < EXT2_N_BLOCKS; i++) { + if (i != EXT2_DIND_BLOCK && inode.i_block[i]) + break; + } + if ((i < EXT2_N_BLOCKS) || !blk || !inode.i_links_count || + !(inode.i_mode & LINUX_S_IFREG) || + (blk < fs->super->s_first_data_block || + blk >= fs->super->s_blocks_count)) { + resize_inode_invalid: + if (fix_problem(ctx, PR_0_RESIZE_INODE_INVALID, &pctx)) { + memset(&inode, 0, sizeof(inode)); + e2fsck_write_inode(ctx, EXT2_RESIZE_INO, &inode, + "clear_resize"); + ctx->flags |= E2F_FLAG_RESIZE_INODE; + } + if (!(ctx->options & E2F_OPT_READONLY)) { + fs->super->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(fs); + } + goto cleanup; + } + dind_buf = (__u32 *) e2fsck_allocate_memory(ctx, fs->blocksize * 2, + "resize dind buffer"); + ind_buf = (__u32 *) ((char *) dind_buf + fs->blocksize); + + retval = ext2fs_read_ind_block(fs, blk, dind_buf); + if (retval) + goto resize_inode_invalid; + + gdt_off = fs->desc_blocks; + pblk = fs->super->s_first_data_block + 1 + fs->desc_blocks; + for (i = 0; i < fs->super->s_reserved_gdt_blocks / 4; + i++, gdt_off++, pblk++) { + gdt_off %= fs->blocksize/4; + if (dind_buf[gdt_off] != pblk) + goto resize_inode_invalid; + retval = ext2fs_read_ind_block(fs, pblk, ind_buf); + if (retval) + goto resize_inode_invalid; + ind_off = 0; + for (j = 1; j < fs->group_desc_count; j++) { + if (!ext2fs_bg_has_super(fs, j)) + continue; + expect = pblk + (j * fs->super->s_blocks_per_group); + if (ind_buf[ind_off] != expect) + goto resize_inode_invalid; + ind_off++; + } + } + +cleanup: + ext2fs_free_mem(&dind_buf); + + } + +static void check_super_block(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk_t first_block, last_block; + struct ext2_super_block *sb = fs->super; + struct ext2_group_desc *gd; + blk_t blocks_per_group = fs->super->s_blocks_per_group; + blk_t bpg_max; + int inodes_per_block; + int ipg_max; + int inode_size; + dgrp_t i; + blk_t should_be; + struct problem_context pctx; + __u32 free_blocks = 0, free_inodes = 0; + + inodes_per_block = EXT2_INODES_PER_BLOCK(fs->super); + ipg_max = inodes_per_block * (blocks_per_group - 4); + if (ipg_max > EXT2_MAX_INODES_PER_GROUP(sb)) + ipg_max = EXT2_MAX_INODES_PER_GROUP(sb); + bpg_max = 8 * EXT2_BLOCK_SIZE(sb); + if (bpg_max > EXT2_MAX_BLOCKS_PER_GROUP(sb)) + bpg_max = EXT2_MAX_BLOCKS_PER_GROUP(sb); + + ctx->invalid_inode_bitmap_flag = (int *) e2fsck_allocate_memory(ctx, + sizeof(int) * fs->group_desc_count, "invalid_inode_bitmap"); + ctx->invalid_block_bitmap_flag = (int *) e2fsck_allocate_memory(ctx, + sizeof(int) * fs->group_desc_count, "invalid_block_bitmap"); + ctx->invalid_inode_table_flag = (int *) e2fsck_allocate_memory(ctx, + sizeof(int) * fs->group_desc_count, "invalid_inode_table"); + + clear_problem_context(&pctx); + + /* + * Verify the super block constants... + */ + check_super_value(ctx, "inodes_count", sb->s_inodes_count, + MIN_CHECK, 1, 0); + check_super_value(ctx, "blocks_count", sb->s_blocks_count, + MIN_CHECK, 1, 0); + check_super_value(ctx, "first_data_block", sb->s_first_data_block, + MAX_CHECK, 0, sb->s_blocks_count); + check_super_value(ctx, "log_block_size", sb->s_log_block_size, + MIN_CHECK | MAX_CHECK, 0, + EXT2_MAX_BLOCK_LOG_SIZE - EXT2_MIN_BLOCK_LOG_SIZE); + check_super_value(ctx, "log_frag_size", sb->s_log_frag_size, + MIN_CHECK | MAX_CHECK, 0, sb->s_log_block_size); + check_super_value(ctx, "frags_per_group", sb->s_frags_per_group, + MIN_CHECK | MAX_CHECK, sb->s_blocks_per_group, + bpg_max); + check_super_value(ctx, "blocks_per_group", sb->s_blocks_per_group, + MIN_CHECK | MAX_CHECK, 8, bpg_max); + check_super_value(ctx, "inodes_per_group", sb->s_inodes_per_group, + MIN_CHECK | MAX_CHECK, inodes_per_block, ipg_max); + check_super_value(ctx, "r_blocks_count", sb->s_r_blocks_count, + MAX_CHECK, 0, sb->s_blocks_count / 2); + check_super_value(ctx, "reserved_gdt_blocks", + sb->s_reserved_gdt_blocks, MAX_CHECK, 0, + fs->blocksize/4); + inode_size = EXT2_INODE_SIZE(sb); + check_super_value(ctx, "inode_size", + inode_size, MIN_CHECK | MAX_CHECK, + EXT2_GOOD_OLD_INODE_SIZE, fs->blocksize); + if (inode_size & (inode_size - 1)) { + pctx.num = inode_size; + pctx.str = "inode_size"; + fix_problem(ctx, PR_0_MISC_CORRUPT_SUPER, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* never get here! */ + return; + } + + if (!ctx->num_blocks) { + pctx.errcode = e2fsck_get_device_size(ctx); + if (pctx.errcode && pctx.errcode != EXT2_ET_UNIMPLEMENTED) { + fix_problem(ctx, PR_0_GETSIZE_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if ((pctx.errcode != EXT2_ET_UNIMPLEMENTED) && + (ctx->num_blocks < sb->s_blocks_count)) { + pctx.blk = sb->s_blocks_count; + pctx.blk2 = ctx->num_blocks; + if (fix_problem(ctx, PR_0_FS_SIZE_WRONG, &pctx)) { + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + } + + if (sb->s_log_block_size != (__u32) sb->s_log_frag_size) { + pctx.blk = EXT2_BLOCK_SIZE(sb); + pctx.blk2 = EXT2_FRAG_SIZE(sb); + fix_problem(ctx, PR_0_NO_FRAGMENTS, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + should_be = sb->s_frags_per_group >> + (sb->s_log_block_size - sb->s_log_frag_size); + if (sb->s_blocks_per_group != should_be) { + pctx.blk = sb->s_blocks_per_group; + pctx.blk2 = should_be; + fix_problem(ctx, PR_0_BLOCKS_PER_GROUP, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + should_be = (sb->s_log_block_size == 0) ? 1 : 0; + if (sb->s_first_data_block != should_be) { + pctx.blk = sb->s_first_data_block; + pctx.blk2 = should_be; + fix_problem(ctx, PR_0_FIRST_DATA_BLOCK, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + should_be = sb->s_inodes_per_group * fs->group_desc_count; + if (sb->s_inodes_count != should_be) { + pctx.ino = sb->s_inodes_count; + pctx.ino2 = should_be; + if (fix_problem(ctx, PR_0_INODE_COUNT_WRONG, &pctx)) { + sb->s_inodes_count = should_be; + ext2fs_mark_super_dirty(fs); + } + } + + /* + * Verify the group descriptors.... + */ + first_block = sb->s_first_data_block; + last_block = first_block + blocks_per_group; + + for (i = 0, gd=fs->group_desc; i < fs->group_desc_count; i++, gd++) { + pctx.group = i; + + if (i == fs->group_desc_count - 1) + last_block = sb->s_blocks_count; + if ((gd->bg_block_bitmap < first_block) || + (gd->bg_block_bitmap >= last_block)) { + pctx.blk = gd->bg_block_bitmap; + if (fix_problem(ctx, PR_0_BB_NOT_GROUP, &pctx)) + gd->bg_block_bitmap = 0; + } + if (gd->bg_block_bitmap == 0) { + ctx->invalid_block_bitmap_flag[i]++; + ctx->invalid_bitmaps++; + } + if ((gd->bg_inode_bitmap < first_block) || + (gd->bg_inode_bitmap >= last_block)) { + pctx.blk = gd->bg_inode_bitmap; + if (fix_problem(ctx, PR_0_IB_NOT_GROUP, &pctx)) + gd->bg_inode_bitmap = 0; + } + if (gd->bg_inode_bitmap == 0) { + ctx->invalid_inode_bitmap_flag[i]++; + ctx->invalid_bitmaps++; + } + if ((gd->bg_inode_table < first_block) || + ((gd->bg_inode_table + + fs->inode_blocks_per_group - 1) >= last_block)) { + pctx.blk = gd->bg_inode_table; + if (fix_problem(ctx, PR_0_ITABLE_NOT_GROUP, &pctx)) + gd->bg_inode_table = 0; + } + if (gd->bg_inode_table == 0) { + ctx->invalid_inode_table_flag[i]++; + ctx->invalid_bitmaps++; + } + free_blocks += gd->bg_free_blocks_count; + free_inodes += gd->bg_free_inodes_count; + first_block += sb->s_blocks_per_group; + last_block += sb->s_blocks_per_group; + + if ((gd->bg_free_blocks_count > sb->s_blocks_per_group) || + (gd->bg_free_inodes_count > sb->s_inodes_per_group) || + (gd->bg_used_dirs_count > sb->s_inodes_per_group)) + ext2fs_unmark_valid(fs); + + } + + /* + * Update the global counts from the block group counts. This + * is needed for an experimental patch which eliminates + * locking the entire filesystem when allocating blocks or + * inodes; if the filesystem is not unmounted cleanly, the + * global counts may not be accurate. + */ + if ((free_blocks != sb->s_free_blocks_count) || + (free_inodes != sb->s_free_inodes_count)) { + if (ctx->options & E2F_OPT_READONLY) + ext2fs_unmark_valid(fs); + else { + sb->s_free_blocks_count = free_blocks; + sb->s_free_inodes_count = free_inodes; + ext2fs_mark_super_dirty(fs); + } + } + + if ((sb->s_free_blocks_count > sb->s_blocks_count) || + (sb->s_free_inodes_count > sb->s_inodes_count)) + ext2fs_unmark_valid(fs); + + + /* + * If we have invalid bitmaps, set the error state of the + * filesystem. + */ + if (ctx->invalid_bitmaps && !(ctx->options & E2F_OPT_READONLY)) { + sb->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(fs); + } + + clear_problem_context(&pctx); + + /* + * If the UUID field isn't assigned, assign it. + */ + if (!(ctx->options & E2F_OPT_READONLY) && uuid_is_null(sb->s_uuid)) { + if (fix_problem(ctx, PR_0_ADD_UUID, &pctx)) { + uuid_generate(sb->s_uuid); + ext2fs_mark_super_dirty(fs); + fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; + } + } + + /* FIXME - HURD support? + * For the Hurd, check to see if the filetype option is set, + * since it doesn't support it. + */ + if (!(ctx->options & E2F_OPT_READONLY) && + fs->super->s_creator_os == EXT2_OS_HURD && + (fs->super->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_FILETYPE)) { + if (fix_problem(ctx, PR_0_HURD_CLEAR_FILETYPE, &pctx)) { + fs->super->s_feature_incompat &= + ~EXT2_FEATURE_INCOMPAT_FILETYPE; + ext2fs_mark_super_dirty(fs); + + } + } + + /* + * If we have any of the compatibility flags set, we need to have a + * revision 1 filesystem. Most kernels will not check the flags on + * a rev 0 filesystem and we may have corruption issues because of + * the incompatible changes to the filesystem. + */ + if (!(ctx->options & E2F_OPT_READONLY) && + fs->super->s_rev_level == EXT2_GOOD_OLD_REV && + (fs->super->s_feature_compat || + fs->super->s_feature_ro_compat || + fs->super->s_feature_incompat) && + fix_problem(ctx, PR_0_FS_REV_LEVEL, &pctx)) { + ext2fs_update_dynamic_rev(fs); + ext2fs_mark_super_dirty(fs); + } + + check_resize_inode(ctx); + + /* + * Clean up any orphan inodes, if present. + */ + if (!(ctx->options & E2F_OPT_READONLY) && release_orphan_inodes(ctx)) { + fs->super->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(fs); + } + + /* + * Move the ext3 journal file, if necessary. + */ + e2fsck_move_ext3_journal(ctx); + return; +} + +/* + * swapfs.c --- byte-swap an ext2 filesystem + */ + +#ifdef ENABLE_SWAPFS + +struct swap_block_struct { + ext2_ino_t ino; + int isdir; + errcode_t errcode; + char *dir_buf; + struct ext2_inode *inode; +}; + +/* + * This is a helper function for block_iterate. We mark all of the + * indirect and direct blocks as changed, so that block_iterate will + * write them out. + */ +static int swap_block(ext2_filsys fs, blk_t *block_nr, int blockcnt, + void *priv_data) +{ + errcode_t retval; + + struct swap_block_struct *sb = (struct swap_block_struct *) priv_data; + + if (sb->isdir && (blockcnt >= 0) && *block_nr) { + retval = ext2fs_read_dir_block(fs, *block_nr, sb->dir_buf); + if (retval) { + sb->errcode = retval; + return BLOCK_ABORT; + } + retval = ext2fs_write_dir_block(fs, *block_nr, sb->dir_buf); + if (retval) { + sb->errcode = retval; + return BLOCK_ABORT; + } + } + if (blockcnt >= 0) { + if (blockcnt < EXT2_NDIR_BLOCKS) + return 0; + return BLOCK_CHANGED; + } + if (blockcnt == BLOCK_COUNT_IND) { + if (*block_nr == sb->inode->i_block[EXT2_IND_BLOCK]) + return 0; + return BLOCK_CHANGED; + } + if (blockcnt == BLOCK_COUNT_DIND) { + if (*block_nr == sb->inode->i_block[EXT2_DIND_BLOCK]) + return 0; + return BLOCK_CHANGED; + } + if (blockcnt == BLOCK_COUNT_TIND) { + if (*block_nr == sb->inode->i_block[EXT2_TIND_BLOCK]) + return 0; + return BLOCK_CHANGED; + } + return BLOCK_CHANGED; +} + +/* + * This function is responsible for byte-swapping all of the indirect, + * block pointers. It is also responsible for byte-swapping directories. + */ +static void swap_inode_blocks(e2fsck_t ctx, ext2_ino_t ino, char *block_buf, + struct ext2_inode *inode) +{ + errcode_t retval; + struct swap_block_struct sb; + + sb.ino = ino; + sb.inode = inode; + sb.dir_buf = block_buf + ctx->fs->blocksize*3; + sb.errcode = 0; + sb.isdir = 0; + if (LINUX_S_ISDIR(inode->i_mode)) + sb.isdir = 1; + + retval = ext2fs_block_iterate(ctx->fs, ino, 0, block_buf, + swap_block, &sb); + if (retval) { + bb_error_msg(_("while calling ext2fs_block_iterate")); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (sb.errcode) { + bb_error_msg(_("while calling iterator function")); + ctx->flags |= E2F_FLAG_ABORT; + return; + } +} + +static void swap_inodes(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + dgrp_t group; + unsigned int i; + ext2_ino_t ino = 1; + char *buf, *block_buf; + errcode_t retval; + struct ext2_inode * inode; + + e2fsck_use_inode_shortcuts(ctx, 1); + + retval = ext2fs_get_mem(fs->blocksize * fs->inode_blocks_per_group, + &buf); + if (retval) { + bb_error_msg(_("while allocating inode buffer")); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 4, + "block interate buffer"); + for (group = 0; group < fs->group_desc_count; group++) { + retval = io_channel_read_blk(fs->io, + fs->group_desc[group].bg_inode_table, + fs->inode_blocks_per_group, buf); + if (retval) { + bb_error_msg(_("while reading inode table (group %d)"), + group); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + inode = (struct ext2_inode *) buf; + for (i=0; i < fs->super->s_inodes_per_group; + i++, ino++, inode++) { + ctx->stashed_ino = ino; + ctx->stashed_inode = inode; + + if (fs->flags & EXT2_FLAG_SWAP_BYTES_READ) + ext2fs_swap_inode(fs, inode, inode, 0); + + /* + * Skip deleted files. + */ + if (inode->i_links_count == 0) + continue; + + if (LINUX_S_ISDIR(inode->i_mode) || + ((inode->i_block[EXT2_IND_BLOCK] || + inode->i_block[EXT2_DIND_BLOCK] || + inode->i_block[EXT2_TIND_BLOCK]) && + ext2fs_inode_has_valid_blocks(inode))) + swap_inode_blocks(ctx, ino, block_buf, inode); + + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + + if (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE) + ext2fs_swap_inode(fs, inode, inode, 1); + } + retval = io_channel_write_blk(fs->io, + fs->group_desc[group].bg_inode_table, + fs->inode_blocks_per_group, buf); + if (retval) { + bb_error_msg(_("while writing inode table (group %d)"), + group); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + ext2fs_free_mem(&buf); + ext2fs_free_mem(&block_buf); + e2fsck_use_inode_shortcuts(ctx, 0); + ext2fs_flush_icache(fs); +} + +#if defined(__powerpc__) && BB_BIG_ENDIAN +/* + * On the PowerPC, the big-endian variant of the ext2 filesystem + * has its bitmaps stored as 32-bit words with bit 0 as the LSB + * of each word. Thus a bitmap with only bit 0 set would be, as + * a string of bytes, 00 00 00 01 00 ... + * To cope with this, we byte-reverse each word of a bitmap if + * we have a big-endian filesystem, that is, if we are *not* + * byte-swapping other word-sized numbers. + */ +#define EXT2_BIG_ENDIAN_BITMAPS +#endif + +#ifdef EXT2_BIG_ENDIAN_BITMAPS +static void ext2fs_swap_bitmap(ext2fs_generic_bitmap bmap) +{ + __u32 *p = (__u32 *) bmap->bitmap; + int n, nbytes = (bmap->end - bmap->start + 7) / 8; + + for (n = nbytes / sizeof(__u32); n > 0; --n, ++p) + *p = ext2fs_swab32(*p); +} +#endif + + +#ifdef ENABLE_SWAPFS +static void swap_filesys(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + if (!(ctx->options & E2F_OPT_PREEN)) + printf(_("Pass 0: Doing byte-swap of filesystem\n")); + + /* Byte swap */ + + if (fs->super->s_mnt_count) { + fprintf(stderr, _("%s: the filesystem must be freshly " + "checked using fsck\n" + "and not mounted before trying to " + "byte-swap it.\n"), ctx->device_name); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + fs->flags &= ~(EXT2_FLAG_SWAP_BYTES| + EXT2_FLAG_SWAP_BYTES_WRITE); + fs->flags |= EXT2_FLAG_SWAP_BYTES_READ; + } else { + fs->flags &= ~EXT2_FLAG_SWAP_BYTES_READ; + fs->flags |= EXT2_FLAG_SWAP_BYTES_WRITE; + } + swap_inodes(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + if (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE) + fs->flags |= EXT2_FLAG_SWAP_BYTES; + fs->flags &= ~(EXT2_FLAG_SWAP_BYTES_READ| + EXT2_FLAG_SWAP_BYTES_WRITE); + +#ifdef EXT2_BIG_ENDIAN_BITMAPS + e2fsck_read_bitmaps(ctx); + ext2fs_swap_bitmap(fs->inode_map); + ext2fs_swap_bitmap(fs->block_map); + fs->flags |= EXT2_FLAG_BB_DIRTY | EXT2_FLAG_IB_DIRTY; +#endif + fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; + ext2fs_flush(fs); + fs->flags |= EXT2_FLAG_MASTER_SB_ONLY; +} +#endif /* ENABLE_SWAPFS */ + +#endif + +/* + * util.c --- miscellaneous utilities + */ + + +void *e2fsck_allocate_memory(e2fsck_t ctx, unsigned int size, + const char *description) +{ + void *ret; + char buf[256]; + + ret = malloc(size); + if (!ret) { + sprintf(buf, "Can't allocate %s\n", description); + bb_error_msg_and_die(buf); + } + memset(ret, 0, size); + return ret; +} + +static char *string_copy(const char *str, int len) +{ + char *ret; + + if (!str) + return NULL; + if (!len) + len = strlen(str); + ret = malloc(len+1); + if (ret) { + strncpy(ret, str, len); + ret[len] = 0; + } + return ret; +} + +#ifndef HAVE_CONIO_H +static int read_a_char(void) +{ + char c; + int r; + int fail = 0; + + while(1) { + if (e2fsck_global_ctx && + (e2fsck_global_ctx->flags & E2F_FLAG_CANCEL)) { + return 3; + } + r = read(0, &c, 1); + if (r == 1) + return c; + if (fail++ > 100) + break; + } + return EOF; +} +#endif + +static int ask_yn(const char * string, int def) +{ + int c; + const char *defstr; + static const char short_yes[] = "yY"; + static const char short_no[] = "nN"; + +#ifdef HAVE_TERMIOS_H + struct termios termios, tmp; + + tcgetattr (0, &termios); + tmp = termios; + tmp.c_lflag &= ~(ICANON | ECHO); + tmp.c_cc[VMIN] = 1; + tmp.c_cc[VTIME] = 0; + tcsetattr (0, TCSANOW, &tmp); +#endif + + if (def == 1) + defstr = ""; + else if (def == 0) + defstr = ""; + else + defstr = " (y/n)"; + printf("%s%s? ", string, defstr); + while (1) { + fflush (stdout); + if ((c = read_a_char()) == EOF) + break; + if (c == 3) { +#ifdef HAVE_TERMIOS_H + tcsetattr (0, TCSANOW, &termios); +#endif + if (e2fsck_global_ctx && + e2fsck_global_ctx->flags & E2F_FLAG_SETJMP_OK) { + puts("\n"); + longjmp(e2fsck_global_ctx->abort_loc, 1); + } + puts(_("cancelled!\n")); + return 0; + } + if (strchr(short_yes, (char) c)) { + def = 1; + break; + } + else if (strchr(short_no, (char) c)) { + def = 0; + break; + } + else if ((c == ' ' || c == '\n') && (def != -1)) + break; + } + if (def) + puts("yes\n"); + else + puts ("no\n"); +#ifdef HAVE_TERMIOS_H + tcsetattr (0, TCSANOW, &termios); +#endif + return def; +} + +int ask (e2fsck_t ctx, const char * string, int def) +{ + if (ctx->options & E2F_OPT_NO) { + printf(_("%s? no\n\n"), string); + return 0; + } + if (ctx->options & E2F_OPT_YES) { + printf(_("%s? yes\n\n"), string); + return 1; + } + if (ctx->options & E2F_OPT_PREEN) { + printf("%s? %s\n\n", string, def ? _("yes") : _("no")); + return def; + } + return ask_yn(string, def); +} + +void e2fsck_read_bitmaps(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + + if (ctx->invalid_bitmaps) { + bb_error_msg(_("e2fsck_read_bitmaps: illegal bitmap block(s) for %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + + ehandler_operation(_("reading inode and block bitmaps")); + retval = ext2fs_read_bitmaps(fs); + ehandler_operation(0); + if (retval) { + bb_error_msg(_("while retrying to read bitmaps for %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } +} + +static void e2fsck_write_bitmaps(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + + if (ext2fs_test_bb_dirty(fs)) { + ehandler_operation(_("writing block bitmaps")); + retval = ext2fs_write_block_bitmap(fs); + ehandler_operation(0); + if (retval) { + bb_error_msg(_("while retrying to write block bitmaps for %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + } + + if (ext2fs_test_ib_dirty(fs)) { + ehandler_operation(_("writing inode bitmaps")); + retval = ext2fs_write_inode_bitmap(fs); + ehandler_operation(0); + if (retval) { + bb_error_msg(_("while retrying to write inode bitmaps for %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + } +} + +void preenhalt(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + + if (!(ctx->options & E2F_OPT_PREEN)) + return; + fprintf(stderr, _("\n\n%s: UNEXPECTED INCONSISTENCY; " + "RUN fsck MANUALLY.\n\t(i.e., without -a or -p options)\n"), + ctx->device_name); + if (fs != NULL) { + fs->super->s_state |= EXT2_ERROR_FS; + ext2fs_mark_super_dirty(fs); + ext2fs_close(fs); + } + exit(EXIT_UNCORRECTED); +} + +void e2fsck_read_inode(e2fsck_t ctx, unsigned long ino, + struct ext2_inode * inode, const char *proc) +{ + int retval; + + retval = ext2fs_read_inode(ctx->fs, ino, inode); + if (retval) { + bb_error_msg(_("while reading inode %ld in %s"), ino, proc); + bb_error_msg_and_die(0); + } +} + +extern void e2fsck_write_inode_full(e2fsck_t ctx, unsigned long ino, + struct ext2_inode * inode, int bufsize, + const char *proc) +{ + int retval; + + retval = ext2fs_write_inode_full(ctx->fs, ino, inode, bufsize); + if (retval) { + bb_error_msg(_("while writing inode %ld in %s"), ino, proc); + bb_error_msg_and_die(0); + } +} + +extern void e2fsck_write_inode(e2fsck_t ctx, unsigned long ino, + struct ext2_inode * inode, const char *proc) +{ + int retval; + + retval = ext2fs_write_inode(ctx->fs, ino, inode); + if (retval) { + bb_error_msg(_("while writing inode %ld in %s"), ino, proc); + bb_error_msg_and_die(0); + } +} + +blk_t get_backup_sb(e2fsck_t ctx, ext2_filsys fs, const char *name, + io_manager manager) +{ + struct ext2_super_block *sb; + io_channel io = NULL; + void *buf = NULL; + int blocksize; + blk_t superblock, ret_sb = 8193; + + if (fs && fs->super) { + ret_sb = (fs->super->s_blocks_per_group + + fs->super->s_first_data_block); + if (ctx) { + ctx->superblock = ret_sb; + ctx->blocksize = fs->blocksize; + } + return ret_sb; + } + + if (ctx) { + if (ctx->blocksize) { + ret_sb = ctx->blocksize * 8; + if (ctx->blocksize == 1024) + ret_sb++; + ctx->superblock = ret_sb; + return ret_sb; + } + ctx->superblock = ret_sb; + ctx->blocksize = 1024; + } + + if (!name || !manager) + goto cleanup; + + if (manager->open(name, 0, &io) != 0) + goto cleanup; + + if (ext2fs_get_mem(SUPERBLOCK_SIZE, &buf)) + goto cleanup; + sb = (struct ext2_super_block *) buf; + + for (blocksize = EXT2_MIN_BLOCK_SIZE; + blocksize <= EXT2_MAX_BLOCK_SIZE ; blocksize *= 2) { + superblock = blocksize*8; + if (blocksize == 1024) + superblock++; + io_channel_set_blksize(io, blocksize); + if (io_channel_read_blk(io, superblock, + -SUPERBLOCK_SIZE, buf)) + continue; +#if BB_BIG_ENDIAN + if (sb->s_magic == ext2fs_swab16(EXT2_SUPER_MAGIC)) + ext2fs_swap_super(sb); +#endif + if (sb->s_magic == EXT2_SUPER_MAGIC) { + ret_sb = superblock; + if (ctx) { + ctx->superblock = superblock; + ctx->blocksize = blocksize; + } + break; + } + } + +cleanup: + if (io) + io_channel_close(io); + ext2fs_free_mem(&buf); + return ret_sb; +} + + +/* + * This function runs through the e2fsck passes and calls them all, + * returning restart, abort, or cancel as necessary... + */ +typedef void (*pass_t)(e2fsck_t ctx); + +static const pass_t e2fsck_passes[] = { + e2fsck_pass1, e2fsck_pass2, e2fsck_pass3, e2fsck_pass4, + e2fsck_pass5, 0 }; + +#define E2F_FLAG_RUN_RETURN (E2F_FLAG_SIGNAL_MASK|E2F_FLAG_RESTART) + +static int e2fsck_run(e2fsck_t ctx) +{ + int i; + pass_t e2fsck_pass; + + if (setjmp(ctx->abort_loc)) { + ctx->flags &= ~E2F_FLAG_SETJMP_OK; + return (ctx->flags & E2F_FLAG_RUN_RETURN); + } + ctx->flags |= E2F_FLAG_SETJMP_OK; + + for (i=0; (e2fsck_pass = e2fsck_passes[i]); i++) { + if (ctx->flags & E2F_FLAG_RUN_RETURN) + break; + e2fsck_pass(ctx); + if (ctx->progress) + (void) (ctx->progress)(ctx, 0, 0, 0); + } + ctx->flags &= ~E2F_FLAG_SETJMP_OK; + + if (ctx->flags & E2F_FLAG_RUN_RETURN) + return (ctx->flags & E2F_FLAG_RUN_RETURN); + return 0; +} + + +/* + * unix.c - The unix-specific code for e2fsck + */ + + +/* Command line options */ +static int swapfs; +#ifdef ENABLE_SWAPFS +static int normalize_swapfs; +#endif +static int cflag; /* check disk */ +static int show_version_only; +static int verbose; + +#define P_E2(singular, plural, n) n, ((n) == 1 ? singular : plural) + +static void show_stats(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + int inodes, inodes_used, blocks, blocks_used; + int dir_links; + int num_files, num_links; + int frag_percent; + + dir_links = 2 * ctx->fs_directory_count - 1; + num_files = ctx->fs_total_count - dir_links; + num_links = ctx->fs_links_count - dir_links; + inodes = fs->super->s_inodes_count; + inodes_used = (fs->super->s_inodes_count - + fs->super->s_free_inodes_count); + blocks = fs->super->s_blocks_count; + blocks_used = (fs->super->s_blocks_count - + fs->super->s_free_blocks_count); + + frag_percent = (10000 * ctx->fs_fragmented) / inodes_used; + frag_percent = (frag_percent + 5) / 10; + + if (!verbose) { + printf("%s: %d/%d files (%0d.%d%% non-contiguous), %d/%d blocks\n", + ctx->device_name, inodes_used, inodes, + frag_percent / 10, frag_percent % 10, + blocks_used, blocks); + return; + } + printf("\n%8d inode%s used (%d%%)\n", P_E2("", "s", inodes_used), + 100 * inodes_used / inodes); + printf("%8d non-contiguous inode%s (%0d.%d%%)\n", + P_E2("", "s", ctx->fs_fragmented), + frag_percent / 10, frag_percent % 10); + printf(_(" # of inodes with ind/dind/tind blocks: %d/%d/%d\n"), + ctx->fs_ind_count, ctx->fs_dind_count, ctx->fs_tind_count); + printf("%8d block%s used (%d%%)\n", P_E2("", "s", blocks_used), + (int) ((long long) 100 * blocks_used / blocks)); + printf("%8d large file%s\n", P_E2("", "s", ctx->large_files)); + printf("\n%8d regular file%s\n", P_E2("", "s", ctx->fs_regular_count)); + printf("%8d director%s\n", P_E2("y", "ies", ctx->fs_directory_count)); + printf("%8d character device file%s\n", P_E2("", "s", ctx->fs_chardev_count)); + printf("%8d block device file%s\n", P_E2("", "s", ctx->fs_blockdev_count)); + printf("%8d fifo%s\n", P_E2("", "s", ctx->fs_fifo_count)); + printf("%8d link%s\n", P_E2("", "s", ctx->fs_links_count - dir_links)); + printf("%8d symbolic link%s", P_E2("", "s", ctx->fs_symlinks_count)); + printf(" (%d fast symbolic link%s)\n", P_E2("", "s", ctx->fs_fast_symlinks_count)); + printf("%8d socket%s--------\n\n", P_E2("", "s", ctx->fs_sockets_count)); + printf("%8d file%s\n", P_E2("", "s", ctx->fs_total_count - dir_links)); +} + +static void check_mount(e2fsck_t ctx) +{ + errcode_t retval; + int cont; + + retval = ext2fs_check_if_mounted(ctx->filesystem_name, + &ctx->mount_flags); + if (retval) { + bb_error_msg(_("while determining whether %s is mounted."), + ctx->filesystem_name); + return; + } + + /* + * If the filesystem isn't mounted, or it's the root filesystem + * and it's mounted read-only, then everything's fine. + */ + if ((!(ctx->mount_flags & EXT2_MF_MOUNTED)) || + ((ctx->mount_flags & EXT2_MF_ISROOT) && + (ctx->mount_flags & EXT2_MF_READONLY))) + return; + + if (ctx->options & E2F_OPT_READONLY) { + printf(_("Warning! %s is mounted.\n"), ctx->filesystem_name); + return; + } + + printf(_("%s is mounted. "), ctx->filesystem_name); + if (!ctx->interactive) + bb_error_msg_and_die(_("Cannot continue, aborting.")); + printf(_("\n\n\007\007\007\007WARNING!!! " + "Running e2fsck on a mounted filesystem may cause\n" + "SEVERE filesystem damage.\007\007\007\n\n")); + cont = ask_yn(_("Do you really want to continue"), -1); + if (!cont) { + printf(_("check aborted.\n")); + exit (0); + } + return; +} + +static int is_on_batt(void) +{ + FILE *f; + DIR *d; + char tmp[80], tmp2[80], fname[80]; + unsigned int acflag; + struct dirent* de; + + f = fopen("/proc/apm", "r"); + if (f) { + if (fscanf(f, "%s %s %s %x", tmp, tmp, tmp, &acflag) != 4) + acflag = 1; + fclose(f); + return (acflag != 1); + } + d = opendir("/proc/acpi/ac_adapter"); + if (d) { + while ((de=readdir(d)) != NULL) { + if (!strncmp(".", de->d_name, 1)) + continue; + snprintf(fname, 80, "/proc/acpi/ac_adapter/%s/state", + de->d_name); + f = fopen(fname, "r"); + if (!f) + continue; + if (fscanf(f, "%s %s", tmp2, tmp) != 2) + tmp[0] = 0; + fclose(f); + if (strncmp(tmp, "off-line", 8) == 0) { + closedir(d); + return 1; + } + } + closedir(d); + } + return 0; +} + +/* + * This routine checks to see if a filesystem can be skipped; if so, + * it will exit with EXIT_OK. Under some conditions it will print a + * message explaining why a check is being forced. + */ +static void check_if_skip(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + const char *reason = NULL; + unsigned int reason_arg = 0; + long next_check; + int batt = is_on_batt(); + time_t now = time(0); + + if ((ctx->options & E2F_OPT_FORCE) || cflag || swapfs) + return; + + if ((fs->super->s_state & EXT2_ERROR_FS) || + !ext2fs_test_valid(fs)) + reason = _(" contains a file system with errors"); + else if ((fs->super->s_state & EXT2_VALID_FS) == 0) + reason = _(" was not cleanly unmounted"); + else if ((fs->super->s_max_mnt_count > 0) && + (fs->super->s_mnt_count >= + (unsigned) fs->super->s_max_mnt_count)) { + reason = _(" has been mounted %u times without being checked"); + reason_arg = fs->super->s_mnt_count; + if (batt && (fs->super->s_mnt_count < + (unsigned) fs->super->s_max_mnt_count*2)) + reason = 0; + } else if (fs->super->s_checkinterval && + ((now - fs->super->s_lastcheck) >= + fs->super->s_checkinterval)) { + reason = _(" has gone %u days without being checked"); + reason_arg = (now - fs->super->s_lastcheck)/(3600*24); + if (batt && ((now - fs->super->s_lastcheck) < + fs->super->s_checkinterval*2)) + reason = 0; + } + if (reason) { + fputs(ctx->device_name, stdout); + printf(reason, reason_arg); + fputs(_(", check forced.\n"), stdout); + return; + } + printf(_("%s: clean, %d/%d files, %d/%d blocks"), ctx->device_name, + fs->super->s_inodes_count - fs->super->s_free_inodes_count, + fs->super->s_inodes_count, + fs->super->s_blocks_count - fs->super->s_free_blocks_count, + fs->super->s_blocks_count); + next_check = 100000; + if (fs->super->s_max_mnt_count > 0) { + next_check = fs->super->s_max_mnt_count - fs->super->s_mnt_count; + if (next_check <= 0) + next_check = 1; + } + if (fs->super->s_checkinterval && + ((now - fs->super->s_lastcheck) >= fs->super->s_checkinterval)) + next_check = 1; + if (next_check <= 5) { + if (next_check == 1) + fputs(_(" (check after next mount)"), stdout); + else + printf(_(" (check in %ld mounts)"), next_check); + } + fputc('\n', stdout); + ext2fs_close(fs); + ctx->fs = NULL; + e2fsck_free_context(ctx); + exit(EXIT_OK); +} + +/* + * For completion notice + */ +struct percent_tbl { + int max_pass; + int table[32]; +}; +static const struct percent_tbl e2fsck_tbl = { + 5, { 0, 70, 90, 92, 95, 100 } +}; + +static char bar[128], spaces[128]; + +static float calc_percent(const struct percent_tbl *tbl, int pass, int curr, + int max) +{ + float percent; + + if (pass <= 0) + return 0.0; + if (pass > tbl->max_pass || max == 0) + return 100.0; + percent = ((float) curr) / ((float) max); + return ((percent * (tbl->table[pass] - tbl->table[pass-1])) + + tbl->table[pass-1]); +} + +void e2fsck_clear_progbar(e2fsck_t ctx) +{ + if (!(ctx->flags & E2F_FLAG_PROG_BAR)) + return; + + printf("%s%s\r%s", ctx->start_meta, spaces + (sizeof(spaces) - 80), + ctx->stop_meta); + fflush(stdout); + ctx->flags &= ~E2F_FLAG_PROG_BAR; +} + +int e2fsck_simple_progress(e2fsck_t ctx, const char *label, float percent, + unsigned int dpynum) +{ + static const char spinner[] = "\\|/-"; + int i; + unsigned int tick; + struct timeval tv; + int dpywidth; + int fixed_percent; + + if (ctx->flags & E2F_FLAG_PROG_SUPPRESS) + return 0; + + /* + * Calculate the new progress position. If the + * percentage hasn't changed, then we skip out right + * away. + */ + fixed_percent = (int) ((10 * percent) + 0.5); + if (ctx->progress_last_percent == fixed_percent) + return 0; + ctx->progress_last_percent = fixed_percent; + + /* + * If we've already updated the spinner once within + * the last 1/8th of a second, no point doing it + * again. + */ + gettimeofday(&tv, NULL); + tick = (tv.tv_sec << 3) + (tv.tv_usec / (1000000 / 8)); + if ((tick == ctx->progress_last_time) && + (fixed_percent != 0) && (fixed_percent != 1000)) + return 0; + ctx->progress_last_time = tick; + + /* + * Advance the spinner, and note that the progress bar + * will be on the screen + */ + ctx->progress_pos = (ctx->progress_pos+1) & 3; + ctx->flags |= E2F_FLAG_PROG_BAR; + + dpywidth = 66 - strlen(label); + dpywidth = 8 * (dpywidth / 8); + if (dpynum) + dpywidth -= 8; + + i = ((percent * dpywidth) + 50) / 100; + printf("%s%s: |%s%s", ctx->start_meta, label, + bar + (sizeof(bar) - (i+1)), + spaces + (sizeof(spaces) - (dpywidth - i + 1))); + if (fixed_percent == 1000) + fputc('|', stdout); + else + fputc(spinner[ctx->progress_pos & 3], stdout); + printf(" %4.1f%% ", percent); + if (dpynum) + printf("%u\r", dpynum); + else + fputs(" \r", stdout); + fputs(ctx->stop_meta, stdout); + + if (fixed_percent == 1000) + e2fsck_clear_progbar(ctx); + fflush(stdout); + + return 0; +} + +static int e2fsck_update_progress(e2fsck_t ctx, int pass, + unsigned long cur, unsigned long max) +{ + char buf[80]; + float percent; + + if (pass == 0) + return 0; + + if (ctx->progress_fd) { + sprintf(buf, "%d %lu %lu\n", pass, cur, max); + write(ctx->progress_fd, buf, strlen(buf)); + } else { + percent = calc_percent(&e2fsck_tbl, pass, cur, max); + e2fsck_simple_progress(ctx, ctx->device_name, + percent, 0); + } + return 0; +} + +static void reserve_stdio_fds(void) +{ + int fd; + + while (1) { + fd = open(bb_dev_null, O_RDWR); + if (fd > 2) + break; + if (fd < 0) { + fprintf(stderr, _("ERROR: Cannot open " + "/dev/null (%s)\n"), + strerror(errno)); + break; + } + } + close(fd); +} + +static void signal_progress_on(int sig FSCK_ATTR((unused))) +{ + e2fsck_t ctx = e2fsck_global_ctx; + + if (!ctx) + return; + + ctx->progress = e2fsck_update_progress; + ctx->progress_fd = 0; +} + +static void signal_progress_off(int sig FSCK_ATTR((unused))) +{ + e2fsck_t ctx = e2fsck_global_ctx; + + if (!ctx) + return; + + e2fsck_clear_progbar(ctx); + ctx->progress = 0; +} + +static void signal_cancel(int sig FSCK_ATTR((unused))) +{ + e2fsck_t ctx = e2fsck_global_ctx; + + if (!ctx) + exit(FSCK_CANCELED); + + ctx->flags |= E2F_FLAG_CANCEL; +} + +static void parse_extended_opts(e2fsck_t ctx, const char *opts) +{ + char *buf, *token, *next, *p, *arg; + int ea_ver; + int extended_usage = 0; + + buf = string_copy(opts, 0); + for (token = buf; token && *token; token = next) { + p = strchr(token, ','); + next = 0; + if (p) { + *p = 0; + next = p+1; + } + arg = strchr(token, '='); + if (arg) { + *arg = 0; + arg++; + } + if (strcmp(token, "ea_ver") == 0) { + if (!arg) { + extended_usage++; + continue; + } + ea_ver = strtoul(arg, &p, 0); + if (*p || + ((ea_ver != 1) && (ea_ver != 2))) { + fprintf(stderr, + _("Invalid EA version.\n")); + extended_usage++; + continue; + } + ctx->ext_attr_ver = ea_ver; + } else { + fprintf(stderr, _("Unknown extended option: %s\n"), + token); + extended_usage++; + } + } + if (extended_usage) { + bb_error_msg_and_die( + "Extended options are separated by commas, " + "and may take an argument which\n" + "is set off by an equals ('=') sign. " + "Valid extended options are:\n" + "\tea_ver=\n\n"); + } +} + + +static errcode_t PRS(int argc, char *argv[], e2fsck_t *ret_ctx) +{ + int flush = 0; + int c, fd; + e2fsck_t ctx; + errcode_t retval; + struct sigaction sa; + char *extended_opts = 0; + + retval = e2fsck_allocate_context(&ctx); + if (retval) + return retval; + + *ret_ctx = ctx; + + setvbuf(stdout, NULL, _IONBF, BUFSIZ); + setvbuf(stderr, NULL, _IONBF, BUFSIZ); + if (isatty(0) && isatty(1)) { + ctx->interactive = 1; + } else { + ctx->start_meta[0] = '\001'; + ctx->stop_meta[0] = '\002'; + } + memset(bar, '=', sizeof(bar)-1); + memset(spaces, ' ', sizeof(spaces)-1); + blkid_get_cache(&ctx->blkid, NULL); + + if (argc && *argv) + ctx->program_name = *argv; + else + ctx->program_name = "e2fsck"; + while ((c = getopt (argc, argv, "panyrcC:B:dE:fvtFVM:b:I:j:P:l:L:N:SsDk")) != EOF) + switch (c) { + case 'C': + ctx->progress = e2fsck_update_progress; + ctx->progress_fd = atoi(optarg); + if (!ctx->progress_fd) + break; + /* Validate the file descriptor to avoid disasters */ + fd = dup(ctx->progress_fd); + if (fd < 0) { + fprintf(stderr, + _("Error validating file descriptor %d: %s\n"), + ctx->progress_fd, + error_message(errno)); + bb_error_msg_and_die(_("Invalid completion information file descriptor")); + } else + close(fd); + break; + case 'D': + ctx->options |= E2F_OPT_COMPRESS_DIRS; + break; + case 'E': + extended_opts = optarg; + break; + case 'p': + case 'a': + if (ctx->options & (E2F_OPT_YES|E2F_OPT_NO)) { + conflict_opt: + bb_error_msg_and_die(_("Only one the options -p/-a, -n or -y may be specified.")); + } + ctx->options |= E2F_OPT_PREEN; + break; + case 'n': + if (ctx->options & (E2F_OPT_YES|E2F_OPT_PREEN)) + goto conflict_opt; + ctx->options |= E2F_OPT_NO; + break; + case 'y': + if (ctx->options & (E2F_OPT_PREEN|E2F_OPT_NO)) + goto conflict_opt; + ctx->options |= E2F_OPT_YES; + break; + case 't': + /* FIXME - This needs to go away in a future path - will change binary */ + fprintf(stderr, _("The -t option is not " + "supported on this version of e2fsck.\n")); + break; + case 'c': + if (cflag++) + ctx->options |= E2F_OPT_WRITECHECK; + ctx->options |= E2F_OPT_CHECKBLOCKS; + break; + case 'r': + /* What we do by default, anyway! */ + break; + case 'b': + ctx->use_superblock = atoi(optarg); + ctx->flags |= E2F_FLAG_SB_SPECIFIED; + break; + case 'B': + ctx->blocksize = atoi(optarg); + break; + case 'I': + ctx->inode_buffer_blocks = atoi(optarg); + break; + case 'j': + ctx->journal_name = string_copy(optarg, 0); + break; + case 'P': + ctx->process_inode_size = atoi(optarg); + break; + case 'd': + ctx->options |= E2F_OPT_DEBUG; + break; + case 'f': + ctx->options |= E2F_OPT_FORCE; + break; + case 'F': + flush = 1; + break; + case 'v': + verbose = 1; + break; + case 'V': + show_version_only = 1; + break; + case 'N': + ctx->device_name = optarg; + break; +#ifdef ENABLE_SWAPFS + case 's': + normalize_swapfs = 1; + case 'S': + swapfs = 1; + break; +#else + case 's': + case 'S': + fprintf(stderr, _("Byte-swapping filesystems " + "not compiled in this version " + "of e2fsck\n")); + exit(1); +#endif + default: + bb_show_usage(); + } + if (show_version_only) + return 0; + if (optind != argc - 1) + bb_show_usage(); + if ((ctx->options & E2F_OPT_NO) && + !cflag && !swapfs && !(ctx->options & E2F_OPT_COMPRESS_DIRS)) + ctx->options |= E2F_OPT_READONLY; + ctx->io_options = strchr(argv[optind], '?'); + if (ctx->io_options) + *ctx->io_options++ = 0; + ctx->filesystem_name = blkid_get_devname(ctx->blkid, argv[optind], 0); + if (!ctx->filesystem_name) { + bb_error_msg(_("Unable to resolve '%s'"), argv[optind]); + bb_error_msg_and_die(0); + } + if (extended_opts) + parse_extended_opts(ctx, extended_opts); + + if (flush) { + fd = open(ctx->filesystem_name, O_RDONLY, 0); + if (fd < 0) { + bb_error_msg(_("while opening %s for flushing"), + ctx->filesystem_name); + bb_error_msg_and_die(0); + } + if ((retval = ext2fs_sync_device(fd, 1))) { + bb_error_msg(_("while trying to flush %s"), + ctx->filesystem_name); + bb_error_msg_and_die(0); + } + close(fd); + } +#ifdef ENABLE_SWAPFS + if (swapfs && cflag) { + fprintf(stderr, _("Incompatible options not " + "allowed when byte-swapping.\n")); + exit(EXIT_USAGE); + } +#endif + /* + * Set up signal action + */ + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = signal_cancel; + sigaction(SIGINT, &sa, 0); + sigaction(SIGTERM, &sa, 0); +#ifdef SA_RESTART + sa.sa_flags = SA_RESTART; +#endif + e2fsck_global_ctx = ctx; + sa.sa_handler = signal_progress_on; + sigaction(SIGUSR1, &sa, 0); + sa.sa_handler = signal_progress_off; + sigaction(SIGUSR2, &sa, 0); + + /* Update our PATH to include /sbin if we need to run badblocks */ + if (cflag) + e2fs_set_sbin_path(); + return 0; +} + +static const char my_ver_string[] = E2FSPROGS_VERSION; +static const char my_ver_date[] = E2FSPROGS_DATE; + +int e2fsck_main (int argc, char *argv[]) +{ + errcode_t retval; + int exit_value = EXIT_OK; + ext2_filsys fs = 0; + io_manager io_ptr; + struct ext2_super_block *sb; + const char *lib_ver_date; + int my_ver, lib_ver; + e2fsck_t ctx; + struct problem_context pctx; + int flags, run_result; + + clear_problem_context(&pctx); + + my_ver = ext2fs_parse_version_string(my_ver_string); + lib_ver = ext2fs_get_library_version(0, &lib_ver_date); + if (my_ver > lib_ver) { + fprintf( stderr, _("Error: ext2fs library version " + "out of date!\n")); + show_version_only++; + } + + retval = PRS(argc, argv, &ctx); + if (retval) { + bb_error_msg(_("while trying to initialize program")); + exit(EXIT_ERROR); + } + reserve_stdio_fds(); + + if (!(ctx->options & E2F_OPT_PREEN) || show_version_only) + fprintf(stderr, "e2fsck %s (%s)\n", my_ver_string, + my_ver_date); + + if (show_version_only) { + fprintf(stderr, _("\tUsing %s, %s\n"), + error_message(EXT2_ET_BASE), lib_ver_date); + exit(EXIT_OK); + } + + check_mount(ctx); + + if (!(ctx->options & E2F_OPT_PREEN) && + !(ctx->options & E2F_OPT_NO) && + !(ctx->options & E2F_OPT_YES)) { + if (!ctx->interactive) + bb_error_msg_and_die(_("need terminal for interactive repairs")); + } + ctx->superblock = ctx->use_superblock; +restart: +#ifdef CONFIG_TESTIO_DEBUG + io_ptr = test_io_manager; + test_io_backing_manager = unix_io_manager; +#else + io_ptr = unix_io_manager; +#endif + flags = 0; + if ((ctx->options & E2F_OPT_READONLY) == 0) + flags |= EXT2_FLAG_RW; + + if (ctx->superblock && ctx->blocksize) { + retval = ext2fs_open2(ctx->filesystem_name, ctx->io_options, + flags, ctx->superblock, ctx->blocksize, + io_ptr, &fs); + } else if (ctx->superblock) { + int blocksize; + for (blocksize = EXT2_MIN_BLOCK_SIZE; + blocksize <= EXT2_MAX_BLOCK_SIZE; blocksize *= 2) { + retval = ext2fs_open2(ctx->filesystem_name, + ctx->io_options, flags, + ctx->superblock, blocksize, + io_ptr, &fs); + if (!retval) + break; + } + } else + retval = ext2fs_open2(ctx->filesystem_name, ctx->io_options, + flags, 0, 0, io_ptr, &fs); + if (!ctx->superblock && !(ctx->options & E2F_OPT_PREEN) && + !(ctx->flags & E2F_FLAG_SB_SPECIFIED) && + ((retval == EXT2_ET_BAD_MAGIC) || + ((retval == 0) && ext2fs_check_desc(fs)))) { + if (!fs || (fs->group_desc_count > 1)) { + printf(_("%s trying backup blocks...\n"), + retval ? _("Couldn't find ext2 superblock,") : + _("Group descriptors look bad...")); + get_backup_sb(ctx, fs, ctx->filesystem_name, io_ptr); + if (fs) + ext2fs_close(fs); + goto restart; + } + } + if (retval) { + bb_error_msg(_("while trying to open %s"), + ctx->filesystem_name); + if (retval == EXT2_ET_REV_TOO_HIGH) { + printf(_("The filesystem revision is apparently " + "too high for this version of e2fsck.\n" + "(Or the filesystem superblock " + "is corrupt)\n\n")); + fix_problem(ctx, PR_0_SB_CORRUPT, &pctx); + } else if (retval == EXT2_ET_SHORT_READ) + printf(_("Could this be a zero-length partition?\n")); + else if ((retval == EPERM) || (retval == EACCES)) + printf(_("You must have %s access to the " + "filesystem or be root\n"), + (ctx->options & E2F_OPT_READONLY) ? + "r/o" : "r/w"); + else if (retval == ENXIO) + printf(_("Possibly non-existent or swap device?\n")); +#ifdef EROFS + else if (retval == EROFS) + printf(_("Disk write-protected; use the -n option " + "to do a read-only\n" + "check of the device.\n")); +#endif + else + fix_problem(ctx, PR_0_SB_CORRUPT, &pctx); + bb_error_msg_and_die(0); + } + ctx->fs = fs; + fs->priv_data = ctx; + sb = fs->super; + if (sb->s_rev_level > E2FSCK_CURRENT_REV) { + bb_error_msg(_("while trying to open %s"), + ctx->filesystem_name); + get_newer: + bb_error_msg_and_die(_("Get a newer version of e2fsck!")); + } + + /* + * Set the device name, which is used whenever we print error + * or informational messages to the user. + */ + if (ctx->device_name == 0 && + (sb->s_volume_name[0] != 0)) { + ctx->device_name = string_copy(sb->s_volume_name, + sizeof(sb->s_volume_name)); + } + if (ctx->device_name == 0) + ctx->device_name = ctx->filesystem_name; + + /* + * Make sure the ext3 superblock fields are consistent. + */ + retval = e2fsck_check_ext3_journal(ctx); + if (retval) { + bb_error_msg(_("while checking ext3 journal for %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + + /* + * Check to see if we need to do ext3-style recovery. If so, + * do it, and then restart the fsck. + */ + if (sb->s_feature_incompat & EXT3_FEATURE_INCOMPAT_RECOVER) { + if (ctx->options & E2F_OPT_READONLY) { + printf(_("Warning: skipping journal recovery " + "because doing a read-only filesystem " + "check.\n")); + io_channel_flush(ctx->fs->io); + } else { + if (ctx->flags & E2F_FLAG_RESTARTED) { + /* + * Whoops, we attempted to run the + * journal twice. This should never + * happen, unless the hardware or + * device driver is being bogus. + */ + bb_error_msg(_("cannot set superblock flags on %s"), ctx->device_name); + bb_error_msg_and_die(0); + } + retval = e2fsck_run_ext3_journal(ctx); + if (retval) { + bb_error_msg(_("while recovering ext3 journal of %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + ext2fs_close(ctx->fs); + ctx->fs = 0; + ctx->flags |= E2F_FLAG_RESTARTED; + goto restart; + } + } + + /* + * Check for compatibility with the feature sets. We need to + * be more stringent than ext2fs_open(). + */ + if ((sb->s_feature_compat & ~EXT2_LIB_FEATURE_COMPAT_SUPP) || + (sb->s_feature_incompat & ~EXT2_LIB_FEATURE_INCOMPAT_SUPP)) { + bb_error_msg("(%s)", ctx->device_name); + goto get_newer; + } + if (sb->s_feature_ro_compat & ~EXT2_LIB_FEATURE_RO_COMPAT_SUPP) { + bb_error_msg("(%s)", ctx->device_name); + goto get_newer; + } +#ifdef ENABLE_COMPRESSION + /* FIXME - do we support this at all? */ + if (sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_COMPRESSION) + bb_error_msg(_("Warning: compression support is experimental.")); +#endif +#ifndef ENABLE_HTREE + if (sb->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) { + bb_error_msg(_("E2fsck not compiled with HTREE support,\n\t" + "but filesystem %s has HTREE directories."), + ctx->device_name); + goto get_newer; + } +#endif + + /* + * If the user specified a specific superblock, presumably the + * master superblock has been trashed. So we mark the + * superblock as dirty, so it can be written out. + */ + if (ctx->superblock && + !(ctx->options & E2F_OPT_READONLY)) + ext2fs_mark_super_dirty(fs); + + /* + * We only update the master superblock because (a) paranoia; + * we don't want to corrupt the backup superblocks, and (b) we + * don't need to update the mount count and last checked + * fields in the backup superblock (the kernel doesn't + * update the backup superblocks anyway). + */ + fs->flags |= EXT2_FLAG_MASTER_SB_ONLY; + + ehandler_init(fs->io); + + if (ctx->superblock) + set_latch_flags(PR_LATCH_RELOC, PRL_LATCHED, 0); + ext2fs_mark_valid(fs); + check_super_block(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + bb_error_msg_and_die(0); + check_if_skip(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + bb_error_msg_and_die(0); +#ifdef ENABLE_SWAPFS + +#ifdef WORDS_BIGENDIAN +#define NATIVE_FLAG EXT2_FLAG_SWAP_BYTES +#else +#define NATIVE_FLAG 0 +#endif + + + if (normalize_swapfs) { + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) == NATIVE_FLAG) { + fprintf(stderr, _("%s: Filesystem byte order " + "already normalized.\n"), ctx->device_name); + bb_error_msg_and_die(0); + } + } + if (swapfs) { + swap_filesys(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + bb_error_msg_and_die(0); + } +#endif + + /* + * Mark the system as valid, 'til proven otherwise + */ + ext2fs_mark_valid(fs); + + retval = ext2fs_read_bb_inode(fs, &fs->badblocks); + if (retval) { + bb_error_msg(_("while reading bad blocks inode")); + preenhalt(ctx); + printf(_("This doesn't bode well," + " but we'll try to go on...\n")); + } + + run_result = e2fsck_run(ctx); + e2fsck_clear_progbar(ctx); + if (run_result == E2F_FLAG_RESTART) { + printf(_("Restarting e2fsck from the beginning...\n")); + retval = e2fsck_reset_context(ctx); + if (retval) { + bb_error_msg(_("while resetting context")); + bb_error_msg_and_die(0); + } + ext2fs_close(fs); + goto restart; + } + if (run_result & E2F_FLAG_CANCEL) { + printf(_("%s: e2fsck canceled.\n"), ctx->device_name ? + ctx->device_name : ctx->filesystem_name); + exit_value |= FSCK_CANCELED; + } + if (run_result & E2F_FLAG_ABORT) + bb_error_msg_and_die(_("aborted")); + + /* Cleanup */ + if (ext2fs_test_changed(fs)) { + exit_value |= EXIT_NONDESTRUCT; + if (!(ctx->options & E2F_OPT_PREEN)) + printf(_("\n%s: ***** FILE SYSTEM WAS MODIFIED *****\n"), + ctx->device_name); + if (ctx->mount_flags & EXT2_MF_ISROOT) { + printf(_("%s: ***** REBOOT LINUX *****\n"), + ctx->device_name); + exit_value |= EXIT_DESTRUCT; + } + } + if (!ext2fs_test_valid(fs)) { + printf(_("\n%s: ********** WARNING: Filesystem still has " + "errors **********\n\n"), ctx->device_name); + exit_value |= EXIT_UNCORRECTED; + exit_value &= ~EXIT_NONDESTRUCT; + } + if (exit_value & FSCK_CANCELED) + exit_value &= ~EXIT_NONDESTRUCT; + else { + show_stats(ctx); + if (!(ctx->options & E2F_OPT_READONLY)) { + if (ext2fs_test_valid(fs)) { + if (!(sb->s_state & EXT2_VALID_FS)) + exit_value |= EXIT_NONDESTRUCT; + sb->s_state = EXT2_VALID_FS; + } else + sb->s_state &= ~EXT2_VALID_FS; + sb->s_mnt_count = 0; + sb->s_lastcheck = time(NULL); + ext2fs_mark_super_dirty(fs); + } + } + + e2fsck_write_bitmaps(ctx); + + ext2fs_close(fs); + ctx->fs = NULL; + free(ctx->filesystem_name); + free(ctx->journal_name); + e2fsck_free_context(ctx); + + return exit_value; +} diff --git a/e2fsprogs/e2fsck.h b/e2fsprogs/e2fsck.h new file mode 100644 index 000000000..e520632a0 --- /dev/null +++ b/e2fsprogs/e2fsck.h @@ -0,0 +1,640 @@ +/* vi: set sw=4 ts=4: */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ext2fs/kernel-list.h" +#include +#include + +/* + * Now pull in the real linux/jfs.h definitions. + */ +#include "ext2fs/kernel-jbd.h" + + + +#include "fsck.h" + +#include "ext2fs/ext2_fs.h" +#include "blkid/blkid.h" +#include "ext2fs/ext2_ext_attr.h" +#include "uuid/uuid.h" +#include "busybox.h" + +#ifdef HAVE_CONIO_H +#undef HAVE_TERMIOS_H +#include +#define read_a_char() getch() +#else +#ifdef HAVE_TERMIOS_H +#include +#endif +#endif + + +/* + * The last ext2fs revision level that this version of e2fsck is able to + * support + */ +#define E2FSCK_CURRENT_REV 1 + +/* Used by the region allocation code */ +typedef __u32 region_addr_t; +typedef struct region_struct *region_t; + +struct dx_dirblock_info { + int type; + blk_t phys; + int flags; + blk_t parent; + ext2_dirhash_t min_hash; + ext2_dirhash_t max_hash; + ext2_dirhash_t node_min_hash; + ext2_dirhash_t node_max_hash; +}; + +/* +These defines are used in the type field of dx_dirblock_info +*/ + +#define DX_DIRBLOCK_ROOT 1 +#define DX_DIRBLOCK_LEAF 2 +#define DX_DIRBLOCK_NODE 3 + + +/* +The following defines are used in the 'flags' field of a dx_dirblock_info +*/ +#define DX_FLAG_REFERENCED 1 +#define DX_FLAG_DUP_REF 2 +#define DX_FLAG_FIRST 4 +#define DX_FLAG_LAST 8 + +/* + * E2fsck options + */ +#define E2F_OPT_READONLY 0x0001 +#define E2F_OPT_PREEN 0x0002 +#define E2F_OPT_YES 0x0004 +#define E2F_OPT_NO 0x0008 +#define E2F_OPT_TIME 0x0010 +#define E2F_OPT_CHECKBLOCKS 0x0040 +#define E2F_OPT_DEBUG 0x0080 +#define E2F_OPT_FORCE 0x0100 +#define E2F_OPT_WRITECHECK 0x0200 +#define E2F_OPT_COMPRESS_DIRS 0x0400 + +/* + * E2fsck flags + */ +#define E2F_FLAG_ABORT 0x0001 /* Abort signaled */ +#define E2F_FLAG_CANCEL 0x0002 /* Cancel signaled */ +#define E2F_FLAG_SIGNAL_MASK 0x0003 +#define E2F_FLAG_RESTART 0x0004 /* Restart signaled */ + +#define E2F_FLAG_SETJMP_OK 0x0010 /* Setjmp valid for abort */ + +#define E2F_FLAG_PROG_BAR 0x0020 /* Progress bar on screen */ +#define E2F_FLAG_PROG_SUPPRESS 0x0040 /* Progress suspended */ +#define E2F_FLAG_JOURNAL_INODE 0x0080 /* Create a new ext3 journal inode */ +#define E2F_FLAG_SB_SPECIFIED 0x0100 /* The superblock was explicitly + * specified by the user */ +#define E2F_FLAG_RESTARTED 0x0200 /* E2fsck has been restarted */ +#define E2F_FLAG_RESIZE_INODE 0x0400 /* Request to recreate resize inode */ + + +/*Don't know where these come from*/ +#define READ 0 +#define WRITE 1 +#define cpu_to_be32(n) htonl(n) +#define be32_to_cpu(n) ntohl(n) + +/* + * We define a set of "latch groups"; these are problems which are + * handled as a set. The user answers once for a particular latch + * group. + */ +#define PR_LATCH_MASK 0x0ff0 /* Latch mask */ +#define PR_LATCH_BLOCK 0x0010 /* Latch for illegal blocks (pass 1) */ +#define PR_LATCH_BBLOCK 0x0020 /* Latch for bad block inode blocks (pass 1) */ +#define PR_LATCH_IBITMAP 0x0030 /* Latch for pass 5 inode bitmap proc. */ +#define PR_LATCH_BBITMAP 0x0040 /* Latch for pass 5 inode bitmap proc. */ +#define PR_LATCH_RELOC 0x0050 /* Latch for superblock relocate hint */ +#define PR_LATCH_DBLOCK 0x0060 /* Latch for pass 1b dup block headers */ +#define PR_LATCH_LOW_DTIME 0x0070 /* Latch for pass1 orphaned list refugees */ +#define PR_LATCH_TOOBIG 0x0080 /* Latch for file to big errors */ +#define PR_LATCH_OPTIMIZE_DIR 0x0090 /* Latch for optimize directories */ + +#define PR_LATCH(x) ((((x) & PR_LATCH_MASK) >> 4) - 1) + +/* + * Latch group descriptor flags + */ +#define PRL_YES 0x0001 /* Answer yes */ +#define PRL_NO 0x0002 /* Answer no */ +#define PRL_LATCHED 0x0004 /* The latch group is latched */ +#define PRL_SUPPRESS 0x0008 /* Suppress all latch group questions */ + +#define PRL_VARIABLE 0x000f /* All the flags that need to be reset */ + +/* + * Pre-Pass 1 errors + */ + +#define PR_0_BB_NOT_GROUP 0x000001 /* Block bitmap not in group */ +#define PR_0_IB_NOT_GROUP 0x000002 /* Inode bitmap not in group */ +#define PR_0_ITABLE_NOT_GROUP 0x000003 /* Inode table not in group */ +#define PR_0_SB_CORRUPT 0x000004 /* Superblock corrupt */ +#define PR_0_FS_SIZE_WRONG 0x000005 /* Filesystem size is wrong */ +#define PR_0_NO_FRAGMENTS 0x000006 /* Fragments not supported */ +#define PR_0_BLOCKS_PER_GROUP 0x000007 /* Bad blocks_per_group */ +#define PR_0_FIRST_DATA_BLOCK 0x000008 /* Bad first_data_block */ +#define PR_0_ADD_UUID 0x000009 /* Adding UUID to filesystem */ +#define PR_0_RELOCATE_HINT 0x00000A /* Relocate hint */ +#define PR_0_MISC_CORRUPT_SUPER 0x00000B /* Miscellaneous superblock corruption */ +#define PR_0_GETSIZE_ERROR 0x00000C /* Error determing physical device size of filesystem */ +#define PR_0_INODE_COUNT_WRONG 0x00000D /* Inode count in the superblock incorrect */ +#define PR_0_HURD_CLEAR_FILETYPE 0x00000E /* The Hurd does not support the filetype feature */ +#define PR_0_JOURNAL_BAD_INODE 0x00000F /* The Hurd does not support the filetype feature */ +#define PR_0_JOURNAL_UNSUPP_MULTIFS 0x000010 /* The external journal has multiple filesystems (which we can't handle yet) */ +#define PR_0_CANT_FIND_JOURNAL 0x000011 /* Can't find external journal */ +#define PR_0_EXT_JOURNAL_BAD_SUPER 0x000012/* External journal has bad superblock */ +#define PR_0_JOURNAL_BAD_UUID 0x000013 /* Superblock has a bad journal UUID */ +#define PR_0_JOURNAL_UNSUPP_SUPER 0x000014 /* Journal has an unknown superblock type */ +#define PR_0_JOURNAL_BAD_SUPER 0x000015 /* Journal superblock is corrupt */ +#define PR_0_JOURNAL_HAS_JOURNAL 0x000016 /* Journal superblock is corrupt */ +#define PR_0_JOURNAL_RECOVER_SET 0x000017 /* Superblock has recovery flag set but no journal */ +#define PR_0_JOURNAL_RECOVERY_CLEAR 0x000018 /* Journal has data, but recovery flag is clear */ +#define PR_0_JOURNAL_RESET_JOURNAL 0x000019 /* Ask if we should clear the journal */ +#define PR_0_FS_REV_LEVEL 0x00001A /* Filesystem revision is 0, but feature flags are set */ +#define PR_0_ORPHAN_CLEAR_INODE 0x000020 /* Clearing orphan inode */ +#define PR_0_ORPHAN_ILLEGAL_BLOCK_NUM 0x000021 /* Illegal block found in orphaned inode */ +#define PR_0_ORPHAN_ALREADY_CLEARED_BLOCK 0x000022 /* Already cleared block found in orphaned inode */ +#define PR_0_ORPHAN_ILLEGAL_HEAD_INODE 0x000023 /* Illegal orphan inode in superblock */ +#define PR_0_ORPHAN_ILLEGAL_INODE 0x000024 /* Illegal inode in orphaned inode list */ +#define PR_0_JOURNAL_UNSUPP_ROCOMPAT 0x000025 /* Journal has unsupported read-only feature - abort */ +#define PR_0_JOURNAL_UNSUPP_INCOMPAT 0x000026 /* Journal has unsupported incompatible feature - abort */ +#define PR_0_JOURNAL_UNSUPP_VERSION 0x000027 /* Journal has unsupported version number */ +#define PR_0_MOVE_JOURNAL 0x000028 /* Moving journal to hidden file */ +#define PR_0_ERR_MOVE_JOURNAL 0x000029 /* Error moving journal */ +#define PR_0_CLEAR_V2_JOURNAL 0x00002A /* Clearing V2 journal superblock */ +#define PR_0_JOURNAL_RUN 0x00002B /* Run journal anyway */ +#define PR_0_JOURNAL_RUN_DEFAULT 0x00002C /* Run journal anyway by default */ +#define PR_0_BACKUP_JNL 0x00002D /* Backup journal inode blocks */ +#define PR_0_NONZERO_RESERVED_GDT_BLOCKS 0x00002E /* Reserved blocks w/o resize_inode */ +#define PR_0_CLEAR_RESIZE_INODE 0x00002F /* Resize_inode not enabled, but resize inode is non-zero */ +#define PR_0_RESIZE_INODE_INVALID 0x000030 /* Resize inode invalid */ + +/* + * Pass 1 errors + */ + +#define PR_1_PASS_HEADER 0x010000 /* Pass 1: Checking inodes, blocks, and sizes */ +#define PR_1_ROOT_NO_DIR 0x010001 /* Root directory is not an inode */ +#define PR_1_ROOT_DTIME 0x010002 /* Root directory has dtime set */ +#define PR_1_RESERVED_BAD_MODE 0x010003 /* Reserved inode has bad mode */ +#define PR_1_ZERO_DTIME 0x010004 /* Deleted inode has zero dtime */ +#define PR_1_SET_DTIME 0x010005 /* Inode in use, but dtime set */ +#define PR_1_ZERO_LENGTH_DIR 0x010006 /* Zero-length directory */ +#define PR_1_BB_CONFLICT 0x010007 /* Block bitmap conflicts with some other fs block */ +#define PR_1_IB_CONFLICT 0x010008 /* Inode bitmap conflicts with some other fs block */ +#define PR_1_ITABLE_CONFLICT 0x010009 /* Inode table conflicts with some other fs block */ +#define PR_1_BB_BAD_BLOCK 0x01000A /* Block bitmap is on a bad block */ +#define PR_1_IB_BAD_BLOCK 0x01000B /* Inode bitmap is on a bad block */ +#define PR_1_BAD_I_SIZE 0x01000C /* Inode has incorrect i_size */ +#define PR_1_BAD_I_BLOCKS 0x01000D /* Inode has incorrect i_blocks */ +#define PR_1_ILLEGAL_BLOCK_NUM 0x01000E /* Illegal block number in inode */ +#define PR_1_BLOCK_OVERLAPS_METADATA 0x01000F /* Block number overlaps fs metadata */ +#define PR_1_INODE_BLOCK_LATCH 0x010010 /* Inode has illegal blocks (latch question) */ +#define PR_1_TOO_MANY_BAD_BLOCKS 0x010011 /* Too many bad blocks in inode */ +#define PR_1_BB_ILLEGAL_BLOCK_NUM 0x010012 /* Illegal block number in bad block inode */ +#define PR_1_INODE_BBLOCK_LATCH 0x010013 /* Bad block inode has illegal blocks (latch question) */ +#define PR_1_DUP_BLOCKS_PREENSTOP 0x010014 /* Duplicate or bad blocks in use! */ +#define PR_1_BBINODE_BAD_METABLOCK 0x010015 /* Bad block used as bad block indirect block */ +#define PR_1_BBINODE_BAD_METABLOCK_PROMPT 0x010016 /* Inconsistency can't be fixed prompt */ +#define PR_1_BAD_PRIMARY_BLOCK 0x010017 /* Bad primary block */ +#define PR_1_BAD_PRIMARY_BLOCK_PROMPT 0x010018 /* Bad primary block prompt */ +#define PR_1_BAD_PRIMARY_SUPERBLOCK 0x010019 /* Bad primary superblock */ +#define PR_1_BAD_PRIMARY_GROUP_DESCRIPTOR 0x01001A /* Bad primary block group descriptors */ +#define PR_1_BAD_SUPERBLOCK 0x01001B /* Bad superblock in group */ +#define PR_1_BAD_GROUP_DESCRIPTORS 0x01001C /* Bad block group descriptors in group */ +#define PR_1_PROGERR_CLAIMED_BLOCK 0x01001D /* Block claimed for no reason */ +#define PR_1_RELOC_BLOCK_ALLOCATE 0x01001E /* Error allocating blocks for relocating metadata */ +#define PR_1_RELOC_MEMORY_ALLOCATE 0x01001F /* Error allocating block buffer during relocation process */ +#define PR_1_RELOC_FROM_TO 0x010020 /* Relocating metadata group information from X to Y */ +#define PR_1_RELOC_TO 0x010021 /* Relocating metatdata group information to X */ +#define PR_1_RELOC_READ_ERR 0x010022 /* Block read error during relocation process */ +#define PR_1_RELOC_WRITE_ERR 0x010023 /* Block write error during relocation process */ +#define PR_1_ALLOCATE_IBITMAP_ERROR 0x010024 /* Error allocating inode bitmap */ +#define PR_1_ALLOCATE_BBITMAP_ERROR 0x010025 /* Error allocating block bitmap */ +#define PR_1_ALLOCATE_ICOUNT 0x010026 /* Error allocating icount structure */ +#define PR_1_ALLOCATE_DBCOUNT 0x010027 /* Error allocating dbcount */ +#define PR_1_ISCAN_ERROR 0x010028 /* Error while scanning inodes */ +#define PR_1_BLOCK_ITERATE 0x010029 /* Error while iterating over blocks */ +#define PR_1_ICOUNT_STORE 0x01002A /* Error while storing inode count information */ +#define PR_1_ADD_DBLOCK 0x01002B /* Error while storing directory block information */ +#define PR_1_READ_INODE 0x01002C /* Error while reading inode (for clearing) */ +#define PR_1_SUPPRESS_MESSAGES 0x01002D /* Suppress messages prompt */ +#define PR_1_SET_IMAGIC 0x01002F /* Imagic flag set on an inode when filesystem doesn't support it */ +#define PR_1_SET_IMMUTABLE 0x010030 /* Immutable flag set on a device or socket inode */ +#define PR_1_COMPR_SET 0x010031 /* Compression flag set on a non-compressed filesystem */ +#define PR_1_SET_NONZSIZE 0x010032 /* Non-zero size on on device, fifo or socket inode */ +#define PR_1_FS_REV_LEVEL 0x010033 /* Filesystem revision is 0, but feature flags are set */ +#define PR_1_JOURNAL_INODE_NOT_CLEAR 0x010034 /* Journal inode not in use, needs clearing */ +#define PR_1_JOURNAL_BAD_MODE 0x010035 /* Journal inode has wrong mode */ +#define PR_1_LOW_DTIME 0x010036 /* Inode that was part of orphan linked list */ +#define PR_1_ORPHAN_LIST_REFUGEES 0x010037 /* Latch question which asks how to deal with low dtime inodes */ +#define PR_1_ALLOCATE_REFCOUNT 0x010038 /* Error allocating refcount structure */ +#define PR_1_READ_EA_BLOCK 0x010039 /* Error reading Extended Attribute block */ +#define PR_1_BAD_EA_BLOCK 0x01003A /* Invalid Extended Attribute block */ +#define PR_1_EXTATTR_READ_ABORT 0x01003B /* Error reading Extended Attribute block while fixing refcount -- abort */ +#define PR_1_EXTATTR_REFCOUNT 0x01003C /* Extended attribute reference count incorrect */ +#define PR_1_EXTATTR_WRITE 0x01003D /* Error writing Extended Attribute block while fixing refcount */ +#define PR_1_EA_MULTI_BLOCK 0x01003E /* Multiple EA blocks not supported */ +#define PR_1_EA_ALLOC_REGION 0x01003F /* Error allocating EA region allocation structure */ +#define PR_1_EA_ALLOC_COLLISION 0x010040 /* Error EA allocation collision */ +#define PR_1_EA_BAD_NAME 0x010041 /* Bad extended attribute name */ +#define PR_1_EA_BAD_VALUE 0x010042 /* Bad extended attribute value */ +#define PR_1_INODE_TOOBIG 0x010043 /* Inode too big (latch question) */ +#define PR_1_TOOBIG_DIR 0x010044 /* Directory too big */ +#define PR_1_TOOBIG_REG 0x010045 /* Regular file too big */ +#define PR_1_TOOBIG_SYMLINK 0x010046 /* Symlink too big */ +#define PR_1_HTREE_SET 0x010047 /* INDEX_FL flag set on a non-HTREE filesystem */ +#define PR_1_HTREE_NODIR 0x010048 /* INDEX_FL flag set on a non-directory */ +#define PR_1_HTREE_BADROOT 0x010049 /* Invalid root node in HTREE directory */ +#define PR_1_HTREE_HASHV 0x01004A /* Unsupported hash version in HTREE directory */ +#define PR_1_HTREE_INCOMPAT 0x01004B /* Incompatible flag in HTREE root node */ +#define PR_1_HTREE_DEPTH 0x01004C /* HTREE too deep */ +#define PR_1_BB_FS_BLOCK 0x01004D /* Bad block has indirect block that conflicts with filesystem block */ +#define PR_1_RESIZE_INODE_CREATE 0x01004E /* Resize inode failed */ +#define PR_1_EXTRA_ISIZE 0x01004F /* inode->i_size is too long */ +#define PR_1_ATTR_NAME_LEN 0x010050 /* attribute name is too long */ +#define PR_1_ATTR_VALUE_OFFSET 0x010051 /* wrong EA value offset */ +#define PR_1_ATTR_VALUE_BLOCK 0x010052 /* wrong EA blocknumber */ +#define PR_1_ATTR_VALUE_SIZE 0x010053 /* wrong EA value size */ +#define PR_1_ATTR_HASH 0x010054 /* wrong EA hash value */ + +/* + * Pass 1b errors + */ + +#define PR_1B_PASS_HEADER 0x011000 /* Pass 1B: Rescan for duplicate/bad blocks */ +#define PR_1B_DUP_BLOCK_HEADER 0x011001 /* Duplicate/bad block(s) header */ +#define PR_1B_DUP_BLOCK 0x011002 /* Duplicate/bad block(s) in inode */ +#define PR_1B_DUP_BLOCK_END 0x011003 /* Duplicate/bad block(s) end */ +#define PR_1B_ISCAN_ERROR 0x011004 /* Error while scanning inodes */ +#define PR_1B_ALLOCATE_IBITMAP_ERROR 0x011005 /* Error allocating inode bitmap */ +#define PR_1B_BLOCK_ITERATE 0x0110006 /* Error while iterating over blocks */ +#define PR_1B_ADJ_EA_REFCOUNT 0x0110007 /* Error adjusting EA refcount */ +#define PR_1C_PASS_HEADER 0x012000 /* Pass 1C: Scan directories for inodes with dup blocks. */ +#define PR_1D_PASS_HEADER 0x013000 /* Pass 1D: Reconciling duplicate blocks */ +#define PR_1D_DUP_FILE 0x013001 /* File has duplicate blocks */ +#define PR_1D_DUP_FILE_LIST 0x013002 /* List of files sharing duplicate blocks */ +#define PR_1D_SHARE_METADATA 0x013003 /* File sharing blocks with filesystem metadata */ +#define PR_1D_NUM_DUP_INODES 0x013004 /* Report of how many duplicate/bad inodes */ +#define PR_1D_DUP_BLOCKS_DEALT 0x013005 /* Duplicated blocks already reassigned or cloned. */ +#define PR_1D_CLONE_QUESTION 0x013006 /* Clone duplicate/bad blocks? */ +#define PR_1D_DELETE_QUESTION 0x013007 /* Delete file? */ +#define PR_1D_CLONE_ERROR 0x013008 /* Couldn't clone file (error) */ + +/* + * Pass 2 errors + */ + +#define PR_2_PASS_HEADER 0x020000 /* Pass 2: Checking directory structure */ +#define PR_2_BAD_INODE_DOT 0x020001 /* Bad inode number for '.' */ +#define PR_2_BAD_INO 0x020002 /* Directory entry has bad inode number */ +#define PR_2_UNUSED_INODE 0x020003 /* Directory entry has deleted or unused inode */ +#define PR_2_LINK_DOT 0x020004 /* Directry entry is link to '.' */ +#define PR_2_BB_INODE 0x020005 /* Directory entry points to inode now located in a bad block */ +#define PR_2_LINK_DIR 0x020006 /* Directory entry contains a link to a directory */ +#define PR_2_LINK_ROOT 0x020007 /* Directory entry contains a link to the root directry */ +#define PR_2_BAD_NAME 0x020008 /* Directory entry has illegal characters in its name */ +#define PR_2_MISSING_DOT 0x020009 /* Missing '.' in directory inode */ +#define PR_2_MISSING_DOT_DOT 0x02000A /* Missing '..' in directory inode */ +#define PR_2_1ST_NOT_DOT 0x02000B /* First entry in directory inode doesn't contain '.' */ +#define PR_2_2ND_NOT_DOT_DOT 0x02000C /* Second entry in directory inode doesn't contain '..' */ +#define PR_2_FADDR_ZERO 0x02000D /* i_faddr should be zero */ +#define PR_2_FILE_ACL_ZERO 0x02000E /* i_file_acl should be zero */ +#define PR_2_DIR_ACL_ZERO 0x02000F /* i_dir_acl should be zero */ +#define PR_2_FRAG_ZERO 0x020010 /* i_frag should be zero */ +#define PR_2_FSIZE_ZERO 0x020011 /* i_fsize should be zero */ +#define PR_2_BAD_MODE 0x020012 /* inode has bad mode */ +#define PR_2_DIR_CORRUPTED 0x020013 /* directory corrupted */ +#define PR_2_FILENAME_LONG 0x020014 /* filename too long */ +#define PR_2_DIRECTORY_HOLE 0x020015 /* Directory inode has a missing block (hole) */ +#define PR_2_DOT_NULL_TERM 0x020016 /* '.' is not NULL terminated */ +#define PR_2_DOT_DOT_NULL_TERM 0x020017 /* '..' is not NULL terminated */ +#define PR_2_BAD_CHAR_DEV 0x020018 /* Illegal character device in inode */ +#define PR_2_BAD_BLOCK_DEV 0x020019 /* Illegal block device in inode */ +#define PR_2_DUP_DOT 0x02001A /* Duplicate '.' entry */ +#define PR_2_DUP_DOT_DOT 0x02001B /* Duplicate '..' entry */ +#define PR_2_NO_DIRINFO 0x02001C /* Internal error: couldn't find dir_info */ +#define PR_2_FINAL_RECLEN 0x02001D /* Final rec_len is wrong */ +#define PR_2_ALLOCATE_ICOUNT 0x02001E /* Error allocating icount structure */ +#define PR_2_DBLIST_ITERATE 0x02001F /* Error iterating over directory blocks */ +#define PR_2_READ_DIRBLOCK 0x020020 /* Error reading directory block */ +#define PR_2_WRITE_DIRBLOCK 0x020021 /* Error writing directory block */ +#define PR_2_ALLOC_DIRBOCK 0x020022 /* Error allocating new directory block */ +#define PR_2_DEALLOC_INODE 0x020023 /* Error deallocating inode */ +#define PR_2_SPLIT_DOT 0x020024 /* Directory entry for '.' is big. Split? */ +#define PR_2_BAD_FIFO 0x020025 /* Illegal FIFO */ +#define PR_2_BAD_SOCKET 0x020026 /* Illegal socket */ +#define PR_2_SET_FILETYPE 0x020027 /* Directory filetype not set */ +#define PR_2_BAD_FILETYPE 0x020028 /* Directory filetype incorrect */ +#define PR_2_CLEAR_FILETYPE 0x020029 /* Directory filetype set when it shouldn't be */ +#define PR_2_NULL_NAME 0x020030 /* Directory filename can't be zero-length */ +#define PR_2_INVALID_SYMLINK 0x020031 /* Invalid symlink */ +#define PR_2_FILE_ACL_BAD 0x020032 /* i_file_acl (extended attribute) is bad */ +#define PR_2_FEATURE_LARGE_FILES 0x020033 /* Filesystem contains large files, but has no such flag in sb */ +#define PR_2_HTREE_NOTREF 0x020034 /* Node in HTREE directory not referenced */ +#define PR_2_HTREE_DUPREF 0x020035 /* Node in HTREE directory referenced twice */ +#define PR_2_HTREE_MIN_HASH 0x020036 /* Node in HTREE directory has bad min hash */ +#define PR_2_HTREE_MAX_HASH 0x020037 /* Node in HTREE directory has bad max hash */ +#define PR_2_HTREE_CLEAR 0x020038 /* Clear invalid HTREE directory */ +#define PR_2_HTREE_BADBLK 0x02003A /* Bad block in htree interior node */ +#define PR_2_ADJ_EA_REFCOUNT 0x02003B /* Error adjusting EA refcount */ +#define PR_2_HTREE_BAD_ROOT 0x02003C /* Invalid HTREE root node */ +#define PR_2_HTREE_BAD_LIMIT 0x02003D /* Invalid HTREE limit */ +#define PR_2_HTREE_BAD_COUNT 0x02003E /* Invalid HTREE count */ +#define PR_2_HTREE_HASH_ORDER 0x02003F /* HTREE interior node has out-of-order hashes in table */ +#define PR_2_HTREE_BAD_DEPTH 0x020040 /* Node in HTREE directory has bad depth */ +#define PR_2_DUPLICATE_DIRENT 0x020041 /* Duplicate directory entry found */ +#define PR_2_NON_UNIQUE_FILE 0x020042 /* Non-unique filename found */ +#define PR_2_REPORT_DUP_DIRENT 0x020043 /* Duplicate directory entry found */ + +/* + * Pass 3 errors + */ + +#define PR_3_PASS_HEADER 0x030000 /* Pass 3: Checking directory connectivity */ +#define PR_3_NO_ROOT_INODE 0x030001 /* Root inode not allocated */ +#define PR_3_EXPAND_LF_DIR 0x030002 /* No room in lost+found */ +#define PR_3_UNCONNECTED_DIR 0x030003 /* Unconnected directory inode */ +#define PR_3_NO_LF_DIR 0x030004 /* /lost+found not found */ +#define PR_3_BAD_DOT_DOT 0x030005 /* .. entry is incorrect */ +#define PR_3_NO_LPF 0x030006 /* Bad or non-existent /lost+found. Cannot reconnect */ +#define PR_3_CANT_EXPAND_LPF 0x030007 /* Could not expand /lost+found */ +#define PR_3_CANT_RECONNECT 0x030008 /* Could not reconnect inode */ +#define PR_3_ERR_FIND_LPF 0x030009 /* Error while trying to find /lost+found */ +#define PR_3_ERR_LPF_NEW_BLOCK 0x03000A /* Error in ext2fs_new_block while creating /lost+found */ +#define PR_3_ERR_LPF_NEW_INODE 0x03000B /* Error in ext2fs_new_inode while creating /lost+found */ +#define PR_3_ERR_LPF_NEW_DIR_BLOCK 0x03000C /* Error in ext2fs_new_dir_block while creating /lost+found */ +#define PR_3_ERR_LPF_WRITE_BLOCK 0x03000D /* Error while writing directory block for /lost+found */ +#define PR_3_ADJUST_INODE 0x03000E /* Error while adjusting inode count */ +#define PR_3_FIX_PARENT_ERR 0x03000F /* Couldn't fix parent directory -- error */ +#define PR_3_FIX_PARENT_NOFIND 0x030010 /* Couldn't fix parent directory -- couldn't find it */ +#define PR_3_ALLOCATE_IBITMAP_ERROR 0x030011 /* Error allocating inode bitmap */ +#define PR_3_CREATE_ROOT_ERROR 0x030012 /* Error creating root directory */ +#define PR_3_CREATE_LPF_ERROR 0x030013 /* Error creating lost and found directory */ +#define PR_3_ROOT_NOT_DIR_ABORT 0x030014 /* Root inode is not directory; aborting */ +#define PR_3_NO_ROOT_INODE_ABORT 0x030015 /* Cannot proceed without a root inode. */ +#define PR_3_NO_DIRINFO 0x030016 /* Internal error: couldn't find dir_info */ +#define PR_3_LPF_NOTDIR 0x030017 /* Lost+found is not a directory */ + +/* + * Pass 3a --- rehashing diretories + */ +#define PR_3A_PASS_HEADER 0x031000 /* Pass 3a: Reindexing directories */ +#define PR_3A_OPTIMIZE_ITER 0x031001 /* Error iterating over directories */ +#define PR_3A_OPTIMIZE_DIR_ERR 0x031002 /* Error rehash directory */ +#define PR_3A_OPTIMIZE_DIR_HEADER 0x031003 /* Rehashing dir header */ +#define PR_3A_OPTIMIZE_DIR 0x031004 /* Rehashing directory %d */ +#define PR_3A_OPTIMIZE_DIR_END 0x031005 /* Rehashing dir end */ + +/* + * Pass 4 errors + */ + +#define PR_4_PASS_HEADER 0x040000 /* Pass 4: Checking reference counts */ +#define PR_4_ZERO_LEN_INODE 0x040001 /* Unattached zero-length inode */ +#define PR_4_UNATTACHED_INODE 0x040002 /* Unattached inode */ +#define PR_4_BAD_REF_COUNT 0x040003 /* Inode ref count wrong */ +#define PR_4_INCONSISTENT_COUNT 0x040004 /* Inconsistent inode count information cached */ + +/* + * Pass 5 errors + */ + +#define PR_5_PASS_HEADER 0x050000 /* Pass 5: Checking group summary information */ +#define PR_5_INODE_BMAP_PADDING 0x050001 /* Padding at end of inode bitmap is not set. */ +#define PR_5_BLOCK_BMAP_PADDING 0x050002 /* Padding at end of block bitmap is not set. */ +#define PR_5_BLOCK_BITMAP_HEADER 0x050003 /* Block bitmap differences header */ +#define PR_5_BLOCK_UNUSED 0x050004 /* Block not used, but marked in bitmap */ +#define PR_5_BLOCK_USED 0x050005 /* Block used, but not marked used in bitmap */ +#define PR_5_BLOCK_BITMAP_END 0x050006 /* Block bitmap differences end */ +#define PR_5_INODE_BITMAP_HEADER 0x050007 /* Inode bitmap differences header */ +#define PR_5_INODE_UNUSED 0x050008 /* Inode not used, but marked in bitmap */ +#define PR_5_INODE_USED 0x050009 /* Inode used, but not marked used in bitmap */ +#define PR_5_INODE_BITMAP_END 0x05000A /* Inode bitmap differences end */ +#define PR_5_FREE_INODE_COUNT_GROUP 0x05000B /* Free inodes count for group wrong */ +#define PR_5_FREE_DIR_COUNT_GROUP 0x05000C /* Directories count for group wrong */ +#define PR_5_FREE_INODE_COUNT 0x05000D /* Free inodes count wrong */ +#define PR_5_FREE_BLOCK_COUNT_GROUP 0x05000E /* Free blocks count for group wrong */ +#define PR_5_FREE_BLOCK_COUNT 0x05000F /* Free blocks count wrong */ +#define PR_5_BMAP_ENDPOINTS 0x050010 /* Programming error: bitmap endpoints don't match */ +#define PR_5_FUDGE_BITMAP_ERROR 0x050011 /* Internal error: fudging end of bitmap */ +#define PR_5_COPY_IBITMAP_ERROR 0x050012 /* Error copying in replacement inode bitmap */ +#define PR_5_COPY_BBITMAP_ERROR 0x050013 /* Error copying in replacement block bitmap */ +#define PR_5_BLOCK_RANGE_UNUSED 0x050014 /* Block range not used, but marked in bitmap */ +#define PR_5_BLOCK_RANGE_USED 0x050015 /* Block range used, but not marked used in bitmap */ +#define PR_5_INODE_RANGE_UNUSED 0x050016 /* Inode range not used, but marked in bitmap */ +#define PR_5_INODE_RANGE_USED 0x050017 /* Inode rangeused, but not marked used in bitmap */ + + +/* + * The directory information structure; stores directory information + * collected in earlier passes, to avoid disk i/o in fetching the + * directory information. + */ +struct dir_info { + ext2_ino_t ino; /* Inode number */ + ext2_ino_t dotdot; /* Parent according to '..' */ + ext2_ino_t parent; /* Parent according to treewalk */ +}; + + + +/* + * The indexed directory information structure; stores information for + * directories which contain a hash tree index. + */ +struct dx_dir_info { + ext2_ino_t ino; /* Inode number */ + int numblocks; /* number of blocks */ + int hashversion; + short depth; /* depth of tree */ + struct dx_dirblock_info *dx_block; /* Array of size numblocks */ +}; + +/* + * Define the extended attribute refcount structure + */ +typedef struct ea_refcount *ext2_refcount_t; + +struct e2fsck_struct { + ext2_filsys fs; + const char *program_name; + char *filesystem_name; + char *device_name; + char *io_options; + int flags; /* E2fsck internal flags */ + int options; + blk_t use_superblock; /* sb requested by user */ + blk_t superblock; /* sb used to open fs */ + int blocksize; /* blocksize */ + blk_t num_blocks; /* Total number of blocks */ + int mount_flags; + blkid_cache blkid; /* blkid cache */ + + jmp_buf abort_loc; + + unsigned long abort_code; + + int (*progress)(e2fsck_t ctx, int pass, unsigned long cur, + unsigned long max); + + ext2fs_inode_bitmap inode_used_map; /* Inodes which are in use */ + ext2fs_inode_bitmap inode_bad_map; /* Inodes which are bad somehow */ + ext2fs_inode_bitmap inode_dir_map; /* Inodes which are directories */ + ext2fs_inode_bitmap inode_imagic_map; /* AFS inodes */ + ext2fs_inode_bitmap inode_reg_map; /* Inodes which are regular files*/ + + ext2fs_block_bitmap block_found_map; /* Blocks which are in use */ + ext2fs_block_bitmap block_dup_map; /* Blks referenced more than once */ + ext2fs_block_bitmap block_ea_map; /* Blocks which are used by EA's */ + + /* + * Inode count arrays + */ + ext2_icount_t inode_count; + ext2_icount_t inode_link_info; + + ext2_refcount_t refcount; + ext2_refcount_t refcount_extra; + + /* + * Array of flags indicating whether an inode bitmap, block + * bitmap, or inode table is invalid + */ + int *invalid_inode_bitmap_flag; + int *invalid_block_bitmap_flag; + int *invalid_inode_table_flag; + int invalid_bitmaps; /* There are invalid bitmaps/itable */ + + /* + * Block buffer + */ + char *block_buf; + + /* + * For pass1_check_directory and pass1_get_blocks + */ + ext2_ino_t stashed_ino; + struct ext2_inode *stashed_inode; + + /* + * Location of the lost and found directory + */ + ext2_ino_t lost_and_found; + int bad_lost_and_found; + + /* + * Directory information + */ + int dir_info_count; + int dir_info_size; + struct dir_info *dir_info; + + /* + * Indexed directory information + */ + int dx_dir_info_count; + int dx_dir_info_size; + struct dx_dir_info *dx_dir_info; + + /* + * Directories to hash + */ + ext2_u32_list dirs_to_hash; + + /* + * Tuning parameters + */ + int process_inode_size; + int inode_buffer_blocks; + + /* + * ext3 journal support + */ + io_channel journal_io; + char *journal_name; + + /* + * How we display the progress update (for unix) + */ + int progress_fd; + int progress_pos; + int progress_last_percent; + unsigned int progress_last_time; + int interactive; /* Are we connected directly to a tty? */ + char start_meta[2], stop_meta[2]; + + /* File counts */ + int fs_directory_count; + int fs_regular_count; + int fs_blockdev_count; + int fs_chardev_count; + int fs_links_count; + int fs_symlinks_count; + int fs_fast_symlinks_count; + int fs_fifo_count; + int fs_total_count; + int fs_sockets_count; + int fs_ind_count; + int fs_dind_count; + int fs_tind_count; + int fs_fragmented; + int large_files; + int fs_ext_attr_inodes; + int fs_ext_attr_blocks; + + int ext_attr_ver; + + /* + * For the use of callers of the e2fsck functions; not used by + * e2fsck functions themselves. + */ + void *priv_data; +}; + + +#define tid_gt(x, y) ((x - y) > 0) + +static inline int tid_geq(tid_t x, tid_t y) +{ + int difference = (x - y); + return (difference >= 0); +} + + diff --git a/e2fsprogs/e2p/Kbuild b/e2fsprogs/e2p/Kbuild new file mode 100644 index 000000000..c0ff824e3 --- /dev/null +++ b/e2fsprogs/e2p/Kbuild @@ -0,0 +1,15 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +NEEDED-$(CONFIG_CHATTR) = y +NEEDED-$(CONFIG_LSATTR) = y +NEEDED-$(CONFIG_MKE2FS) = y +NEEDED-$(CONFIG_TUNE2FS) = y + +lib-y:= +lib-$(NEEDED-y) += fgetsetflags.o fgetsetversion.o pf.o iod.o mntopts.o \ + feature.o ls.o uuid.o pe.o ostype.o ps.o hashstr.o \ + parse_num.o diff --git a/e2fsprogs/e2p/e2p.h b/e2fsprogs/e2p/e2p.h new file mode 100644 index 000000000..2a2367b9f --- /dev/null +++ b/e2fsprogs/e2p/e2p.h @@ -0,0 +1,64 @@ +/* vi: set sw=4 ts=4: */ +#include "busybox.h" +#include /* Needed by dirent.h on netbsd */ +#include +#include + +#include "../ext2fs/ext2_fs.h" + +#define E2P_FEATURE_COMPAT 0 +#define E2P_FEATURE_INCOMPAT 1 +#define E2P_FEATURE_RO_INCOMPAT 2 +#ifndef EXT3_FEATURE_INCOMPAT_EXTENTS +#define EXT3_FEATURE_INCOMPAT_EXTENTS 0x0040 +#endif + +/* `options' for print_flags() */ + +#define PFOPT_LONG 1 /* Must be 1 for compatibility with `int long_format'. */ + +/*int fgetversion (const char * name, unsigned long * version);*/ +/*int fsetversion (const char * name, unsigned long version);*/ +int fgetsetversion(const char * name, unsigned long * get_version, unsigned long set_version); +#define fgetversion(name, version) fgetsetversion(name, version, 0) +#define fsetversion(name, version) fgetsetversion(name, NULL, version) + +/*int fgetflags (const char * name, unsigned long * flags);*/ +/*int fsetflags (const char * name, unsigned long flags);*/ +int fgetsetflags(const char * name, unsigned long * get_flags, unsigned long set_flags); +#define fgetflags(name, flags) fgetsetflags(name, flags, 0) +#define fsetflags(name, flags) fgetsetflags(name, NULL, flags) + +int getflags (int fd, unsigned long * flags); +int getversion (int fd, unsigned long * version); +int iterate_on_dir (const char * dir_name, + int (*func) (const char *, struct dirent *, void *), + void * private); +/*void list_super(struct ext2_super_block * s);*/ +void list_super2(struct ext2_super_block * s, FILE *f); +#define list_super(s) list_super2(s, stdout) +void print_fs_errors (FILE * f, unsigned short errors); +void print_flags (FILE * f, unsigned long flags, unsigned options); +void print_fs_state (FILE * f, unsigned short state); +int setflags (int fd, unsigned long flags); +int setversion (int fd, unsigned long version); + +const char *e2p_feature2string(int compat, unsigned int mask); +int e2p_string2feature(char *string, int *compat, unsigned int *mask); +int e2p_edit_feature(const char *str, __u32 *compat_array, __u32 *ok_array); + +int e2p_is_null_uuid(void *uu); +void e2p_uuid_to_str(void *uu, char *out); +const char *e2p_uuid2str(void *uu); + +const char *e2p_hash2string(int num); +int e2p_string2hash(char *string); + +const char *e2p_mntopt2string(unsigned int mask); +int e2p_string2mntopt(char *string, unsigned int *mask); +int e2p_edit_mntopts(const char *str, __u32 *mntopts, __u32 ok); + +unsigned long parse_num_blocks(const char *arg, int log_block_size); + +char *e2p_os2string(int os_type); +int e2p_string2os(char *str); diff --git a/e2fsprogs/e2p/feature.c b/e2fsprogs/e2p/feature.c new file mode 100644 index 000000000..b45754f97 --- /dev/null +++ b/e2fsprogs/e2p/feature.c @@ -0,0 +1,187 @@ +/* vi: set sw=4 ts=4: */ +/* + * feature.c --- convert between features and strings + * + * Copyright (C) 1999 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + * + */ + +#include +#include +#include +#include +#include + +#include "e2p.h" + +struct feature { + int compat; + unsigned int mask; + const char *string; +}; + +static const struct feature feature_list[] = { + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_DIR_PREALLOC, + "dir_prealloc" }, + { E2P_FEATURE_COMPAT, EXT3_FEATURE_COMPAT_HAS_JOURNAL, + "has_journal" }, + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_IMAGIC_INODES, + "imagic_inodes" }, + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_EXT_ATTR, + "ext_attr" }, + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_DIR_INDEX, + "dir_index" }, + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_RESIZE_INODE, + "resize_inode" }, + { E2P_FEATURE_RO_INCOMPAT, EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER, + "sparse_super" }, + { E2P_FEATURE_RO_INCOMPAT, EXT2_FEATURE_RO_COMPAT_LARGE_FILE, + "large_file" }, + { E2P_FEATURE_INCOMPAT, EXT2_FEATURE_INCOMPAT_COMPRESSION, + "compression" }, + { E2P_FEATURE_INCOMPAT, EXT2_FEATURE_INCOMPAT_FILETYPE, + "filetype" }, + { E2P_FEATURE_INCOMPAT, EXT3_FEATURE_INCOMPAT_RECOVER, + "needs_recovery" }, + { E2P_FEATURE_INCOMPAT, EXT3_FEATURE_INCOMPAT_JOURNAL_DEV, + "journal_dev" }, + { E2P_FEATURE_INCOMPAT, EXT3_FEATURE_INCOMPAT_EXTENTS, + "extents" }, + { E2P_FEATURE_INCOMPAT, EXT2_FEATURE_INCOMPAT_META_BG, + "meta_bg" }, + { 0, 0, 0 }, +}; + +const char *e2p_feature2string(int compat, unsigned int mask) +{ + const struct feature *f; + static char buf[20]; + char fchar; + int fnum; + + for (f = feature_list; f->string; f++) { + if ((compat == f->compat) && + (mask == f->mask)) + return f->string; + } + switch (compat) { + case E2P_FEATURE_COMPAT: + fchar = 'C'; + break; + case E2P_FEATURE_INCOMPAT: + fchar = 'I'; + break; + case E2P_FEATURE_RO_INCOMPAT: + fchar = 'R'; + break; + default: + fchar = '?'; + break; + } + for (fnum = 0; mask >>= 1; fnum++); + sprintf(buf, "FEATURE_%c%d", fchar, fnum); + return buf; +} + +int e2p_string2feature(char *string, int *compat_type, unsigned int *mask) +{ + const struct feature *f; + char *eptr; + int num; + + for (f = feature_list; f->string; f++) { + if (!strcasecmp(string, f->string)) { + *compat_type = f->compat; + *mask = f->mask; + return 0; + } + } + if (strncasecmp(string, "FEATURE_", 8)) + return 1; + + switch (string[8]) { + case 'c': + case 'C': + *compat_type = E2P_FEATURE_COMPAT; + break; + case 'i': + case 'I': + *compat_type = E2P_FEATURE_INCOMPAT; + break; + case 'r': + case 'R': + *compat_type = E2P_FEATURE_RO_INCOMPAT; + break; + default: + return 1; + } + if (string[9] == 0) + return 1; + num = strtol(string+9, &eptr, 10); + if (num > 32 || num < 0) + return 1; + if (*eptr) + return 1; + *mask = 1 << num; + return 0; +} + +static inline char *skip_over_blanks(char *cp) +{ + while (*cp && isspace(*cp)) + cp++; + return cp; +} + +static inline char *skip_over_word(char *cp) +{ + while (*cp && !isspace(*cp) && *cp != ',') + cp++; + return cp; +} + +/* + * Edit a feature set array as requested by the user. The ok_array, + * if set, allows the application to limit what features the user is + * allowed to set or clear using this function. + */ +int e2p_edit_feature(const char *str, __u32 *compat_array, __u32 *ok_array) +{ + char *cp, *buf, *next; + int neg; + unsigned int mask; + int compat_type; + + buf = xstrdup(str); + cp = buf; + while (cp && *cp) { + neg = 0; + cp = skip_over_blanks(cp); + next = skip_over_word(cp); + if (*next == 0) + next = 0; + else + *next = 0; + switch (*cp) { + case '-': + case '^': + neg++; + case '+': + cp++; + break; + } + if (e2p_string2feature(cp, &compat_type, &mask)) + return 1; + if (ok_array && !(ok_array[compat_type] & mask)) + return 1; + if (neg) + compat_array[compat_type] &= ~mask; + else + compat_array[compat_type] |= mask; + cp = next ? next+1 : 0; + } + return 0; +} diff --git a/e2fsprogs/e2p/fgetsetflags.c b/e2fsprogs/e2p/fgetsetflags.c new file mode 100644 index 000000000..008b79850 --- /dev/null +++ b/e2fsprogs/e2p/fgetsetflags.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: */ +/* + * fgetflags.c - Get a file flags on an ext2 file system + * fsetflags.c - Set a file flags on an ext2 file system + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + */ + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#ifdef HAVE_EXT2_IOCTLS +#include +#include +#endif + +#include "e2p.h" + +#ifdef O_LARGEFILE +#define OPEN_FLAGS (O_RDONLY|O_NONBLOCK|O_LARGEFILE) +#else +#define OPEN_FLAGS (O_RDONLY|O_NONBLOCK) +#endif + +int fgetsetflags (const char * name, unsigned long * get_flags, unsigned long set_flags) +{ +#ifdef HAVE_EXT2_IOCTLS + struct stat buf; + int fd, r, f, save_errno = 0; + + if (!stat(name, &buf) && + !S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode)) { + goto notsupp; + } + fd = open (name, OPEN_FLAGS); + if (fd == -1) + return -1; + if (!get_flags) { + f = (int) set_flags; + r = ioctl (fd, EXT2_IOC_SETFLAGS, &f); + } else { + r = ioctl (fd, EXT2_IOC_GETFLAGS, &f); + *get_flags = f; + } + if (r == -1) + save_errno = errno; + close (fd); + if (save_errno) + errno = save_errno; + return r; +notsupp: +#endif /* HAVE_EXT2_IOCTLS */ + errno = EOPNOTSUPP; + return -1; +} diff --git a/e2fsprogs/e2p/fgetsetversion.c b/e2fsprogs/e2p/fgetsetversion.c new file mode 100644 index 000000000..8d79054d6 --- /dev/null +++ b/e2fsprogs/e2p/fgetsetversion.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: */ +/* + * fgetversion.c - Get a file version on an ext2 file system + * fsetversion.c - Set a file version on an ext2 file system + * + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + */ + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "e2p.h" + +#ifdef O_LARGEFILE +#define OPEN_FLAGS (O_RDONLY|O_NONBLOCK|O_LARGEFILE) +#else +#define OPEN_FLAGS (O_RDONLY|O_NONBLOCK) +#endif + +/* + To do fsetversion: unsigned long *ptr_version must be set to NULL. + and unsigned long version must be set to a value + To do fgetversion: unsigned long *ptr_version must NOT be set to NULL + and unsigned long version is ignored. + TITO. +*/ + +int fgetsetversion (const char * name, unsigned long * get_version, unsigned long set_version) +{ +#ifdef HAVE_EXT2_IOCTLS + int fd, r, ver, save_errno = 0; + + fd = open (name, OPEN_FLAGS); + if (fd == -1) + return -1; + if (!get_version) { + ver = (int) set_version; + r = ioctl (fd, EXT2_IOC_SETVERSION, &ver); + } else { + r = ioctl (fd, EXT2_IOC_GETVERSION, &ver); + *get_version = ver; + } + if (r == -1) + save_errno = errno; + close (fd); + if (save_errno) + errno = save_errno; + return r; +#else /* ! HAVE_EXT2_IOCTLS */ + errno = EOPNOTSUPP; + return -1; +#endif /* ! HAVE_EXT2_IOCTLS */ +} diff --git a/e2fsprogs/e2p/hashstr.c b/e2fsprogs/e2p/hashstr.c new file mode 100644 index 000000000..697ffadc3 --- /dev/null +++ b/e2fsprogs/e2p/hashstr.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: */ +/* + * feature.c --- convert between features and strings + * + * Copyright (C) 1999 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + * + */ + +#include +#include +#include +#include +#include + +#include "e2p.h" + +struct hash { + int num; + const char *string; +}; + +static const struct hash hash_list[] = { + { EXT2_HASH_LEGACY, "legacy" }, + { EXT2_HASH_HALF_MD4, "half_md4" }, + { EXT2_HASH_TEA, "tea" }, + { 0, 0 }, +}; + +const char *e2p_hash2string(int num) +{ + const struct hash *p; + static char buf[20]; + + for (p = hash_list; p->string; p++) { + if (num == p->num) + return p->string; + } + sprintf(buf, "HASHALG_%d", num); + return buf; +} + +/* + * Returns the hash algorithm, or -1 on error + */ +int e2p_string2hash(char *string) +{ + const struct hash *p; + char *eptr; + int num; + + for (p = hash_list; p->string; p++) { + if (!strcasecmp(string, p->string)) { + return p->num; + } + } + if (strncasecmp(string, "HASHALG_", 8)) + return -1; + + if (string[8] == 0) + return -1; + num = strtol(string+8, &eptr, 10); + if (num > 255 || num < 0) + return -1; + if (*eptr) + return -1; + return num; +} diff --git a/e2fsprogs/e2p/iod.c b/e2fsprogs/e2p/iod.c new file mode 100644 index 000000000..8d4c5cdcb --- /dev/null +++ b/e2fsprogs/e2p/iod.c @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: */ +/* + * iod.c - Iterate a function on each entry of a directory + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + */ + +#include "e2p.h" +#include +#include +#include + +int iterate_on_dir (const char * dir_name, + int (*func) (const char *, struct dirent *, void *), + void * private) +{ + DIR * dir; + struct dirent *de, *dep; + int max_len, len; + + max_len = PATH_MAX + sizeof(struct dirent); + de = (struct dirent *)xmalloc(max_len+1); + memset(de, 0, max_len+1); + + dir = opendir (dir_name); + if (dir == NULL) { + free(de); + return -1; + } + while ((dep = readdir (dir))) { + len = sizeof(struct dirent); + if (len < dep->d_reclen) + len = dep->d_reclen; + if (len > max_len) + len = max_len; + memcpy(de, dep, len); + (*func) (dir_name, de, private); + } + free(de); + closedir(dir); + return 0; +} diff --git a/e2fsprogs/e2p/ls.c b/e2fsprogs/e2p/ls.c new file mode 100644 index 000000000..9d29db6af --- /dev/null +++ b/e2fsprogs/e2p/ls.c @@ -0,0 +1,273 @@ +/* vi: set sw=4 ts=4: */ +/* + * ls.c - List the contents of an ext2fs superblock + * + * Copyright (C) 1992, 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * Copyright (C) 1995, 1996, 1997 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "e2p.h" + +static void print_user(unsigned short uid, FILE *f) +{ + struct passwd *pw = getpwuid(uid); + fprintf(f, "%u (user %s)\n", uid, + (pw == NULL ? "unknown" : pw->pw_name)); +} + +static void print_group(unsigned short gid, FILE *f) +{ + struct group *gr = getgrgid(gid); + fprintf(f, "%u (group %s)\n", gid, + (gr == NULL ? "unknown" : gr->gr_name)); +} + +#define MONTH_INT (86400 * 30) +#define WEEK_INT (86400 * 7) +#define DAY_INT (86400) +#define HOUR_INT (60 * 60) +#define MINUTE_INT (60) + +static const char *interval_string(unsigned int secs) +{ + static char buf[256], tmp[80]; + int hr, min, num; + + buf[0] = 0; + + if (secs == 0) + return ""; + + if (secs >= MONTH_INT) { + num = secs / MONTH_INT; + secs -= num*MONTH_INT; + sprintf(buf, "%d month%s", num, (num>1) ? "s" : ""); + } + if (secs >= WEEK_INT) { + num = secs / WEEK_INT; + secs -= num*WEEK_INT; + sprintf(tmp, "%s%d week%s", buf[0] ? ", " : "", + num, (num>1) ? "s" : ""); + strcat(buf, tmp); + } + if (secs >= DAY_INT) { + num = secs / DAY_INT; + secs -= num*DAY_INT; + sprintf(tmp, "%s%d day%s", buf[0] ? ", " : "", + num, (num>1) ? "s" : ""); + strcat(buf, tmp); + } + if (secs > 0) { + hr = secs / HOUR_INT; + secs -= hr*HOUR_INT; + min = secs / MINUTE_INT; + secs -= min*MINUTE_INT; + sprintf(tmp, "%s%d:%02d:%02d", buf[0] ? ", " : "", + hr, min, secs); + strcat(buf, tmp); + } + return buf; +} + +static void print_features(struct ext2_super_block * s, FILE *f) +{ +#ifdef EXT2_DYNAMIC_REV + int i, j, printed=0; + __u32 *mask = &s->s_feature_compat, m; + + fprintf(f, "Filesystem features: "); + for (i=0; i <3; i++,mask++) { + for (j=0,m=1; j < 32; j++, m<<=1) { + if (*mask & m) { + fprintf(f, " %s", e2p_feature2string(i, m)); + printed++; + } + } + } + if (printed == 0) + fprintf(f, " (none)"); + fprintf(f, "\n"); +#endif +} + +static void print_mntopts(struct ext2_super_block * s, FILE *f) +{ +#ifdef EXT2_DYNAMIC_REV + int i, printed=0; + __u32 mask = s->s_default_mount_opts, m; + + fprintf(f, "Default mount options: "); + if (mask & EXT3_DEFM_JMODE) { + fprintf(f, " %s", e2p_mntopt2string(mask & EXT3_DEFM_JMODE)); + printed++; + } + for (i=0,m=1; i < 32; i++, m<<=1) { + if (m & EXT3_DEFM_JMODE) + continue; + if (mask & m) { + fprintf(f, " %s", e2p_mntopt2string(m)); + printed++; + } + } + if (printed == 0) + fprintf(f, " (none)"); + fprintf(f, "\n"); +#endif +} + + +#ifndef EXT2_INODE_SIZE +#define EXT2_INODE_SIZE(s) sizeof(struct ext2_inode) +#endif + +#ifndef EXT2_GOOD_OLD_REV +#define EXT2_GOOD_OLD_REV 0 +#endif + +void list_super2(struct ext2_super_block * sb, FILE *f) +{ + int inode_blocks_per_group; + char buf[80], *str; + time_t tm; + + inode_blocks_per_group = (((sb->s_inodes_per_group * + EXT2_INODE_SIZE(sb)) + + EXT2_BLOCK_SIZE(sb) - 1) / + EXT2_BLOCK_SIZE(sb)); + if (sb->s_volume_name[0]) { + memset(buf, 0, sizeof(buf)); + strncpy(buf, sb->s_volume_name, sizeof(sb->s_volume_name)); + } else + strcpy(buf, ""); + fprintf(f, "Filesystem volume name: %s\n", buf); + if (sb->s_last_mounted[0]) { + memset(buf, 0, sizeof(buf)); + strncpy(buf, sb->s_last_mounted, sizeof(sb->s_last_mounted)); + } else + strcpy(buf, ""); + fprintf(f, + "Last mounted on: %s\n" + "Filesystem UUID: %s\n" + "Filesystem magic number: 0x%04X\n" + "Filesystem revision #: %d", + buf, e2p_uuid2str(sb->s_uuid), sb->s_magic, sb->s_rev_level); + if (sb->s_rev_level == EXT2_GOOD_OLD_REV) { + fprintf(f, " (original)\n"); +#ifdef EXT2_DYNAMIC_REV + } else if (sb->s_rev_level == EXT2_DYNAMIC_REV) { + fprintf(f, " (dynamic)\n"); +#endif + } else + fprintf(f, " (unknown)\n"); + print_features(sb, f); + print_mntopts(sb, f); + fprintf(f, "Filesystem state: "); + print_fs_state (f, sb->s_state); + fprintf(f, "\nErrors behavior: "); + print_fs_errors(f, sb->s_errors); + str = e2p_os2string(sb->s_creator_os); + fprintf(f, + "\n" + "Filesystem OS type: %s\n" + "Inode count: %u\n" + "Block count: %u\n" + "Reserved block count: %u\n" + "Free blocks: %u\n" + "Free inodes: %u\n" + "First block: %u\n" + "Block size: %u\n" + "Fragment size: %u\n", + str, sb->s_inodes_count, sb->s_blocks_count, sb->s_r_blocks_count, + sb->s_free_blocks_count, sb->s_free_inodes_count, + sb->s_first_data_block, EXT2_BLOCK_SIZE(sb), EXT2_FRAG_SIZE(sb)); + free(str); + if (sb->s_reserved_gdt_blocks) + fprintf(f, "Reserved GDT blocks: %u\n", + sb->s_reserved_gdt_blocks); + fprintf(f, + "Blocks per group: %u\n" + "Fragments per group: %u\n" + "Inodes per group: %u\n" + "Inode blocks per group: %u\n", + sb->s_blocks_per_group, sb->s_frags_per_group, + sb->s_inodes_per_group, inode_blocks_per_group); + if (sb->s_first_meta_bg) + fprintf(f, "First meta block group: %u\n", + sb->s_first_meta_bg); + if (sb->s_mkfs_time) { + tm = sb->s_mkfs_time; + fprintf(f, "Filesystem created: %s", ctime(&tm)); + } + tm = sb->s_mtime; + fprintf(f, "Last mount time: %s", + sb->s_mtime ? ctime(&tm) : "n/a\n"); + tm = sb->s_wtime; + fprintf(f, + "Last write time: %s" + "Mount count: %u\n" + "Maximum mount count: %d\n", + ctime(&tm), sb->s_mnt_count, sb->s_max_mnt_count); + tm = sb->s_lastcheck; + fprintf(f, + "Last checked: %s" + "Check interval: %u (%s)\n", + ctime(&tm), + sb->s_checkinterval, interval_string(sb->s_checkinterval)); + if (sb->s_checkinterval) + { + time_t next; + + next = sb->s_lastcheck + sb->s_checkinterval; + fprintf(f, "Next check after: %s", ctime(&next)); + } + fprintf(f, "Reserved blocks uid: "); + print_user(sb->s_def_resuid, f); + fprintf(f, "Reserved blocks gid: "); + print_group(sb->s_def_resgid, f); + if (sb->s_rev_level >= EXT2_DYNAMIC_REV) { + fprintf(f, + "First inode: %d\n" + "Inode size: %d\n", + sb->s_first_ino, sb->s_inode_size); + } + if (!e2p_is_null_uuid(sb->s_journal_uuid)) + fprintf(f, "Journal UUID: %s\n", + e2p_uuid2str(sb->s_journal_uuid)); + if (sb->s_journal_inum) + fprintf(f, "Journal inode: %u\n", + sb->s_journal_inum); + if (sb->s_journal_dev) + fprintf(f, "Journal device: 0x%04x\n", + sb->s_journal_dev); + if (sb->s_last_orphan) + fprintf(f, "First orphan inode: %u\n", + sb->s_last_orphan); + if ((sb->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) || + sb->s_def_hash_version) + fprintf(f, "Default directory hash: %s\n", + e2p_hash2string(sb->s_def_hash_version)); + if (!e2p_is_null_uuid(sb->s_hash_seed)) + fprintf(f, "Directory Hash Seed: %s\n", + e2p_uuid2str(sb->s_hash_seed)); + if (sb->s_jnl_backup_type) { + fprintf(f, "Journal backup: "); + if (sb->s_jnl_backup_type == 1) + fprintf(f, "inode blocks\n"); + else + fprintf(f, "type %u\n", sb->s_jnl_backup_type); + } +} diff --git a/e2fsprogs/e2p/mntopts.c b/e2fsprogs/e2p/mntopts.c new file mode 100644 index 000000000..17c26c480 --- /dev/null +++ b/e2fsprogs/e2p/mntopts.c @@ -0,0 +1,134 @@ +/* vi: set sw=4 ts=4: */ +/* + * mountopts.c --- convert between default mount options and strings + * + * Copyright (C) 2002 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + * + */ + +#include +#include +#include +#include +#include + +#include "e2p.h" + +struct mntopt { + unsigned int mask; + const char *string; +}; + +static const struct mntopt mntopt_list[] = { + { EXT2_DEFM_DEBUG, "debug" }, + { EXT2_DEFM_BSDGROUPS, "bsdgroups" }, + { EXT2_DEFM_XATTR_USER, "user_xattr" }, + { EXT2_DEFM_ACL, "acl" }, + { EXT2_DEFM_UID16, "uid16" }, + { EXT3_DEFM_JMODE_DATA, "journal_data" }, + { EXT3_DEFM_JMODE_ORDERED, "journal_data_ordered" }, + { EXT3_DEFM_JMODE_WBACK, "journal_data_writeback" }, + { 0, 0 }, +}; + +const char *e2p_mntopt2string(unsigned int mask) +{ + const struct mntopt *f; + static char buf[20]; + int fnum; + + for (f = mntopt_list; f->string; f++) { + if (mask == f->mask) + return f->string; + } + for (fnum = 0; mask >>= 1; fnum++); + sprintf(buf, "MNTOPT_%d", fnum); + return buf; +} + +int e2p_string2mntopt(char *string, unsigned int *mask) +{ + const struct mntopt *f; + char *eptr; + int num; + + for (f = mntopt_list; f->string; f++) { + if (!strcasecmp(string, f->string)) { + *mask = f->mask; + return 0; + } + } + if (strncasecmp(string, "MNTOPT_", 8)) + return 1; + + if (string[8] == 0) + return 1; + num = strtol(string+8, &eptr, 10); + if (num > 32 || num < 0) + return 1; + if (*eptr) + return 1; + *mask = 1 << num; + return 0; +} + +static char *skip_over_blanks(char *cp) +{ + while (*cp && isspace(*cp)) + cp++; + return cp; +} + +static char *skip_over_word(char *cp) +{ + while (*cp && !isspace(*cp) && *cp != ',') + cp++; + return cp; +} + +/* + * Edit a mntopt set array as requested by the user. The ok + * parameter, if non-zero, allows the application to limit what + * mntopts the user is allowed to set or clear using this function. + */ +int e2p_edit_mntopts(const char *str, __u32 *mntopts, __u32 ok) +{ + char *cp, *buf, *next; + int neg; + unsigned int mask; + + buf = xstrdup(str); + cp = buf; + while (cp && *cp) { + neg = 0; + cp = skip_over_blanks(cp); + next = skip_over_word(cp); + if (*next == 0) + next = 0; + else + *next = 0; + switch (*cp) { + case '-': + case '^': + neg++; + case '+': + cp++; + break; + } + if (e2p_string2mntopt(cp, &mask)) + return 1; + if (ok && !(ok & mask)) + return 1; + if (mask & EXT3_DEFM_JMODE) + *mntopts &= ~EXT3_DEFM_JMODE; + if (neg) + *mntopts &= ~mask; + else + *mntopts |= mask; + cp = next ? next+1 : 0; + } + return 0; +} diff --git a/e2fsprogs/e2p/ostype.c b/e2fsprogs/e2p/ostype.c new file mode 100644 index 000000000..0e111d408 --- /dev/null +++ b/e2fsprogs/e2p/ostype.c @@ -0,0 +1,74 @@ +/* vi: set sw=4 ts=4: */ +/* + * getostype.c - Get the Filesystem OS type + * + * Copyright (C) 2004,2005 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +#include "e2p.h" +#include +#include + +static const char * const os_tab[] = + { "Linux", + "Hurd", + "Masix", + "FreeBSD", + "Lites", + 0 }; + +/* + * Convert an os_type to a string + */ +char *e2p_os2string(int os_type) +{ + const char *os; + char *ret; + + if (os_type <= EXT2_OS_LITES) + os = os_tab[os_type]; + else + os = "(unknown os)"; + + ret = xstrdup(os); + return ret; +} + +/* + * Convert an os_type to a string + */ +int e2p_string2os(char *str) +{ + const char * const *cpp; + int i = 0; + + for (cpp = os_tab; *cpp; cpp++, i++) { + if (!strcasecmp(str, *cpp)) + return i; + } + return -1; +} + +#ifdef TEST_PROGRAM +int main(int argc, char **argv) +{ + char *s; + int i, os; + + for (i=0; i <= EXT2_OS_LITES; i++) { + s = e2p_os2string(i); + os = e2p_string2os(s); + printf("%d: %s (%d)\n", i, s, os); + if (i != os) { + fprintf(stderr, "Failure!\n"); + exit(1); + } + } + exit(0); +} +#endif + + diff --git a/e2fsprogs/e2p/parse_num.c b/e2fsprogs/e2p/parse_num.c new file mode 100644 index 000000000..6db076f9c --- /dev/null +++ b/e2fsprogs/e2p/parse_num.c @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * parse_num.c - Parse the number of blocks + * + * Copyright (C) 2004,2005 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +#include "e2p.h" + +#include + +unsigned long parse_num_blocks(const char *arg, int log_block_size) +{ + char *p; + unsigned long long num; + + num = strtoull(arg, &p, 0); + + if (p[0] && p[1]) + return 0; + + switch (*p) { /* Using fall-through logic */ + case 'T': case 't': + num <<= 10; + case 'G': case 'g': + num <<= 10; + case 'M': case 'm': + num <<= 10; + case 'K': case 'k': + num >>= log_block_size; + break; + case 's': + num >>= 1; + break; + case '\0': + break; + default: + return 0; + } + return num; +} + +#ifdef DEBUG +#include +#include + +main(int argc, char **argv) +{ + unsigned long num; + int log_block_size = 0; + + if (argc != 2) { + fprintf(stderr, "Usage: %s arg\n", argv[0]); + exit(1); + } + + num = parse_num_blocks(argv[1], log_block_size); + + printf("Parsed number: %lu\n", num); + exit(0); +} +#endif diff --git a/e2fsprogs/e2p/pe.c b/e2fsprogs/e2p/pe.c new file mode 100644 index 000000000..835274b54 --- /dev/null +++ b/e2fsprogs/e2p/pe.c @@ -0,0 +1,32 @@ +/* vi: set sw=4 ts=4: */ +/* + * pe.c - Print a second extended filesystem errors behavior + * + * Copyright (C) 1992, 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 94/01/09 - Creation + */ + +#include + +#include "e2p.h" + +void print_fs_errors(FILE *f, unsigned short errors) +{ + char *disp = NULL; + switch (errors) { + case EXT2_ERRORS_CONTINUE: disp = "Continue"; break; + case EXT2_ERRORS_RO: disp = "Remount read-only"; break; + case EXT2_ERRORS_PANIC: disp = "Panic"; break; + default: disp = "Unknown (continue)"; + } + fprintf(f, disp); +} diff --git a/e2fsprogs/e2p/pf.c b/e2fsprogs/e2p/pf.c new file mode 100644 index 000000000..55d4bc487 --- /dev/null +++ b/e2fsprogs/e2p/pf.c @@ -0,0 +1,74 @@ +/* vi: set sw=4 ts=4: */ +/* + * pf.c - Print file attributes on an ext2 file system + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + */ + +#include + +#include "e2p.h" + +struct flags_name { + unsigned long flag; + const char *short_name; + const char *long_name; +}; + +static const struct flags_name flags_array[] = { + { EXT2_SECRM_FL, "s", "Secure_Deletion" }, + { EXT2_UNRM_FL, "u" , "Undelete" }, + { EXT2_SYNC_FL, "S", "Synchronous_Updates" }, + { EXT2_DIRSYNC_FL, "D", "Synchronous_Directory_Updates" }, + { EXT2_IMMUTABLE_FL, "i", "Immutable" }, + { EXT2_APPEND_FL, "a", "Append_Only" }, + { EXT2_NODUMP_FL, "d", "No_Dump" }, + { EXT2_NOATIME_FL, "A", "No_Atime" }, + { EXT2_COMPR_FL, "c", "Compression_Requested" }, +#ifdef ENABLE_COMPRESSION + { EXT2_COMPRBLK_FL, "B", "Compressed_File" }, + { EXT2_DIRTY_FL, "Z", "Compressed_Dirty_File" }, + { EXT2_NOCOMPR_FL, "X", "Compression_Raw_Access" }, + { EXT2_ECOMPR_FL, "E", "Compression_Error" }, +#endif + { EXT3_JOURNAL_DATA_FL, "j", "Journaled_Data" }, + { EXT2_INDEX_FL, "I", "Indexed_direcctory" }, + { EXT2_NOTAIL_FL, "t", "No_Tailmerging" }, + { EXT2_TOPDIR_FL, "T", "Top_of_Directory_Hierarchies" }, + { 0, NULL, NULL } +}; + +void print_flags (FILE * f, unsigned long flags, unsigned options) +{ + int long_opt = (options & PFOPT_LONG); + const struct flags_name *fp; + int first = 1; + + for (fp = flags_array; fp->flag != 0; fp++) { + if (flags & fp->flag) { + if (long_opt) { + if (first) + first = 0; + else + fputs(", ", f); + fputs(fp->long_name, f); + } else + fputs(fp->short_name, f); + } else { + if (!long_opt) + fputs("-", f); + } + } + if (long_opt && first) + fputs("---", f); +} diff --git a/e2fsprogs/e2p/ps.c b/e2fsprogs/e2p/ps.c new file mode 100644 index 000000000..a6b4099db --- /dev/null +++ b/e2fsprogs/e2p/ps.c @@ -0,0 +1,27 @@ +/* vi: set sw=4 ts=4: */ +/* + * ps.c - Print filesystem state + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 93/12/22 - Creation + */ + +#include + +#include "e2p.h" + +void print_fs_state(FILE *f, unsigned short state) +{ + fprintf(f, (state & EXT2_VALID_FS ? " clean" : " not clean")); + if (state & EXT2_ERROR_FS) + fprintf(f, " with errors"); +} diff --git a/e2fsprogs/e2p/uuid.c b/e2fsprogs/e2p/uuid.c new file mode 100644 index 000000000..474d64a5a --- /dev/null +++ b/e2fsprogs/e2p/uuid.c @@ -0,0 +1,78 @@ +/* vi: set sw=4 ts=4: */ +/* + * uuid.c -- utility routines for manipulating UUID's. + */ + +#include +#include +#include "../ext2fs/ext2_types.h" + +#include "e2p.h" + +struct uuid { + __u32 time_low; + __u16 time_mid; + __u16 time_hi_and_version; + __u16 clock_seq; + __u8 node[6]; +}; + +/* Returns 1 if the uuid is the NULL uuid */ +int e2p_is_null_uuid(void *uu) +{ + __u8 *cp; + int i; + + for (i=0, cp = uu; i < 16; i++) + if (*cp) + return 0; + return 1; +} + +static void e2p_unpack_uuid(void *in, struct uuid *uu) +{ + __u8 *ptr = in; + __u32 tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_low = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_mid = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_hi_and_version = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->clock_seq = tmp; + + memcpy(uu->node, ptr, 6); +} + +void e2p_uuid_to_str(void *uu, char *out) +{ + struct uuid uuid; + + e2p_unpack_uuid(uu, &uuid); + sprintf(out, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid.time_low, uuid.time_mid, uuid.time_hi_and_version, + uuid.clock_seq >> 8, uuid.clock_seq & 0xFF, + uuid.node[0], uuid.node[1], uuid.node[2], + uuid.node[3], uuid.node[4], uuid.node[5]); +} + +const char *e2p_uuid2str(void *uu) +{ + static char buf[80]; + if (e2p_is_null_uuid(uu)) + return ""; + e2p_uuid_to_str(uu, buf); + return buf; +} diff --git a/e2fsprogs/ext2fs/Kbuild b/e2fsprogs/ext2fs/Kbuild new file mode 100644 index 000000000..185887a44 --- /dev/null +++ b/e2fsprogs/ext2fs/Kbuild @@ -0,0 +1,23 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +NEEDED-$(CONFIG_E2FSCK) = y +NEEDED-$(CONFIG_FSCK) = y +NEEDED-$(CONFIG_MKE2FS) = y +NEEDED-$(CONFIG_TUNE2FS) = y + +lib-y:= +lib-$(NEEDED-y) += gen_bitmap.o bitops.o ismounted.o mkjournal.o unix_io.o \ + rw_bitmaps.o initialize.o bitmaps.o block.o \ + ind_block.o inode.o freefs.o alloc_stats.o closefs.o \ + openfs.o io_manager.o finddev.o read_bb.o alloc.o badblocks.o \ + getsize.o getsectsize.o alloc_tables.o read_bb_file.o mkdir.o \ + bb_inode.o newdir.o alloc_sb.o lookup.o dirblock.o expanddir.o \ + dir_iterate.o link.o res_gdt.o icount.o get_pathname.o dblist.o \ + dirhash.o version.o flushb.o unlink.o check_desc.o valid_blk.o \ + ext_attr.o bmap.o dblist_dir.o ext2fs_inline.o swapfs.o + +CFLAGS += -include $(srctree)/e2fsprogs/e2fsbb.h diff --git a/e2fsprogs/ext2fs/alloc.c b/e2fsprogs/ext2fs/alloc.c new file mode 100644 index 000000000..590f23a7e --- /dev/null +++ b/e2fsprogs/ext2fs/alloc.c @@ -0,0 +1,174 @@ +/* vi: set sw=4 ts=4: */ +/* + * alloc.c --- allocate new inodes, blocks for ext2fs + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * Right now, just search forward from the parent directory's block + * group to find the next free inode. + * + * Should have a special policy for directories. + */ +errcode_t ext2fs_new_inode(ext2_filsys fs, ext2_ino_t dir, + int mode EXT2FS_ATTR((unused)), + ext2fs_inode_bitmap map, ext2_ino_t *ret) +{ + ext2_ino_t dir_group = 0; + ext2_ino_t i; + ext2_ino_t start_inode; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!map) + map = fs->inode_map; + if (!map) + return EXT2_ET_NO_INODE_BITMAP; + + if (dir > 0) + dir_group = (dir - 1) / EXT2_INODES_PER_GROUP(fs->super); + + start_inode = (dir_group * EXT2_INODES_PER_GROUP(fs->super)) + 1; + if (start_inode < EXT2_FIRST_INODE(fs->super)) + start_inode = EXT2_FIRST_INODE(fs->super); + i = start_inode; + + do { + if (!ext2fs_fast_test_inode_bitmap(map, i)) + break; + i++; + if (i > fs->super->s_inodes_count) + i = EXT2_FIRST_INODE(fs->super); + } while (i != start_inode); + + if (ext2fs_test_inode_bitmap(map, i)) + return EXT2_ET_INODE_ALLOC_FAIL; + *ret = i; + return 0; +} + +/* + * Stupid algorithm --- we now just search forward starting from the + * goal. Should put in a smarter one someday.... + */ +errcode_t ext2fs_new_block(ext2_filsys fs, blk_t goal, + ext2fs_block_bitmap map, blk_t *ret) +{ + blk_t i; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!map) + map = fs->block_map; + if (!map) + return EXT2_ET_NO_BLOCK_BITMAP; + if (!goal || (goal >= fs->super->s_blocks_count)) + goal = fs->super->s_first_data_block; + i = goal; + do { + if (!ext2fs_fast_test_block_bitmap(map, i)) { + *ret = i; + return 0; + } + i++; + if (i >= fs->super->s_blocks_count) + i = fs->super->s_first_data_block; + } while (i != goal); + return EXT2_ET_BLOCK_ALLOC_FAIL; +} + +/* + * This function zeros out the allocated block, and updates all of the + * appropriate filesystem records. + */ +errcode_t ext2fs_alloc_block(ext2_filsys fs, blk_t goal, + char *block_buf, blk_t *ret) +{ + errcode_t retval; + blk_t block; + char *buf = 0; + + if (!block_buf) { + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + block_buf = buf; + } + memset(block_buf, 0, fs->blocksize); + + if (!fs->block_map) { + retval = ext2fs_read_block_bitmap(fs); + if (retval) + goto fail; + } + + retval = ext2fs_new_block(fs, goal, 0, &block); + if (retval) + goto fail; + + retval = io_channel_write_blk(fs->io, block, 1, block_buf); + if (retval) + goto fail; + + ext2fs_block_alloc_stats(fs, block, +1); + *ret = block; + return 0; + +fail: + if (buf) + ext2fs_free_mem(&buf); + return retval; +} + +errcode_t ext2fs_get_free_blocks(ext2_filsys fs, blk_t start, blk_t finish, + int num, ext2fs_block_bitmap map, blk_t *ret) +{ + blk_t b = start; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!map) + map = fs->block_map; + if (!map) + return EXT2_ET_NO_BLOCK_BITMAP; + if (!b) + b = fs->super->s_first_data_block; + if (!finish) + finish = start; + if (!num) + num = 1; + do { + if (b+num-1 > fs->super->s_blocks_count) + b = fs->super->s_first_data_block; + if (ext2fs_fast_test_block_bitmap_range(map, b, num)) { + *ret = b; + return 0; + } + b++; + } while (b != finish); + return EXT2_ET_BLOCK_ALLOC_FAIL; +} + diff --git a/e2fsprogs/ext2fs/alloc_sb.c b/e2fsprogs/ext2fs/alloc_sb.c new file mode 100644 index 000000000..a7437c96f --- /dev/null +++ b/e2fsprogs/ext2fs/alloc_sb.c @@ -0,0 +1,58 @@ +/* vi: set sw=4 ts=4: */ +/* + * alloc_sb.c --- Allocate the superblock and block group descriptors for a + * newly initialized filesystem. Used by mke2fs when initializing a filesystem + * + * Copyright (C) 1994, 1995, 1996, 2003 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +int ext2fs_reserve_super_and_bgd(ext2_filsys fs, + dgrp_t group, + ext2fs_block_bitmap bmap) +{ + blk_t super_blk, old_desc_blk, new_desc_blk; + int j, old_desc_blocks, num_blocks; + + num_blocks = ext2fs_super_and_bgd_loc(fs, group, &super_blk, + &old_desc_blk, &new_desc_blk, 0); + + if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) + old_desc_blocks = fs->super->s_first_meta_bg; + else + old_desc_blocks = + fs->desc_blocks + fs->super->s_reserved_gdt_blocks; + + if (super_blk || (group == 0)) + ext2fs_mark_block_bitmap(bmap, super_blk); + + if (old_desc_blk) { + for (j=0; j < old_desc_blocks; j++) + ext2fs_mark_block_bitmap(bmap, old_desc_blk + j); + } + if (new_desc_blk) + ext2fs_mark_block_bitmap(bmap, new_desc_blk); + + return num_blocks; +} diff --git a/e2fsprogs/ext2fs/alloc_stats.c b/e2fsprogs/ext2fs/alloc_stats.c new file mode 100644 index 000000000..f3ab06a23 --- /dev/null +++ b/e2fsprogs/ext2fs/alloc_stats.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * alloc_stats.c --- Update allocation statistics for ext2fs + * + * Copyright (C) 2001 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +void ext2fs_inode_alloc_stats2(ext2_filsys fs, ext2_ino_t ino, + int inuse, int isdir) +{ + int group = ext2fs_group_of_ino(fs, ino); + + if (inuse > 0) + ext2fs_mark_inode_bitmap(fs->inode_map, ino); + else + ext2fs_unmark_inode_bitmap(fs->inode_map, ino); + fs->group_desc[group].bg_free_inodes_count -= inuse; + if (isdir) + fs->group_desc[group].bg_used_dirs_count += inuse; + fs->super->s_free_inodes_count -= inuse; + ext2fs_mark_super_dirty(fs); + ext2fs_mark_ib_dirty(fs); +} + +void ext2fs_inode_alloc_stats(ext2_filsys fs, ext2_ino_t ino, int inuse) +{ + ext2fs_inode_alloc_stats2(fs, ino, inuse, 0); +} + +void ext2fs_block_alloc_stats(ext2_filsys fs, blk_t blk, int inuse) +{ + int group = ext2fs_group_of_blk(fs, blk); + + if (inuse > 0) + ext2fs_mark_block_bitmap(fs->block_map, blk); + else + ext2fs_unmark_block_bitmap(fs->block_map, blk); + fs->group_desc[group].bg_free_blocks_count -= inuse; + fs->super->s_free_blocks_count -= inuse; + ext2fs_mark_super_dirty(fs); + ext2fs_mark_bb_dirty(fs); +} diff --git a/e2fsprogs/ext2fs/alloc_tables.c b/e2fsprogs/ext2fs/alloc_tables.c new file mode 100644 index 000000000..b2d786ed8 --- /dev/null +++ b/e2fsprogs/ext2fs/alloc_tables.c @@ -0,0 +1,118 @@ +/* vi: set sw=4 ts=4: */ +/* + * alloc_tables.c --- Allocate tables for a newly initialized + * filesystem. Used by mke2fs when initializing a filesystem + * + * Copyright (C) 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_allocate_group_table(ext2_filsys fs, dgrp_t group, + ext2fs_block_bitmap bmap) +{ + errcode_t retval; + blk_t group_blk, start_blk, last_blk, new_blk, blk; + int j; + + group_blk = fs->super->s_first_data_block + + (group * fs->super->s_blocks_per_group); + + last_blk = group_blk + fs->super->s_blocks_per_group; + if (last_blk >= fs->super->s_blocks_count) + last_blk = fs->super->s_blocks_count - 1; + + if (!bmap) + bmap = fs->block_map; + + /* + * Allocate the block and inode bitmaps, if necessary + */ + if (fs->stride) { + start_blk = group_blk + fs->inode_blocks_per_group; + start_blk += ((fs->stride * group) % + (last_blk - start_blk)); + if (start_blk > last_blk) + start_blk = group_blk; + } else + start_blk = group_blk; + + if (!fs->group_desc[group].bg_block_bitmap) { + retval = ext2fs_get_free_blocks(fs, start_blk, last_blk, + 1, bmap, &new_blk); + if (retval == EXT2_ET_BLOCK_ALLOC_FAIL) + retval = ext2fs_get_free_blocks(fs, group_blk, + last_blk, 1, bmap, &new_blk); + if (retval) + return retval; + ext2fs_mark_block_bitmap(bmap, new_blk); + fs->group_desc[group].bg_block_bitmap = new_blk; + } + + if (!fs->group_desc[group].bg_inode_bitmap) { + retval = ext2fs_get_free_blocks(fs, start_blk, last_blk, + 1, bmap, &new_blk); + if (retval == EXT2_ET_BLOCK_ALLOC_FAIL) + retval = ext2fs_get_free_blocks(fs, group_blk, + last_blk, 1, bmap, &new_blk); + if (retval) + return retval; + ext2fs_mark_block_bitmap(bmap, new_blk); + fs->group_desc[group].bg_inode_bitmap = new_blk; + } + + /* + * Allocate the inode table + */ + if (!fs->group_desc[group].bg_inode_table) { + retval = ext2fs_get_free_blocks(fs, group_blk, last_blk, + fs->inode_blocks_per_group, + bmap, &new_blk); + if (retval) + return retval; + for (j=0, blk = new_blk; + j < fs->inode_blocks_per_group; + j++, blk++) + ext2fs_mark_block_bitmap(bmap, blk); + fs->group_desc[group].bg_inode_table = new_blk; + } + + + return 0; +} + + + +errcode_t ext2fs_allocate_tables(ext2_filsys fs) +{ + errcode_t retval; + dgrp_t i; + + for (i = 0; i < fs->group_desc_count; i++) { + retval = ext2fs_allocate_group_table(fs, i, fs->block_map); + if (retval) + return retval; + } + return 0; +} + diff --git a/e2fsprogs/ext2fs/badblocks.c b/e2fsprogs/ext2fs/badblocks.c new file mode 100644 index 000000000..7804396d7 --- /dev/null +++ b/e2fsprogs/ext2fs/badblocks.c @@ -0,0 +1,328 @@ +/* vi: set sw=4 ts=4: */ +/* + * badblocks.c --- routines to manipulate the bad block structure + * + * Copyright (C) 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" + +/* + * Helper function for making a badblocks list + */ +static errcode_t make_u32_list(int size, int num, __u32 *list, + ext2_u32_list *ret) +{ + ext2_u32_list bb; + errcode_t retval; + + retval = ext2fs_get_mem(sizeof(struct ext2_struct_u32_list), &bb); + if (retval) + return retval; + memset(bb, 0, sizeof(struct ext2_struct_u32_list)); + bb->magic = EXT2_ET_MAGIC_BADBLOCKS_LIST; + bb->size = size ? size : 10; + bb->num = num; + retval = ext2fs_get_mem(bb->size * sizeof(blk_t), &bb->list); + if (!bb->list) { + ext2fs_free_mem(&bb); + return retval; + } + if (list) + memcpy(bb->list, list, bb->size * sizeof(blk_t)); + else + memset(bb->list, 0, bb->size * sizeof(blk_t)); + *ret = bb; + return 0; +} + + +/* + * This procedure creates an empty u32 list. + */ +errcode_t ext2fs_u32_list_create(ext2_u32_list *ret, int size) +{ + return make_u32_list(size, 0, 0, ret); +} + +/* + * This procedure creates an empty badblocks list. + */ +errcode_t ext2fs_badblocks_list_create(ext2_badblocks_list *ret, int size) +{ + return make_u32_list(size, 0, 0, (ext2_badblocks_list *) ret); +} + + +/* + * This procedure copies a badblocks list + */ +errcode_t ext2fs_u32_copy(ext2_u32_list src, ext2_u32_list *dest) +{ + errcode_t retval; + + retval = make_u32_list(src->size, src->num, src->list, dest); + if (retval) + return retval; + (*dest)->badblocks_flags = src->badblocks_flags; + return 0; +} + +errcode_t ext2fs_badblocks_copy(ext2_badblocks_list src, + ext2_badblocks_list *dest) +{ + return ext2fs_u32_copy((ext2_u32_list) src, + (ext2_u32_list *) dest); +} + +/* + * This procedure frees a badblocks list. + * + * (note: moved to closefs.c) + */ + + +/* + * This procedure adds a block to a badblocks list. + */ +errcode_t ext2fs_u32_list_add(ext2_u32_list bb, __u32 blk) +{ + errcode_t retval; + int i, j; + unsigned long old_size; + + EXT2_CHECK_MAGIC(bb, EXT2_ET_MAGIC_BADBLOCKS_LIST); + + if (bb->num >= bb->size) { + old_size = bb->size * sizeof(__u32); + bb->size += 100; + retval = ext2fs_resize_mem(old_size, bb->size * sizeof(__u32), + &bb->list); + if (retval) { + bb->size -= 100; + return retval; + } + } + + /* + * Add special case code for appending to the end of the list + */ + i = bb->num-1; + if ((bb->num != 0) && (bb->list[i] == blk)) + return 0; + if ((bb->num == 0) || (bb->list[i] < blk)) { + bb->list[bb->num++] = blk; + return 0; + } + + j = bb->num; + for (i=0; i < bb->num; i++) { + if (bb->list[i] == blk) + return 0; + if (bb->list[i] > blk) { + j = i; + break; + } + } + for (i=bb->num; i > j; i--) + bb->list[i] = bb->list[i-1]; + bb->list[j] = blk; + bb->num++; + return 0; +} + +errcode_t ext2fs_badblocks_list_add(ext2_badblocks_list bb, blk_t blk) +{ + return ext2fs_u32_list_add((ext2_u32_list) bb, (__u32) blk); +} + +/* + * This procedure finds a particular block is on a badblocks + * list. + */ +int ext2fs_u32_list_find(ext2_u32_list bb, __u32 blk) +{ + int low, high, mid; + + if (bb->magic != EXT2_ET_MAGIC_BADBLOCKS_LIST) + return -1; + + if (bb->num == 0) + return -1; + + low = 0; + high = bb->num-1; + if (blk == bb->list[low]) + return low; + if (blk == bb->list[high]) + return high; + + while (low < high) { + mid = (low+high)/2; + if (mid == low || mid == high) + break; + if (blk == bb->list[mid]) + return mid; + if (blk < bb->list[mid]) + high = mid; + else + low = mid; + } + return -1; +} + +/* + * This procedure tests to see if a particular block is on a badblocks + * list. + */ +int ext2fs_u32_list_test(ext2_u32_list bb, __u32 blk) +{ + if (ext2fs_u32_list_find(bb, blk) < 0) + return 0; + else + return 1; +} + +int ext2fs_badblocks_list_test(ext2_badblocks_list bb, blk_t blk) +{ + return ext2fs_u32_list_test((ext2_u32_list) bb, (__u32) blk); +} + + +/* + * Remove a block from the badblock list + */ +int ext2fs_u32_list_del(ext2_u32_list bb, __u32 blk) +{ + int remloc, i; + + if (bb->num == 0) + return -1; + + remloc = ext2fs_u32_list_find(bb, blk); + if (remloc < 0) + return -1; + + for (i = remloc ; i < bb->num-1; i++) + bb->list[i] = bb->list[i+1]; + bb->num--; + return 0; +} + +void ext2fs_badblocks_list_del(ext2_u32_list bb, __u32 blk) +{ + ext2fs_u32_list_del(bb, blk); +} + +errcode_t ext2fs_u32_list_iterate_begin(ext2_u32_list bb, + ext2_u32_iterate *ret) +{ + ext2_u32_iterate iter; + errcode_t retval; + + EXT2_CHECK_MAGIC(bb, EXT2_ET_MAGIC_BADBLOCKS_LIST); + + retval = ext2fs_get_mem(sizeof(struct ext2_struct_u32_iterate), &iter); + if (retval) + return retval; + + iter->magic = EXT2_ET_MAGIC_BADBLOCKS_ITERATE; + iter->bb = bb; + iter->ptr = 0; + *ret = iter; + return 0; +} + +errcode_t ext2fs_badblocks_list_iterate_begin(ext2_badblocks_list bb, + ext2_badblocks_iterate *ret) +{ + return ext2fs_u32_list_iterate_begin((ext2_u32_list) bb, + (ext2_u32_iterate *) ret); +} + + +int ext2fs_u32_list_iterate(ext2_u32_iterate iter, __u32 *blk) +{ + ext2_u32_list bb; + + if (iter->magic != EXT2_ET_MAGIC_BADBLOCKS_ITERATE) + return 0; + + bb = iter->bb; + + if (bb->magic != EXT2_ET_MAGIC_BADBLOCKS_LIST) + return 0; + + if (iter->ptr < bb->num) { + *blk = bb->list[iter->ptr++]; + return 1; + } + *blk = 0; + return 0; +} + +int ext2fs_badblocks_list_iterate(ext2_badblocks_iterate iter, blk_t *blk) +{ + return ext2fs_u32_list_iterate((ext2_u32_iterate) iter, + (__u32 *) blk); +} + + +void ext2fs_u32_list_iterate_end(ext2_u32_iterate iter) +{ + if (!iter || (iter->magic != EXT2_ET_MAGIC_BADBLOCKS_ITERATE)) + return; + + iter->bb = 0; + ext2fs_free_mem(&iter); +} + +void ext2fs_badblocks_list_iterate_end(ext2_badblocks_iterate iter) +{ + ext2fs_u32_list_iterate_end((ext2_u32_iterate) iter); +} + + +int ext2fs_u32_list_equal(ext2_u32_list bb1, ext2_u32_list bb2) +{ + EXT2_CHECK_MAGIC(bb1, EXT2_ET_MAGIC_BADBLOCKS_LIST); + EXT2_CHECK_MAGIC(bb2, EXT2_ET_MAGIC_BADBLOCKS_LIST); + + if (bb1->num != bb2->num) + return 0; + + if (memcmp(bb1->list, bb2->list, bb1->num * sizeof(blk_t)) != 0) + return 0; + return 1; +} + +int ext2fs_badblocks_equal(ext2_badblocks_list bb1, ext2_badblocks_list bb2) +{ + return ext2fs_u32_list_equal((ext2_u32_list) bb1, + (ext2_u32_list) bb2); +} + +int ext2fs_u32_list_count(ext2_u32_list bb) +{ + return bb->num; +} diff --git a/e2fsprogs/ext2fs/bb_compat.c b/e2fsprogs/ext2fs/bb_compat.c new file mode 100644 index 000000000..419ac7785 --- /dev/null +++ b/e2fsprogs/ext2fs/bb_compat.c @@ -0,0 +1,64 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_compat.c --- compatibility badblocks routines + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" + +errcode_t badblocks_list_create(badblocks_list *ret, int size) +{ + return ext2fs_badblocks_list_create(ret, size); +} + +void badblocks_list_free(badblocks_list bb) +{ + ext2fs_badblocks_list_free(bb); +} + +errcode_t badblocks_list_add(badblocks_list bb, blk_t blk) +{ + return ext2fs_badblocks_list_add(bb, blk); +} + +int badblocks_list_test(badblocks_list bb, blk_t blk) +{ + return ext2fs_badblocks_list_test(bb, blk); +} + +errcode_t badblocks_list_iterate_begin(badblocks_list bb, + badblocks_iterate *ret) +{ + return ext2fs_badblocks_list_iterate_begin(bb, ret); +} + +int badblocks_list_iterate(badblocks_iterate iter, blk_t *blk) +{ + return ext2fs_badblocks_list_iterate(iter, blk); +} + +void badblocks_list_iterate_end(badblocks_iterate iter) +{ + ext2fs_badblocks_list_iterate_end(iter); +} diff --git a/e2fsprogs/ext2fs/bb_inode.c b/e2fsprogs/ext2fs/bb_inode.c new file mode 100644 index 000000000..855f86eac --- /dev/null +++ b/e2fsprogs/ext2fs/bb_inode.c @@ -0,0 +1,268 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_inode.c --- routines to update the bad block inode. + * + * WARNING: This routine modifies a lot of state in the filesystem; if + * this routine returns an error, the bad block inode may be in an + * inconsistent state. + * + * Copyright (C) 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct set_badblock_record { + ext2_badblocks_iterate bb_iter; + int bad_block_count; + blk_t *ind_blocks; + int max_ind_blocks; + int ind_blocks_size; + int ind_blocks_ptr; + char *block_buf; + errcode_t err; +}; + +static int set_bad_block_proc(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block, int ref_offset, + void *priv_data); +static int clear_bad_block_proc(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block, int ref_offset, + void *priv_data); + +/* + * Given a bad blocks bitmap, update the bad blocks inode to reflect + * the map. + */ +errcode_t ext2fs_update_bb_inode(ext2_filsys fs, ext2_badblocks_list bb_list) +{ + errcode_t retval; + struct set_badblock_record rec; + struct ext2_inode inode; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!fs->block_map) + return EXT2_ET_NO_BLOCK_BITMAP; + + rec.bad_block_count = 0; + rec.ind_blocks_size = rec.ind_blocks_ptr = 0; + rec.max_ind_blocks = 10; + retval = ext2fs_get_mem(rec.max_ind_blocks * sizeof(blk_t), + &rec.ind_blocks); + if (retval) + return retval; + memset(rec.ind_blocks, 0, rec.max_ind_blocks * sizeof(blk_t)); + retval = ext2fs_get_mem(fs->blocksize, &rec.block_buf); + if (retval) + goto cleanup; + memset(rec.block_buf, 0, fs->blocksize); + rec.err = 0; + + /* + * First clear the old bad blocks (while saving the indirect blocks) + */ + retval = ext2fs_block_iterate2(fs, EXT2_BAD_INO, + BLOCK_FLAG_DEPTH_TRAVERSE, 0, + clear_bad_block_proc, &rec); + if (retval) + goto cleanup; + if (rec.err) { + retval = rec.err; + goto cleanup; + } + + /* + * Now set the bad blocks! + * + * First, mark the bad blocks as used. This prevents a bad + * block from being used as an indirecto block for the bad + * block inode (!). + */ + if (bb_list) { + retval = ext2fs_badblocks_list_iterate_begin(bb_list, + &rec.bb_iter); + if (retval) + goto cleanup; + retval = ext2fs_block_iterate2(fs, EXT2_BAD_INO, + BLOCK_FLAG_APPEND, 0, + set_bad_block_proc, &rec); + ext2fs_badblocks_list_iterate_end(rec.bb_iter); + if (retval) + goto cleanup; + if (rec.err) { + retval = rec.err; + goto cleanup; + } + } + + /* + * Update the bad block inode's mod time and block count + * field. + */ + retval = ext2fs_read_inode(fs, EXT2_BAD_INO, &inode); + if (retval) + goto cleanup; + + inode.i_atime = inode.i_mtime = time(0); + if (!inode.i_ctime) + inode.i_ctime = time(0); + inode.i_blocks = rec.bad_block_count * (fs->blocksize / 512); + inode.i_size = rec.bad_block_count * fs->blocksize; + + retval = ext2fs_write_inode(fs, EXT2_BAD_INO, &inode); + if (retval) + goto cleanup; + +cleanup: + ext2fs_free_mem(&rec.ind_blocks); + ext2fs_free_mem(&rec.block_buf); + return retval; +} + +/* + * Helper function for update_bb_inode() + * + * Clear the bad blocks in the bad block inode, while saving the + * indirect blocks. + */ +#ifdef __TURBOC__ +# pragma argsused +#endif +static int clear_bad_block_proc(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct set_badblock_record *rec = (struct set_badblock_record *) + priv_data; + errcode_t retval; + unsigned long old_size; + + if (!*block_nr) + return 0; + + /* + * If the block number is outrageous, clear it and ignore it. + */ + if (*block_nr >= fs->super->s_blocks_count || + *block_nr < fs->super->s_first_data_block) { + *block_nr = 0; + return BLOCK_CHANGED; + } + + if (blockcnt < 0) { + if (rec->ind_blocks_size >= rec->max_ind_blocks) { + old_size = rec->max_ind_blocks * sizeof(blk_t); + rec->max_ind_blocks += 10; + retval = ext2fs_resize_mem(old_size, + rec->max_ind_blocks * sizeof(blk_t), + &rec->ind_blocks); + if (retval) { + rec->max_ind_blocks -= 10; + rec->err = retval; + return BLOCK_ABORT; + } + } + rec->ind_blocks[rec->ind_blocks_size++] = *block_nr; + } + + /* + * Mark the block as unused, and update accounting information + */ + ext2fs_block_alloc_stats(fs, *block_nr, -1); + + *block_nr = 0; + return BLOCK_CHANGED; +} + + +/* + * Helper function for update_bb_inode() + * + * Set the block list in the bad block inode, using the supplied bitmap. + */ +#ifdef __TURBOC__ + #pragma argsused +#endif +static int set_bad_block_proc(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct set_badblock_record *rec = (struct set_badblock_record *) + priv_data; + errcode_t retval; + blk_t blk; + + if (blockcnt >= 0) { + /* + * Get the next bad block. + */ + if (!ext2fs_badblocks_list_iterate(rec->bb_iter, &blk)) + return BLOCK_ABORT; + rec->bad_block_count++; + } else { + /* + * An indirect block; fetch a block from the + * previously used indirect block list. The block + * most be not marked as used; if so, get another one. + * If we run out of reserved indirect blocks, allocate + * a new one. + */ + retry: + if (rec->ind_blocks_ptr < rec->ind_blocks_size) { + blk = rec->ind_blocks[rec->ind_blocks_ptr++]; + if (ext2fs_test_block_bitmap(fs->block_map, blk)) + goto retry; + } else { + retval = ext2fs_new_block(fs, 0, 0, &blk); + if (retval) { + rec->err = retval; + return BLOCK_ABORT; + } + } + retval = io_channel_write_blk(fs->io, blk, 1, rec->block_buf); + if (retval) { + rec->err = retval; + return BLOCK_ABORT; + } + } + + /* + * Update block counts + */ + ext2fs_block_alloc_stats(fs, blk, +1); + + *block_nr = blk; + return BLOCK_CHANGED; +} + + + + + + diff --git a/e2fsprogs/ext2fs/bitmaps.c b/e2fsprogs/ext2fs/bitmaps.c new file mode 100644 index 000000000..712a4a9b1 --- /dev/null +++ b/e2fsprogs/ext2fs/bitmaps.c @@ -0,0 +1,213 @@ +/* vi: set sw=4 ts=4: */ +/* + * bitmaps.c --- routines to read, write, and manipulate the inode and + * block bitmaps. + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +static errcode_t make_bitmap(__u32 start, __u32 end, __u32 real_end, + const char *descr, char *init_map, + ext2fs_generic_bitmap *ret) +{ + ext2fs_generic_bitmap bitmap; + errcode_t retval; + size_t size; + + retval = ext2fs_get_mem(sizeof(struct ext2fs_struct_generic_bitmap), + &bitmap); + if (retval) + return retval; + + bitmap->magic = EXT2_ET_MAGIC_GENERIC_BITMAP; + bitmap->fs = NULL; + bitmap->start = start; + bitmap->end = end; + bitmap->real_end = real_end; + bitmap->base_error_code = EXT2_ET_BAD_GENERIC_MARK; + if (descr) { + retval = ext2fs_get_mem(strlen(descr)+1, &bitmap->description); + if (retval) { + ext2fs_free_mem(&bitmap); + return retval; + } + strcpy(bitmap->description, descr); + } else + bitmap->description = 0; + + size = (size_t) (((bitmap->real_end - bitmap->start) / 8) + 1); + retval = ext2fs_get_mem(size, &bitmap->bitmap); + if (retval) { + ext2fs_free_mem(&bitmap->description); + ext2fs_free_mem(&bitmap); + return retval; + } + + if (init_map) + memcpy(bitmap->bitmap, init_map, size); + else + memset(bitmap->bitmap, 0, size); + *ret = bitmap; + return 0; +} + +errcode_t ext2fs_allocate_generic_bitmap(__u32 start, + __u32 end, + __u32 real_end, + const char *descr, + ext2fs_generic_bitmap *ret) +{ + return make_bitmap(start, end, real_end, descr, 0, ret); +} + +errcode_t ext2fs_copy_bitmap(ext2fs_generic_bitmap src, + ext2fs_generic_bitmap *dest) +{ + errcode_t retval; + ext2fs_generic_bitmap new_map; + + retval = make_bitmap(src->start, src->end, src->real_end, + src->description, src->bitmap, &new_map); + if (retval) + return retval; + new_map->magic = src->magic; + new_map->fs = src->fs; + new_map->base_error_code = src->base_error_code; + *dest = new_map; + return 0; +} + +void ext2fs_set_bitmap_padding(ext2fs_generic_bitmap map) +{ + __u32 i, j; + + for (i=map->end+1, j = i - map->start; i <= map->real_end; i++, j++) + ext2fs_set_bit(j, map->bitmap); + + return; +} + +errcode_t ext2fs_allocate_inode_bitmap(ext2_filsys fs, + const char *descr, + ext2fs_inode_bitmap *ret) +{ + ext2fs_inode_bitmap bitmap; + errcode_t retval; + __u32 start, end, real_end; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + fs->write_bitmaps = ext2fs_write_bitmaps; + + start = 1; + end = fs->super->s_inodes_count; + real_end = (EXT2_INODES_PER_GROUP(fs->super) * fs->group_desc_count); + + retval = ext2fs_allocate_generic_bitmap(start, end, real_end, + descr, &bitmap); + if (retval) + return retval; + + bitmap->magic = EXT2_ET_MAGIC_INODE_BITMAP; + bitmap->fs = fs; + bitmap->base_error_code = EXT2_ET_BAD_INODE_MARK; + + *ret = bitmap; + return 0; +} + +errcode_t ext2fs_allocate_block_bitmap(ext2_filsys fs, + const char *descr, + ext2fs_block_bitmap *ret) +{ + ext2fs_block_bitmap bitmap; + errcode_t retval; + __u32 start, end, real_end; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + fs->write_bitmaps = ext2fs_write_bitmaps; + + start = fs->super->s_first_data_block; + end = fs->super->s_blocks_count-1; + real_end = (EXT2_BLOCKS_PER_GROUP(fs->super) + * fs->group_desc_count)-1 + start; + + retval = ext2fs_allocate_generic_bitmap(start, end, real_end, + descr, &bitmap); + if (retval) + return retval; + + bitmap->magic = EXT2_ET_MAGIC_BLOCK_BITMAP; + bitmap->fs = fs; + bitmap->base_error_code = EXT2_ET_BAD_BLOCK_MARK; + + *ret = bitmap; + return 0; +} + +errcode_t ext2fs_fudge_inode_bitmap_end(ext2fs_inode_bitmap bitmap, + ext2_ino_t end, ext2_ino_t *oend) +{ + EXT2_CHECK_MAGIC(bitmap, EXT2_ET_MAGIC_INODE_BITMAP); + + if (end > bitmap->real_end) + return EXT2_ET_FUDGE_INODE_BITMAP_END; + if (oend) + *oend = bitmap->end; + bitmap->end = end; + return 0; +} + +errcode_t ext2fs_fudge_block_bitmap_end(ext2fs_block_bitmap bitmap, + blk_t end, blk_t *oend) +{ + EXT2_CHECK_MAGIC(bitmap, EXT2_ET_MAGIC_BLOCK_BITMAP); + + if (end > bitmap->real_end) + return EXT2_ET_FUDGE_BLOCK_BITMAP_END; + if (oend) + *oend = bitmap->end; + bitmap->end = end; + return 0; +} + +void ext2fs_clear_inode_bitmap(ext2fs_inode_bitmap bitmap) +{ + if (!bitmap || (bitmap->magic != EXT2_ET_MAGIC_INODE_BITMAP)) + return; + + memset(bitmap->bitmap, 0, + (size_t) (((bitmap->real_end - bitmap->start) / 8) + 1)); +} + +void ext2fs_clear_block_bitmap(ext2fs_block_bitmap bitmap) +{ + if (!bitmap || (bitmap->magic != EXT2_ET_MAGIC_BLOCK_BITMAP)) + return; + + memset(bitmap->bitmap, 0, + (size_t) (((bitmap->real_end - bitmap->start) / 8) + 1)); +} diff --git a/e2fsprogs/ext2fs/bitops.c b/e2fsprogs/ext2fs/bitops.c new file mode 100644 index 000000000..987061130 --- /dev/null +++ b/e2fsprogs/ext2fs/bitops.c @@ -0,0 +1,91 @@ +/* vi: set sw=4 ts=4: */ +/* + * bitops.c --- Bitmap frobbing code. See bitops.h for the inlined + * routines. + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +#ifndef _EXT2_HAVE_ASM_BITOPS_ + +/* + * For the benefit of those who are trying to port Linux to another + * architecture, here are some C-language equivalents. You should + * recode these in the native assmebly language, if at all possible. + * + * C language equivalents written by Theodore Ts'o, 9/26/92. + * Modified by Pete A. Zaitcev 7/14/95 to be portable to big endian + * systems, as well as non-32 bit systems. + */ + +int ext2fs_set_bit(unsigned int nr,void * addr) +{ + int mask, retval; + unsigned char *ADDR = (unsigned char *) addr; + + ADDR += nr >> 3; + mask = 1 << (nr & 0x07); + retval = mask & *ADDR; + *ADDR |= mask; + return retval; +} + +int ext2fs_clear_bit(unsigned int nr, void * addr) +{ + int mask, retval; + unsigned char *ADDR = (unsigned char *) addr; + + ADDR += nr >> 3; + mask = 1 << (nr & 0x07); + retval = mask & *ADDR; + *ADDR &= ~mask; + return retval; +} + +int ext2fs_test_bit(unsigned int nr, const void * addr) +{ + int mask; + const unsigned char *ADDR = (const unsigned char *) addr; + + ADDR += nr >> 3; + mask = 1 << (nr & 0x07); + return (mask & *ADDR); +} + +#endif /* !_EXT2_HAVE_ASM_BITOPS_ */ + +void ext2fs_warn_bitmap(errcode_t errcode, unsigned long arg, + const char *description) +{ +#ifndef OMIT_COM_ERR + if (description) + bb_error_msg("#%lu for %s", arg, description); + else + bb_error_msg("#%lu", arg); +#endif +} + +void ext2fs_warn_bitmap2(ext2fs_generic_bitmap bitmap, + int code, unsigned long arg) +{ +#ifndef OMIT_COM_ERR + if (bitmap->description) + bb_error_msg("#%lu for %s", arg, bitmap->description); + else + bb_error_msg("#%lu", arg); +#endif +} + diff --git a/e2fsprogs/ext2fs/bitops.h b/e2fsprogs/ext2fs/bitops.h new file mode 100644 index 000000000..6dd30862b --- /dev/null +++ b/e2fsprogs/ext2fs/bitops.h @@ -0,0 +1,107 @@ +/* vi: set sw=4 ts=4: */ +/* + * bitops.h --- Bitmap frobbing code. The byte swapping routines are + * also included here. + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + * i386 bitops operations taken from , Copyright 1992, + * Linus Torvalds. + */ + +#include +#include + +extern int ext2fs_set_bit(unsigned int nr,void * addr); +extern int ext2fs_clear_bit(unsigned int nr, void * addr); +extern int ext2fs_test_bit(unsigned int nr, const void * addr); +extern __u16 ext2fs_swab16(__u16 val); +extern __u32 ext2fs_swab32(__u32 val); + +#ifdef WORDS_BIGENDIAN +#define ext2fs_cpu_to_le32(x) ext2fs_swab32((x)) +#define ext2fs_le32_to_cpu(x) ext2fs_swab32((x)) +#define ext2fs_cpu_to_le16(x) ext2fs_swab16((x)) +#define ext2fs_le16_to_cpu(x) ext2fs_swab16((x)) +#define ext2fs_cpu_to_be32(x) ((__u32)(x)) +#define ext2fs_be32_to_cpu(x) ((__u32)(x)) +#define ext2fs_cpu_to_be16(x) ((__u16)(x)) +#define ext2fs_be16_to_cpu(x) ((__u16)(x)) +#else +#define ext2fs_cpu_to_le32(x) ((__u32)(x)) +#define ext2fs_le32_to_cpu(x) ((__u32)(x)) +#define ext2fs_cpu_to_le16(x) ((__u16)(x)) +#define ext2fs_le16_to_cpu(x) ((__u16)(x)) +#define ext2fs_cpu_to_be32(x) ext2fs_swab32((x)) +#define ext2fs_be32_to_cpu(x) ext2fs_swab32((x)) +#define ext2fs_cpu_to_be16(x) ext2fs_swab16((x)) +#define ext2fs_be16_to_cpu(x) ext2fs_swab16((x)) +#endif + +/* + * EXT2FS bitmap manipulation routines. + */ + +/* Support for sending warning messages from the inline subroutines */ +extern const char *ext2fs_block_string; +extern const char *ext2fs_inode_string; +extern const char *ext2fs_mark_string; +extern const char *ext2fs_unmark_string; +extern const char *ext2fs_test_string; +extern void ext2fs_warn_bitmap(errcode_t errcode, unsigned long arg, + const char *description); +extern void ext2fs_warn_bitmap2(ext2fs_generic_bitmap bitmap, + int code, unsigned long arg); + +extern int ext2fs_mark_block_bitmap(ext2fs_block_bitmap bitmap, blk_t block); +extern int ext2fs_unmark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block); +extern int ext2fs_test_block_bitmap(ext2fs_block_bitmap bitmap, blk_t block); + +extern int ext2fs_mark_inode_bitmap(ext2fs_inode_bitmap bitmap, ext2_ino_t inode); +extern int ext2fs_unmark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode); +extern int ext2fs_test_inode_bitmap(ext2fs_inode_bitmap bitmap, ext2_ino_t inode); + +extern void ext2fs_fast_mark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block); +extern void ext2fs_fast_unmark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block); +extern int ext2fs_fast_test_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block); + +extern void ext2fs_fast_mark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode); +extern void ext2fs_fast_unmark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode); +extern int ext2fs_fast_test_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode); +extern blk_t ext2fs_get_block_bitmap_start(ext2fs_block_bitmap bitmap); +extern ext2_ino_t ext2fs_get_inode_bitmap_start(ext2fs_inode_bitmap bitmap); +extern blk_t ext2fs_get_block_bitmap_end(ext2fs_block_bitmap bitmap); +extern ext2_ino_t ext2fs_get_inode_bitmap_end(ext2fs_inode_bitmap bitmap); + +extern void ext2fs_mark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern void ext2fs_unmark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern int ext2fs_test_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern void ext2fs_fast_mark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern void ext2fs_fast_unmark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern int ext2fs_fast_test_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern void ext2fs_set_bitmap_padding(ext2fs_generic_bitmap map); + +/* These two routines moved to gen_bitmap.c */ +extern int ext2fs_mark_generic_bitmap(ext2fs_generic_bitmap bitmap, + __u32 bitno); +extern int ext2fs_unmark_generic_bitmap(ext2fs_generic_bitmap bitmap, + blk_t bitno); diff --git a/e2fsprogs/ext2fs/block.c b/e2fsprogs/ext2fs/block.c new file mode 100644 index 000000000..0a757b201 --- /dev/null +++ b/e2fsprogs/ext2fs/block.c @@ -0,0 +1,438 @@ +/* vi: set sw=4 ts=4: */ +/* + * block.c --- iterate over all blocks in an inode + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct block_context { + ext2_filsys fs; + int (*func)(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t bcount, + blk_t ref_blk, + int ref_offset, + void *priv_data); + e2_blkcnt_t bcount; + int bsize; + int flags; + errcode_t errcode; + char *ind_buf; + char *dind_buf; + char *tind_buf; + void *priv_data; +}; + +static int block_iterate_ind(blk_t *ind_block, blk_t ref_block, + int ref_offset, struct block_context *ctx) +{ + int ret = 0, changed = 0; + int i, flags, limit, offset; + blk_t *block_nr; + + limit = ctx->fs->blocksize >> 2; + if (!(ctx->flags & BLOCK_FLAG_DEPTH_TRAVERSE) && + !(ctx->flags & BLOCK_FLAG_DATA_ONLY)) + ret = (*ctx->func)(ctx->fs, ind_block, + BLOCK_COUNT_IND, ref_block, + ref_offset, ctx->priv_data); + if (!*ind_block || (ret & BLOCK_ABORT)) { + ctx->bcount += limit; + return ret; + } + if (*ind_block >= ctx->fs->super->s_blocks_count || + *ind_block < ctx->fs->super->s_first_data_block) { + ctx->errcode = EXT2_ET_BAD_IND_BLOCK; + ret |= BLOCK_ERROR; + return ret; + } + ctx->errcode = ext2fs_read_ind_block(ctx->fs, *ind_block, + ctx->ind_buf); + if (ctx->errcode) { + ret |= BLOCK_ERROR; + return ret; + } + + block_nr = (blk_t *) ctx->ind_buf; + offset = 0; + if (ctx->flags & BLOCK_FLAG_APPEND) { + for (i = 0; i < limit; i++, ctx->bcount++, block_nr++) { + flags = (*ctx->func)(ctx->fs, block_nr, ctx->bcount, + *ind_block, offset, + ctx->priv_data); + changed |= flags; + if (flags & BLOCK_ABORT) { + ret |= BLOCK_ABORT; + break; + } + offset += sizeof(blk_t); + } + } else { + for (i = 0; i < limit; i++, ctx->bcount++, block_nr++) { + if (*block_nr == 0) + continue; + flags = (*ctx->func)(ctx->fs, block_nr, ctx->bcount, + *ind_block, offset, + ctx->priv_data); + changed |= flags; + if (flags & BLOCK_ABORT) { + ret |= BLOCK_ABORT; + break; + } + offset += sizeof(blk_t); + } + } + if (changed & BLOCK_CHANGED) { + ctx->errcode = ext2fs_write_ind_block(ctx->fs, *ind_block, + ctx->ind_buf); + if (ctx->errcode) + ret |= BLOCK_ERROR | BLOCK_ABORT; + } + if ((ctx->flags & BLOCK_FLAG_DEPTH_TRAVERSE) && + !(ctx->flags & BLOCK_FLAG_DATA_ONLY) && + !(ret & BLOCK_ABORT)) + ret |= (*ctx->func)(ctx->fs, ind_block, + BLOCK_COUNT_IND, ref_block, + ref_offset, ctx->priv_data); + return ret; +} + +static int block_iterate_dind(blk_t *dind_block, blk_t ref_block, + int ref_offset, struct block_context *ctx) +{ + int ret = 0, changed = 0; + int i, flags, limit, offset; + blk_t *block_nr; + + limit = ctx->fs->blocksize >> 2; + if (!(ctx->flags & (BLOCK_FLAG_DEPTH_TRAVERSE | + BLOCK_FLAG_DATA_ONLY))) + ret = (*ctx->func)(ctx->fs, dind_block, + BLOCK_COUNT_DIND, ref_block, + ref_offset, ctx->priv_data); + if (!*dind_block || (ret & BLOCK_ABORT)) { + ctx->bcount += limit*limit; + return ret; + } + if (*dind_block >= ctx->fs->super->s_blocks_count || + *dind_block < ctx->fs->super->s_first_data_block) { + ctx->errcode = EXT2_ET_BAD_DIND_BLOCK; + ret |= BLOCK_ERROR; + return ret; + } + ctx->errcode = ext2fs_read_ind_block(ctx->fs, *dind_block, + ctx->dind_buf); + if (ctx->errcode) { + ret |= BLOCK_ERROR; + return ret; + } + + block_nr = (blk_t *) ctx->dind_buf; + offset = 0; + if (ctx->flags & BLOCK_FLAG_APPEND) { + for (i = 0; i < limit; i++, block_nr++) { + flags = block_iterate_ind(block_nr, + *dind_block, offset, + ctx); + changed |= flags; + if (flags & (BLOCK_ABORT | BLOCK_ERROR)) { + ret |= flags & (BLOCK_ABORT | BLOCK_ERROR); + break; + } + offset += sizeof(blk_t); + } + } else { + for (i = 0; i < limit; i++, block_nr++) { + if (*block_nr == 0) { + ctx->bcount += limit; + continue; + } + flags = block_iterate_ind(block_nr, + *dind_block, offset, + ctx); + changed |= flags; + if (flags & (BLOCK_ABORT | BLOCK_ERROR)) { + ret |= flags & (BLOCK_ABORT | BLOCK_ERROR); + break; + } + offset += sizeof(blk_t); + } + } + if (changed & BLOCK_CHANGED) { + ctx->errcode = ext2fs_write_ind_block(ctx->fs, *dind_block, + ctx->dind_buf); + if (ctx->errcode) + ret |= BLOCK_ERROR | BLOCK_ABORT; + } + if ((ctx->flags & BLOCK_FLAG_DEPTH_TRAVERSE) && + !(ctx->flags & BLOCK_FLAG_DATA_ONLY) && + !(ret & BLOCK_ABORT)) + ret |= (*ctx->func)(ctx->fs, dind_block, + BLOCK_COUNT_DIND, ref_block, + ref_offset, ctx->priv_data); + return ret; +} + +static int block_iterate_tind(blk_t *tind_block, blk_t ref_block, + int ref_offset, struct block_context *ctx) +{ + int ret = 0, changed = 0; + int i, flags, limit, offset; + blk_t *block_nr; + + limit = ctx->fs->blocksize >> 2; + if (!(ctx->flags & (BLOCK_FLAG_DEPTH_TRAVERSE | + BLOCK_FLAG_DATA_ONLY))) + ret = (*ctx->func)(ctx->fs, tind_block, + BLOCK_COUNT_TIND, ref_block, + ref_offset, ctx->priv_data); + if (!*tind_block || (ret & BLOCK_ABORT)) { + ctx->bcount += limit*limit*limit; + return ret; + } + if (*tind_block >= ctx->fs->super->s_blocks_count || + *tind_block < ctx->fs->super->s_first_data_block) { + ctx->errcode = EXT2_ET_BAD_TIND_BLOCK; + ret |= BLOCK_ERROR; + return ret; + } + ctx->errcode = ext2fs_read_ind_block(ctx->fs, *tind_block, + ctx->tind_buf); + if (ctx->errcode) { + ret |= BLOCK_ERROR; + return ret; + } + + block_nr = (blk_t *) ctx->tind_buf; + offset = 0; + if (ctx->flags & BLOCK_FLAG_APPEND) { + for (i = 0; i < limit; i++, block_nr++) { + flags = block_iterate_dind(block_nr, + *tind_block, + offset, ctx); + changed |= flags; + if (flags & (BLOCK_ABORT | BLOCK_ERROR)) { + ret |= flags & (BLOCK_ABORT | BLOCK_ERROR); + break; + } + offset += sizeof(blk_t); + } + } else { + for (i = 0; i < limit; i++, block_nr++) { + if (*block_nr == 0) { + ctx->bcount += limit*limit; + continue; + } + flags = block_iterate_dind(block_nr, + *tind_block, + offset, ctx); + changed |= flags; + if (flags & (BLOCK_ABORT | BLOCK_ERROR)) { + ret |= flags & (BLOCK_ABORT | BLOCK_ERROR); + break; + } + offset += sizeof(blk_t); + } + } + if (changed & BLOCK_CHANGED) { + ctx->errcode = ext2fs_write_ind_block(ctx->fs, *tind_block, + ctx->tind_buf); + if (ctx->errcode) + ret |= BLOCK_ERROR | BLOCK_ABORT; + } + if ((ctx->flags & BLOCK_FLAG_DEPTH_TRAVERSE) && + !(ctx->flags & BLOCK_FLAG_DATA_ONLY) && + !(ret & BLOCK_ABORT)) + ret |= (*ctx->func)(ctx->fs, tind_block, + BLOCK_COUNT_TIND, ref_block, + ref_offset, ctx->priv_data); + + return ret; +} + +errcode_t ext2fs_block_iterate2(ext2_filsys fs, + ext2_ino_t ino, + int flags, + char *block_buf, + int (*func)(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_blk, + int ref_offset, + void *priv_data), + void *priv_data) +{ + int i; + int got_inode = 0; + int ret = 0; + blk_t blocks[EXT2_N_BLOCKS]; /* directory data blocks */ + struct ext2_inode inode; + errcode_t retval; + struct block_context ctx; + int limit; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + /* + * Check to see if we need to limit large files + */ + if (flags & BLOCK_FLAG_NO_LARGE) { + ctx.errcode = ext2fs_read_inode(fs, ino, &inode); + if (ctx.errcode) + return ctx.errcode; + got_inode = 1; + if (!LINUX_S_ISDIR(inode.i_mode) && + (inode.i_size_high != 0)) + return EXT2_ET_FILE_TOO_BIG; + } + + retval = ext2fs_get_blocks(fs, ino, blocks); + if (retval) + return retval; + + limit = fs->blocksize >> 2; + + ctx.fs = fs; + ctx.func = func; + ctx.priv_data = priv_data; + ctx.flags = flags; + ctx.bcount = 0; + if (block_buf) { + ctx.ind_buf = block_buf; + } else { + retval = ext2fs_get_mem(fs->blocksize * 3, &ctx.ind_buf); + if (retval) + return retval; + } + ctx.dind_buf = ctx.ind_buf + fs->blocksize; + ctx.tind_buf = ctx.dind_buf + fs->blocksize; + + /* + * Iterate over the HURD translator block (if present) + */ + if ((fs->super->s_creator_os == EXT2_OS_HURD) && + !(flags & BLOCK_FLAG_DATA_ONLY)) { + ctx.errcode = ext2fs_read_inode(fs, ino, &inode); + if (ctx.errcode) + goto abort_exit; + got_inode = 1; + if (inode.osd1.hurd1.h_i_translator) { + ret |= (*ctx.func)(fs, + &inode.osd1.hurd1.h_i_translator, + BLOCK_COUNT_TRANSLATOR, + 0, 0, priv_data); + if (ret & BLOCK_ABORT) + goto abort_exit; + } + } + + /* + * Iterate over normal data blocks + */ + for (i = 0; i < EXT2_NDIR_BLOCKS ; i++, ctx.bcount++) { + if (blocks[i] || (flags & BLOCK_FLAG_APPEND)) { + ret |= (*ctx.func)(fs, &blocks[i], + ctx.bcount, 0, i, priv_data); + if (ret & BLOCK_ABORT) + goto abort_exit; + } + } + if (*(blocks + EXT2_IND_BLOCK) || (flags & BLOCK_FLAG_APPEND)) { + ret |= block_iterate_ind(blocks + EXT2_IND_BLOCK, + 0, EXT2_IND_BLOCK, &ctx); + if (ret & BLOCK_ABORT) + goto abort_exit; + } else + ctx.bcount += limit; + if (*(blocks + EXT2_DIND_BLOCK) || (flags & BLOCK_FLAG_APPEND)) { + ret |= block_iterate_dind(blocks + EXT2_DIND_BLOCK, + 0, EXT2_DIND_BLOCK, &ctx); + if (ret & BLOCK_ABORT) + goto abort_exit; + } else + ctx.bcount += limit * limit; + if (*(blocks + EXT2_TIND_BLOCK) || (flags & BLOCK_FLAG_APPEND)) { + ret |= block_iterate_tind(blocks + EXT2_TIND_BLOCK, + 0, EXT2_TIND_BLOCK, &ctx); + if (ret & BLOCK_ABORT) + goto abort_exit; + } + +abort_exit: + if (ret & BLOCK_CHANGED) { + if (!got_inode) { + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + } + for (i=0; i < EXT2_N_BLOCKS; i++) + inode.i_block[i] = blocks[i]; + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) + return retval; + } + + if (!block_buf) + ext2fs_free_mem(&ctx.ind_buf); + + return (ret & BLOCK_ERROR) ? ctx.errcode : 0; +} + +/* + * Emulate the old ext2fs_block_iterate function! + */ + +struct xlate { + int (*func)(ext2_filsys fs, + blk_t *blocknr, + int bcount, + void *priv_data); + void *real_private; +}; + +#ifdef __TURBOC__ +# pragma argsused +#endif +static int xlate_func(ext2_filsys fs, blk_t *blocknr, e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct xlate *xl = (struct xlate *) priv_data; + + return (*xl->func)(fs, blocknr, (int) blockcnt, xl->real_private); +} + +errcode_t ext2fs_block_iterate(ext2_filsys fs, + ext2_ino_t ino, + int flags, + char *block_buf, + int (*func)(ext2_filsys fs, + blk_t *blocknr, + int blockcnt, + void *priv_data), + void *priv_data) +{ + struct xlate xl; + + xl.real_private = priv_data; + xl.func = func; + + return ext2fs_block_iterate2(fs, ino, BLOCK_FLAG_NO_LARGE | flags, + block_buf, xlate_func, &xl); +} + diff --git a/e2fsprogs/ext2fs/bmap.c b/e2fsprogs/ext2fs/bmap.c new file mode 100644 index 000000000..b22fe3dbf --- /dev/null +++ b/e2fsprogs/ext2fs/bmap.c @@ -0,0 +1,264 @@ +/* vi: set sw=4 ts=4: */ +/* + * bmap.c --- logical to physical block mapping + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +extern errcode_t ext2fs_bmap(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + char *block_buf, int bmap_flags, + blk_t block, blk_t *phys_blk); + +#define inode_bmap(inode, nr) ((inode)->i_block[(nr)]) + +static errcode_t block_ind_bmap(ext2_filsys fs, int flags, + blk_t ind, char *block_buf, + int *blocks_alloc, + blk_t nr, blk_t *ret_blk) +{ + errcode_t retval; + blk_t b; + + if (!ind) { + if (flags & BMAP_SET) + return EXT2_ET_SET_BMAP_NO_IND; + *ret_blk = 0; + return 0; + } + retval = io_channel_read_blk(fs->io, ind, 1, block_buf); + if (retval) + return retval; + + if (flags & BMAP_SET) { + b = *ret_blk; +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE)) + b = ext2fs_swab32(b); +#endif + ((blk_t *) block_buf)[nr] = b; + return io_channel_write_blk(fs->io, ind, 1, block_buf); + } + + b = ((blk_t *) block_buf)[nr]; + +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_READ)) + b = ext2fs_swab32(b); +#endif + + if (!b && (flags & BMAP_ALLOC)) { + b = nr ? ((blk_t *) block_buf)[nr-1] : 0; + retval = ext2fs_alloc_block(fs, b, + block_buf + fs->blocksize, &b); + if (retval) + return retval; + +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE)) + ((blk_t *) block_buf)[nr] = ext2fs_swab32(b); + else +#endif + ((blk_t *) block_buf)[nr] = b; + + retval = io_channel_write_blk(fs->io, ind, 1, block_buf); + if (retval) + return retval; + + (*blocks_alloc)++; + } + + *ret_blk = b; + return 0; +} + +static errcode_t block_dind_bmap(ext2_filsys fs, int flags, + blk_t dind, char *block_buf, + int *blocks_alloc, + blk_t nr, blk_t *ret_blk) +{ + blk_t b; + errcode_t retval; + blk_t addr_per_block; + + addr_per_block = (blk_t) fs->blocksize >> 2; + + retval = block_ind_bmap(fs, flags & ~BMAP_SET, dind, block_buf, + blocks_alloc, nr / addr_per_block, &b); + if (retval) + return retval; + retval = block_ind_bmap(fs, flags, b, block_buf, blocks_alloc, + nr % addr_per_block, ret_blk); + return retval; +} + +static errcode_t block_tind_bmap(ext2_filsys fs, int flags, + blk_t tind, char *block_buf, + int *blocks_alloc, + blk_t nr, blk_t *ret_blk) +{ + blk_t b; + errcode_t retval; + blk_t addr_per_block; + + addr_per_block = (blk_t) fs->blocksize >> 2; + + retval = block_dind_bmap(fs, flags & ~BMAP_SET, tind, block_buf, + blocks_alloc, nr / addr_per_block, &b); + if (retval) + return retval; + retval = block_ind_bmap(fs, flags, b, block_buf, blocks_alloc, + nr % addr_per_block, ret_blk); + return retval; +} + +errcode_t ext2fs_bmap(ext2_filsys fs, ext2_ino_t ino, struct ext2_inode *inode, + char *block_buf, int bmap_flags, blk_t block, + blk_t *phys_blk) +{ + struct ext2_inode inode_buf; + blk_t addr_per_block; + blk_t b; + char *buf = 0; + errcode_t retval = 0; + int blocks_alloc = 0, inode_dirty = 0; + + if (!(bmap_flags & BMAP_SET)) + *phys_blk = 0; + + /* Read inode structure if necessary */ + if (!inode) { + retval = ext2fs_read_inode(fs, ino, &inode_buf); + if (retval) + return retval; + inode = &inode_buf; + } + addr_per_block = (blk_t) fs->blocksize >> 2; + + if (!block_buf) { + retval = ext2fs_get_mem(fs->blocksize * 2, &buf); + if (retval) + return retval; + block_buf = buf; + } + + if (block < EXT2_NDIR_BLOCKS) { + if (bmap_flags & BMAP_SET) { + b = *phys_blk; +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_READ)) + b = ext2fs_swab32(b); +#endif + inode_bmap(inode, block) = b; + inode_dirty++; + goto done; + } + + *phys_blk = inode_bmap(inode, block); + b = block ? inode_bmap(inode, block-1) : 0; + + if ((*phys_blk == 0) && (bmap_flags & BMAP_ALLOC)) { + retval = ext2fs_alloc_block(fs, b, block_buf, &b); + if (retval) + goto done; + inode_bmap(inode, block) = b; + blocks_alloc++; + *phys_blk = b; + } + goto done; + } + + /* Indirect block */ + block -= EXT2_NDIR_BLOCKS; + if (block < addr_per_block) { + b = inode_bmap(inode, EXT2_IND_BLOCK); + if (!b) { + if (!(bmap_flags & BMAP_ALLOC)) { + if (bmap_flags & BMAP_SET) + retval = EXT2_ET_SET_BMAP_NO_IND; + goto done; + } + + b = inode_bmap(inode, EXT2_IND_BLOCK-1); + retval = ext2fs_alloc_block(fs, b, block_buf, &b); + if (retval) + goto done; + inode_bmap(inode, EXT2_IND_BLOCK) = b; + blocks_alloc++; + } + retval = block_ind_bmap(fs, bmap_flags, b, block_buf, + &blocks_alloc, block, phys_blk); + goto done; + } + + /* Doubly indirect block */ + block -= addr_per_block; + if (block < addr_per_block * addr_per_block) { + b = inode_bmap(inode, EXT2_DIND_BLOCK); + if (!b) { + if (!(bmap_flags & BMAP_ALLOC)) { + if (bmap_flags & BMAP_SET) + retval = EXT2_ET_SET_BMAP_NO_IND; + goto done; + } + + b = inode_bmap(inode, EXT2_IND_BLOCK); + retval = ext2fs_alloc_block(fs, b, block_buf, &b); + if (retval) + goto done; + inode_bmap(inode, EXT2_DIND_BLOCK) = b; + blocks_alloc++; + } + retval = block_dind_bmap(fs, bmap_flags, b, block_buf, + &blocks_alloc, block, phys_blk); + goto done; + } + + /* Triply indirect block */ + block -= addr_per_block * addr_per_block; + b = inode_bmap(inode, EXT2_TIND_BLOCK); + if (!b) { + if (!(bmap_flags & BMAP_ALLOC)) { + if (bmap_flags & BMAP_SET) + retval = EXT2_ET_SET_BMAP_NO_IND; + goto done; + } + + b = inode_bmap(inode, EXT2_DIND_BLOCK); + retval = ext2fs_alloc_block(fs, b, block_buf, &b); + if (retval) + goto done; + inode_bmap(inode, EXT2_TIND_BLOCK) = b; + blocks_alloc++; + } + retval = block_tind_bmap(fs, bmap_flags, b, block_buf, + &blocks_alloc, block, phys_blk); +done: + ext2fs_free_mem(&buf); + if ((retval == 0) && (blocks_alloc || inode_dirty)) { + inode->i_blocks += (blocks_alloc * fs->blocksize) / 512; + retval = ext2fs_write_inode(fs, ino, inode); + } + return retval; +} + + + diff --git a/e2fsprogs/ext2fs/bmove.c b/e2fsprogs/ext2fs/bmove.c new file mode 100644 index 000000000..635410da5 --- /dev/null +++ b/e2fsprogs/ext2fs/bmove.c @@ -0,0 +1,156 @@ +/* vi: set sw=4 ts=4: */ +/* + * bmove.c --- Move blocks around to make way for a particular + * filesystem structure. + * + * Copyright (C) 1997 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" + +struct process_block_struct { + ext2_ino_t ino; + struct ext2_inode * inode; + ext2fs_block_bitmap reserve; + ext2fs_block_bitmap alloc_map; + errcode_t error; + char *buf; + int add_dir; + int flags; +}; + +static int process_block(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, blk_t ref_block, + int ref_offset, void *priv_data) +{ + struct process_block_struct *pb; + errcode_t retval; + int ret; + blk_t block, orig; + + pb = (struct process_block_struct *) priv_data; + block = orig = *block_nr; + ret = 0; + + /* + * Let's see if this is one which we need to relocate + */ + if (ext2fs_test_block_bitmap(pb->reserve, block)) { + do { + if (++block >= fs->super->s_blocks_count) + block = fs->super->s_first_data_block; + if (block == orig) { + pb->error = EXT2_ET_BLOCK_ALLOC_FAIL; + return BLOCK_ABORT; + } + } while (ext2fs_test_block_bitmap(pb->reserve, block) || + ext2fs_test_block_bitmap(pb->alloc_map, block)); + + retval = io_channel_read_blk(fs->io, orig, 1, pb->buf); + if (retval) { + pb->error = retval; + return BLOCK_ABORT; + } + retval = io_channel_write_blk(fs->io, block, 1, pb->buf); + if (retval) { + pb->error = retval; + return BLOCK_ABORT; + } + *block_nr = block; + ext2fs_mark_block_bitmap(pb->alloc_map, block); + ret = BLOCK_CHANGED; + if (pb->flags & EXT2_BMOVE_DEBUG) + printf("ino=%ld, blockcnt=%lld, %d->%d\n", pb->ino, + blockcnt, orig, block); + } + if (pb->add_dir) { + retval = ext2fs_add_dir_block(fs->dblist, pb->ino, + block, (int) blockcnt); + if (retval) { + pb->error = retval; + ret |= BLOCK_ABORT; + } + } + return ret; +} + +errcode_t ext2fs_move_blocks(ext2_filsys fs, + ext2fs_block_bitmap reserve, + ext2fs_block_bitmap alloc_map, + int flags) +{ + ext2_ino_t ino; + struct ext2_inode inode; + errcode_t retval; + struct process_block_struct pb; + ext2_inode_scan scan; + char *block_buf; + + retval = ext2fs_open_inode_scan(fs, 0, &scan); + if (retval) + return retval; + + pb.reserve = reserve; + pb.error = 0; + pb.alloc_map = alloc_map ? alloc_map : fs->block_map; + pb.flags = flags; + + retval = ext2fs_get_mem(fs->blocksize * 4, &block_buf); + if (retval) + return retval; + pb.buf = block_buf + fs->blocksize * 3; + + /* + * If GET_DBLIST is set in the flags field, then we should + * gather directory block information while we're doing the + * block move. + */ + if (flags & EXT2_BMOVE_GET_DBLIST) { + ext2fs_free_dblist(fs->dblist); + fs->dblist = NULL; + retval = ext2fs_init_dblist(fs, 0); + if (retval) + return retval; + } + + retval = ext2fs_get_next_inode(scan, &ino, &inode); + if (retval) + return retval; + + while (ino) { + if ((inode.i_links_count == 0) || + !ext2fs_inode_has_valid_blocks(&inode)) + goto next; + + pb.ino = ino; + pb.inode = &inode; + + pb.add_dir = (LINUX_S_ISDIR(inode.i_mode) && + flags & EXT2_BMOVE_GET_DBLIST); + + retval = ext2fs_block_iterate2(fs, ino, 0, block_buf, + process_block, &pb); + if (retval) + return retval; + if (pb.error) + return pb.error; + + next: + retval = ext2fs_get_next_inode(scan, &ino, &inode); + if (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) + goto next; + } + return 0; +} + diff --git a/e2fsprogs/ext2fs/brel.h b/e2fsprogs/ext2fs/brel.h new file mode 100644 index 000000000..216fd132c --- /dev/null +++ b/e2fsprogs/ext2fs/brel.h @@ -0,0 +1,87 @@ +/* vi: set sw=4 ts=4: */ +/* + * brel.h + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +struct ext2_block_relocate_entry { + blk_t new; + __s16 offset; + __u16 flags; + union { + blk_t block_ref; + ext2_ino_t inode_ref; + } owner; +}; + +#define RELOCATE_TYPE_REF 0x0007 +#define RELOCATE_BLOCK_REF 0x0001 +#define RELOCATE_INODE_REF 0x0002 + +typedef struct ext2_block_relocation_table *ext2_brel; + +struct ext2_block_relocation_table { + __u32 magic; + char *name; + blk_t current; + void *priv_data; + + /* + * Add a block relocation entry. + */ + errcode_t (*put)(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent); + + /* + * Get a block relocation entry. + */ + errcode_t (*get)(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent); + + /* + * Initialize for iterating over the block relocation entries. + */ + errcode_t (*start_iter)(ext2_brel brel); + + /* + * The iterator function for the inode relocation entries. + * Returns an inode number of 0 when out of entries. + */ + errcode_t (*next)(ext2_brel brel, blk_t *old, + struct ext2_block_relocate_entry *ent); + + /* + * Move the inode relocation table from one block number to + * another. + */ + errcode_t (*move)(ext2_brel brel, blk_t old, blk_t new); + + /* + * Remove a block relocation entry. + */ + errcode_t (*delete)(ext2_brel brel, blk_t old); + + + /* + * Free the block relocation table. + */ + errcode_t (*free)(ext2_brel brel); +}; + +errcode_t ext2fs_brel_memarray_create(char *name, blk_t max_block, + ext2_brel *brel); + +#define ext2fs_brel_put(brel, old, ent) ((brel)->put((brel), old, ent)) +#define ext2fs_brel_get(brel, old, ent) ((brel)->get((brel), old, ent)) +#define ext2fs_brel_start_iter(brel) ((brel)->start_iter((brel))) +#define ext2fs_brel_next(brel, old, ent) ((brel)->next((brel), old, ent)) +#define ext2fs_brel_move(brel, old, new) ((brel)->move((brel), old, new)) +#define ext2fs_brel_delete(brel, old) ((brel)->delete((brel), old)) +#define ext2fs_brel_free(brel) ((brel)->free((brel))) + diff --git a/e2fsprogs/ext2fs/brel_ma.c b/e2fsprogs/ext2fs/brel_ma.c new file mode 100644 index 000000000..652a3509c --- /dev/null +++ b/e2fsprogs/ext2fs/brel_ma.c @@ -0,0 +1,196 @@ +/* vi: set sw=4 ts=4: */ +/* + * brel_ma.c + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * TODO: rewrite to not use a direct array!!! (Fortunately this + * module isn't really used yet.) + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" +#include "brel.h" + +static errcode_t bma_put(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent); +static errcode_t bma_get(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent); +static errcode_t bma_start_iter(ext2_brel brel); +static errcode_t bma_next(ext2_brel brel, blk_t *old, + struct ext2_block_relocate_entry *ent); +static errcode_t bma_move(ext2_brel brel, blk_t old, blk_t new); +static errcode_t bma_delete(ext2_brel brel, blk_t old); +static errcode_t bma_free(ext2_brel brel); + +struct brel_ma { + __u32 magic; + blk_t max_block; + struct ext2_block_relocate_entry *entries; +}; + +errcode_t ext2fs_brel_memarray_create(char *name, blk_t max_block, + ext2_brel *new_brel) +{ + ext2_brel brel = 0; + errcode_t retval; + struct brel_ma *ma = 0; + size_t size; + + *new_brel = 0; + + /* + * Allocate memory structures + */ + retval = ext2fs_get_mem(sizeof(struct ext2_block_relocation_table), + &brel); + if (retval) + goto errout; + memset(brel, 0, sizeof(struct ext2_block_relocation_table)); + + retval = ext2fs_get_mem(strlen(name)+1, &brel->name); + if (retval) + goto errout; + strcpy(brel->name, name); + + retval = ext2fs_get_mem(sizeof(struct brel_ma), &ma); + if (retval) + goto errout; + memset(ma, 0, sizeof(struct brel_ma)); + brel->priv_data = ma; + + size = (size_t) (sizeof(struct ext2_block_relocate_entry) * + (max_block+1)); + retval = ext2fs_get_mem(size, &ma->entries); + if (retval) + goto errout; + memset(ma->entries, 0, size); + ma->max_block = max_block; + + /* + * Fill in the brel data structure + */ + brel->put = bma_put; + brel->get = bma_get; + brel->start_iter = bma_start_iter; + brel->next = bma_next; + brel->move = bma_move; + brel->delete = bma_delete; + brel->free = bma_free; + + *new_brel = brel; + return 0; + +errout: + bma_free(brel); + return retval; +} + +static errcode_t bma_put(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent) +{ + struct brel_ma *ma; + + ma = brel->priv_data; + if (old > ma->max_block) + return EXT2_ET_INVALID_ARGUMENT; + ma->entries[(unsigned)old] = *ent; + return 0; +} + +static errcode_t bma_get(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent) +{ + struct brel_ma *ma; + + ma = brel->priv_data; + if (old > ma->max_block) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned)old].new == 0) + return ENOENT; + *ent = ma->entries[old]; + return 0; +} + +static errcode_t bma_start_iter(ext2_brel brel) +{ + brel->current = 0; + return 0; +} + +static errcode_t bma_next(ext2_brel brel, blk_t *old, + struct ext2_block_relocate_entry *ent) +{ + struct brel_ma *ma; + + ma = brel->priv_data; + while (++brel->current < ma->max_block) { + if (ma->entries[(unsigned)brel->current].new == 0) + continue; + *old = brel->current; + *ent = ma->entries[(unsigned)brel->current]; + return 0; + } + *old = 0; + return 0; +} + +static errcode_t bma_move(ext2_brel brel, blk_t old, blk_t new) +{ + struct brel_ma *ma; + + ma = brel->priv_data; + if ((old > ma->max_block) || (new > ma->max_block)) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned)old].new == 0) + return ENOENT; + ma->entries[(unsigned)new] = ma->entries[old]; + ma->entries[(unsigned)old].new = 0; + return 0; +} + +static errcode_t bma_delete(ext2_brel brel, blk_t old) +{ + struct brel_ma *ma; + + ma = brel->priv_data; + if (old > ma->max_block) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned)old].new == 0) + return ENOENT; + ma->entries[(unsigned)old].new = 0; + return 0; +} + +static errcode_t bma_free(ext2_brel brel) +{ + struct brel_ma *ma; + + if (!brel) + return 0; + + ma = brel->priv_data; + + if (ma) { + ext2fs_free_mem(&ma->entries); + ext2fs_free_mem(&ma); + } + ext2fs_free_mem(&brel->name); + ext2fs_free_mem(&brel); + return 0; +} diff --git a/e2fsprogs/ext2fs/check_desc.c b/e2fsprogs/ext2fs/check_desc.c new file mode 100644 index 000000000..dd4b0e9cf --- /dev/null +++ b/e2fsprogs/ext2fs/check_desc.c @@ -0,0 +1,69 @@ +/* vi: set sw=4 ts=4: */ +/* + * check_desc.c --- Check the group descriptors of an ext2 filesystem + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * This routine sanity checks the group descriptors + */ +errcode_t ext2fs_check_desc(ext2_filsys fs) +{ + dgrp_t i; + blk_t block = fs->super->s_first_data_block; + blk_t next; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + for (i = 0; i < fs->group_desc_count; i++) { + next = block + fs->super->s_blocks_per_group; + /* + * Check to make sure block bitmap for group is + * located within the group. + */ + if (fs->group_desc[i].bg_block_bitmap < block || + fs->group_desc[i].bg_block_bitmap >= next) + return EXT2_ET_GDESC_BAD_BLOCK_MAP; + /* + * Check to make sure inode bitmap for group is + * located within the group + */ + if (fs->group_desc[i].bg_inode_bitmap < block || + fs->group_desc[i].bg_inode_bitmap >= next) + return EXT2_ET_GDESC_BAD_INODE_MAP; + /* + * Check to make sure inode table for group is located + * within the group + */ + if (fs->group_desc[i].bg_inode_table < block || + ((fs->group_desc[i].bg_inode_table + + fs->inode_blocks_per_group) >= next)) + return EXT2_ET_GDESC_BAD_INODE_TABLE; + + block = next; + } + return 0; +} diff --git a/e2fsprogs/ext2fs/closefs.c b/e2fsprogs/ext2fs/closefs.c new file mode 100644 index 000000000..008d5f36e --- /dev/null +++ b/e2fsprogs/ext2fs/closefs.c @@ -0,0 +1,381 @@ +/* vi: set sw=4 ts=4: */ +/* + * closefs.c --- close an ext2 filesystem + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fsP.h" + +static int test_root(int a, int b) +{ + if (a == 0) + return 1; + while (1) { + if (a == 1) + return 1; + if (a % b) + return 0; + a = a / b; + } +} + +int ext2fs_bg_has_super(ext2_filsys fs, int group_block) +{ + if (!(fs->super->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) + return 1; + + if (test_root(group_block, 3) || (test_root(group_block, 5)) || + test_root(group_block, 7)) + return 1; + + return 0; +} + +int ext2fs_super_and_bgd_loc(ext2_filsys fs, + dgrp_t group, + blk_t *ret_super_blk, + blk_t *ret_old_desc_blk, + blk_t *ret_new_desc_blk, + int *ret_meta_bg) +{ + blk_t group_block, super_blk = 0, old_desc_blk = 0, new_desc_blk = 0; + unsigned int meta_bg, meta_bg_size; + int numblocks, has_super; + int old_desc_blocks; + + group_block = fs->super->s_first_data_block + + (group * fs->super->s_blocks_per_group); + + if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) + old_desc_blocks = fs->super->s_first_meta_bg; + else + old_desc_blocks = + fs->desc_blocks + fs->super->s_reserved_gdt_blocks; + + if (group == fs->group_desc_count-1) { + numblocks = (fs->super->s_blocks_count - + fs->super->s_first_data_block) % + fs->super->s_blocks_per_group; + if (!numblocks) + numblocks = fs->super->s_blocks_per_group; + } else + numblocks = fs->super->s_blocks_per_group; + + has_super = ext2fs_bg_has_super(fs, group); + + if (has_super) { + super_blk = group_block; + numblocks--; + } + meta_bg_size = (fs->blocksize / sizeof (struct ext2_group_desc)); + meta_bg = group / meta_bg_size; + + if (!(fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) || + (meta_bg < fs->super->s_first_meta_bg)) { + if (has_super) { + old_desc_blk = group_block + 1; + numblocks -= old_desc_blocks; + } + } else { + if (((group % meta_bg_size) == 0) || + ((group % meta_bg_size) == 1) || + ((group % meta_bg_size) == (meta_bg_size-1))) { + if (has_super) + has_super = 1; + new_desc_blk = group_block + has_super; + numblocks--; + } + } + + numblocks -= 2 + fs->inode_blocks_per_group; + + if (ret_super_blk) + *ret_super_blk = super_blk; + if (ret_old_desc_blk) + *ret_old_desc_blk = old_desc_blk; + if (ret_new_desc_blk) + *ret_new_desc_blk = new_desc_blk; + if (ret_meta_bg) + *ret_meta_bg = meta_bg; + return numblocks; +} + + +/* + * This function forces out the primary superblock. We need to only + * write out those fields which we have changed, since if the + * filesystem is mounted, it may have changed some of the other + * fields. + * + * It takes as input a superblock which has already been byte swapped + * (if necessary). + * + */ +static errcode_t write_primary_superblock(ext2_filsys fs, + struct ext2_super_block *super) +{ + __u16 *old_super, *new_super; + int check_idx, write_idx, size; + errcode_t retval; + + if (!fs->io->manager->write_byte || !fs->orig_super) { + io_channel_set_blksize(fs->io, SUPERBLOCK_OFFSET); + retval = io_channel_write_blk(fs->io, 1, -SUPERBLOCK_SIZE, + super); + io_channel_set_blksize(fs->io, fs->blocksize); + return retval; + } + + old_super = (__u16 *) fs->orig_super; + new_super = (__u16 *) super; + + for (check_idx = 0; check_idx < SUPERBLOCK_SIZE/2; check_idx++) { + if (old_super[check_idx] == new_super[check_idx]) + continue; + write_idx = check_idx; + for (check_idx++; check_idx < SUPERBLOCK_SIZE/2; check_idx++) + if (old_super[check_idx] == new_super[check_idx]) + break; + size = 2 * (check_idx - write_idx); + retval = io_channel_write_byte(fs->io, + SUPERBLOCK_OFFSET + (2 * write_idx), size, + new_super + write_idx); + if (retval) + return retval; + } + memcpy(fs->orig_super, super, SUPERBLOCK_SIZE); + return 0; +} + + +/* + * Updates the revision to EXT2_DYNAMIC_REV + */ +void ext2fs_update_dynamic_rev(ext2_filsys fs) +{ + struct ext2_super_block *sb = fs->super; + + if (sb->s_rev_level > EXT2_GOOD_OLD_REV) + return; + + sb->s_rev_level = EXT2_DYNAMIC_REV; + sb->s_first_ino = EXT2_GOOD_OLD_FIRST_INO; + sb->s_inode_size = EXT2_GOOD_OLD_INODE_SIZE; + /* s_uuid is handled by e2fsck already */ + /* other fields should be left alone */ +} + +static errcode_t write_backup_super(ext2_filsys fs, dgrp_t group, + blk_t group_block, + struct ext2_super_block *super_shadow) +{ + dgrp_t sgrp = group; + + if (sgrp > ((1 << 16) - 1)) + sgrp = (1 << 16) - 1; +#if BB_BIG_ENDIAN + if (fs->flags & EXT2_FLAG_SWAP_BYTES) + super_shadow->s_block_group_nr = ext2fs_swab16(sgrp); + else +#endif + fs->super->s_block_group_nr = sgrp; + + return io_channel_write_blk(fs->io, group_block, -SUPERBLOCK_SIZE, + super_shadow); +} + + +errcode_t ext2fs_flush(ext2_filsys fs) +{ + dgrp_t i; + blk_t group_block; + errcode_t retval; + unsigned long fs_state; + struct ext2_super_block *super_shadow = 0; + struct ext2_group_desc *group_shadow = 0; + char *group_ptr; + int old_desc_blocks; +#if BB_BIG_ENDIAN + dgrp_t j; + struct ext2_group_desc *s, *t; +#endif + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + fs_state = fs->super->s_state; + + fs->super->s_wtime = time(NULL); + fs->super->s_block_group_nr = 0; +#if BB_BIG_ENDIAN + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + retval = EXT2_ET_NO_MEMORY; + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &super_shadow); + if (retval) + goto errout; + retval = ext2fs_get_mem((size_t)(fs->blocksize * + fs->desc_blocks), + &group_shadow); + if (retval) + goto errout; + memset(group_shadow, 0, (size_t) fs->blocksize * + fs->desc_blocks); + + /* swap the group descriptors */ + for (j=0, s=fs->group_desc, t=group_shadow; + j < fs->group_desc_count; j++, t++, s++) { + *t = *s; + ext2fs_swap_group_desc(t); + } + } else { + super_shadow = fs->super; + group_shadow = fs->group_desc; + } +#else + super_shadow = fs->super; + group_shadow = fs->group_desc; +#endif + + /* + * If this is an external journal device, don't write out the + * block group descriptors or any of the backup superblocks + */ + if (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) + goto write_primary_superblock_only; + + /* + * Set the state of the FS to be non-valid. (The state has + * already been backed up earlier, and will be restored after + * we write out the backup superblocks.) + */ + fs->super->s_state &= ~EXT2_VALID_FS; +#if BB_BIG_ENDIAN + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + *super_shadow = *fs->super; + ext2fs_swap_super(super_shadow); + } +#endif + + /* + * Write out the master group descriptors, and the backup + * superblocks and group descriptors. + */ + group_block = fs->super->s_first_data_block; + group_ptr = (char *) group_shadow; + if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) + old_desc_blocks = fs->super->s_first_meta_bg; + else + old_desc_blocks = fs->desc_blocks; + + for (i = 0; i < fs->group_desc_count; i++) { + blk_t super_blk, old_desc_blk, new_desc_blk; + int meta_bg; + + ext2fs_super_and_bgd_loc(fs, i, &super_blk, &old_desc_blk, + &new_desc_blk, &meta_bg); + + if (!(fs->flags & EXT2_FLAG_MASTER_SB_ONLY) &&i && super_blk) { + retval = write_backup_super(fs, i, super_blk, + super_shadow); + if (retval) + goto errout; + } + if (fs->flags & EXT2_FLAG_SUPER_ONLY) + continue; + if ((old_desc_blk) && + (!(fs->flags & EXT2_FLAG_MASTER_SB_ONLY) || (i == 0))) { + retval = io_channel_write_blk(fs->io, + old_desc_blk, old_desc_blocks, group_ptr); + if (retval) + goto errout; + } + if (new_desc_blk) { + retval = io_channel_write_blk(fs->io, new_desc_blk, + 1, group_ptr + (meta_bg*fs->blocksize)); + if (retval) + goto errout; + } + } + fs->super->s_block_group_nr = 0; + fs->super->s_state = fs_state; +#if BB_BIG_ENDIAN + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + *super_shadow = *fs->super; + ext2fs_swap_super(super_shadow); + } +#endif + + /* + * If the write_bitmaps() function is present, call it to + * flush the bitmaps. This is done this way so that a simple + * program that doesn't mess with the bitmaps doesn't need to + * drag in the bitmaps.c code. + */ + if (fs->write_bitmaps) { + retval = fs->write_bitmaps(fs); + if (retval) + goto errout; + } + +write_primary_superblock_only: + /* + * Write out master superblock. This has to be done + * separately, since it is located at a fixed location + * (SUPERBLOCK_OFFSET). We flush all other pending changes + * out to disk first, just to avoid a race condition with an + * insy-tinsy window.... + */ + retval = io_channel_flush(fs->io); + retval = write_primary_superblock(fs, super_shadow); + if (retval) + goto errout; + + fs->flags &= ~EXT2_FLAG_DIRTY; + + retval = io_channel_flush(fs->io); +errout: + fs->super->s_state = fs_state; + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + if (super_shadow) + ext2fs_free_mem(&super_shadow); + if (group_shadow) + ext2fs_free_mem(&group_shadow); + } + return retval; +} + +errcode_t ext2fs_close(ext2_filsys fs) +{ + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (fs->flags & EXT2_FLAG_DIRTY) { + retval = ext2fs_flush(fs); + if (retval) + return retval; + } + if (fs->write_bitmaps) { + retval = fs->write_bitmaps(fs); + if (retval) + return retval; + } + ext2fs_free(fs); + return 0; +} + diff --git a/e2fsprogs/ext2fs/cmp_bitmaps.c b/e2fsprogs/ext2fs/cmp_bitmaps.c new file mode 100644 index 000000000..05b8eb8d7 --- /dev/null +++ b/e2fsprogs/ext2fs/cmp_bitmaps.c @@ -0,0 +1,73 @@ +/* vi: set sw=4 ts=4: */ +/* + * cmp_bitmaps.c --- routines to compare inode and block bitmaps. + * + * Copyright (C) 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_compare_block_bitmap(ext2fs_block_bitmap bm1, + ext2fs_block_bitmap bm2) +{ + blk_t i; + + EXT2_CHECK_MAGIC(bm1, EXT2_ET_MAGIC_BLOCK_BITMAP); + EXT2_CHECK_MAGIC(bm2, EXT2_ET_MAGIC_BLOCK_BITMAP); + + if ((bm1->start != bm2->start) || + (bm1->end != bm2->end) || + (memcmp(bm1->bitmap, bm2->bitmap, + (size_t) (bm1->end - bm1->start)/8))) + return EXT2_ET_NEQ_BLOCK_BITMAP; + + for (i = bm1->end - ((bm1->end - bm1->start) % 8); i <= bm1->end; i++) + if (ext2fs_fast_test_block_bitmap(bm1, i) != + ext2fs_fast_test_block_bitmap(bm2, i)) + return EXT2_ET_NEQ_BLOCK_BITMAP; + + return 0; +} + +errcode_t ext2fs_compare_inode_bitmap(ext2fs_inode_bitmap bm1, + ext2fs_inode_bitmap bm2) +{ + ext2_ino_t i; + + EXT2_CHECK_MAGIC(bm1, EXT2_ET_MAGIC_INODE_BITMAP); + EXT2_CHECK_MAGIC(bm2, EXT2_ET_MAGIC_INODE_BITMAP); + + if ((bm1->start != bm2->start) || + (bm1->end != bm2->end) || + (memcmp(bm1->bitmap, bm2->bitmap, + (size_t) (bm1->end - bm1->start)/8))) + return EXT2_ET_NEQ_INODE_BITMAP; + + for (i = bm1->end - ((bm1->end - bm1->start) % 8); i <= bm1->end; i++) + if (ext2fs_fast_test_inode_bitmap(bm1, i) != + ext2fs_fast_test_inode_bitmap(bm2, i)) + return EXT2_ET_NEQ_INODE_BITMAP; + + return 0; +} + diff --git a/e2fsprogs/ext2fs/dblist.c b/e2fsprogs/ext2fs/dblist.c new file mode 100644 index 000000000..06ff6d807 --- /dev/null +++ b/e2fsprogs/ext2fs/dblist.c @@ -0,0 +1,260 @@ +/* vi: set sw=4 ts=4: */ +/* + * dblist.c -- directory block list functions + * + * Copyright 1997 by Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fsP.h" + +static int dir_block_cmp(const void *a, const void *b); + +/* + * Returns the number of directories in the filesystem as reported by + * the group descriptors. Of course, the group descriptors could be + * wrong! + */ +errcode_t ext2fs_get_num_dirs(ext2_filsys fs, ext2_ino_t *ret_num_dirs) +{ + dgrp_t i; + ext2_ino_t num_dirs, max_dirs; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + num_dirs = 0; + max_dirs = fs->super->s_inodes_per_group; + for (i = 0; i < fs->group_desc_count; i++) { + if (fs->group_desc[i].bg_used_dirs_count > max_dirs) + num_dirs += max_dirs / 8; + else + num_dirs += fs->group_desc[i].bg_used_dirs_count; + } + if (num_dirs > fs->super->s_inodes_count) + num_dirs = fs->super->s_inodes_count; + + *ret_num_dirs = num_dirs; + + return 0; +} + +/* + * helper function for making a new directory block list (for + * initialize and copy). + */ +static errcode_t make_dblist(ext2_filsys fs, ext2_ino_t size, ext2_ino_t count, + struct ext2_db_entry *list, + ext2_dblist *ret_dblist) +{ + ext2_dblist dblist; + errcode_t retval; + size_t len; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if ((ret_dblist == 0) && fs->dblist && + (fs->dblist->magic == EXT2_ET_MAGIC_DBLIST)) + return 0; + + retval = ext2fs_get_mem(sizeof(struct ext2_struct_dblist), &dblist); + if (retval) + return retval; + memset(dblist, 0, sizeof(struct ext2_struct_dblist)); + + dblist->magic = EXT2_ET_MAGIC_DBLIST; + dblist->fs = fs; + if (size) + dblist->size = size; + else { + retval = ext2fs_get_num_dirs(fs, &dblist->size); + if (retval) + goto cleanup; + dblist->size = (dblist->size * 2) + 12; + } + len = (size_t) sizeof(struct ext2_db_entry) * dblist->size; + dblist->count = count; + retval = ext2fs_get_mem(len, &dblist->list); + if (retval) + goto cleanup; + + if (list) + memcpy(dblist->list, list, len); + else + memset(dblist->list, 0, len); + if (ret_dblist) + *ret_dblist = dblist; + else + fs->dblist = dblist; + return 0; +cleanup: + ext2fs_free_mem(&dblist); + return retval; +} + +/* + * Initialize a directory block list + */ +errcode_t ext2fs_init_dblist(ext2_filsys fs, ext2_dblist *ret_dblist) +{ + ext2_dblist dblist; + errcode_t retval; + + retval = make_dblist(fs, 0, 0, 0, &dblist); + if (retval) + return retval; + + dblist->sorted = 1; + if (ret_dblist) + *ret_dblist = dblist; + else + fs->dblist = dblist; + + return 0; +} + +/* + * Copy a directory block list + */ +errcode_t ext2fs_copy_dblist(ext2_dblist src, ext2_dblist *dest) +{ + ext2_dblist dblist; + errcode_t retval; + + retval = make_dblist(src->fs, src->size, src->count, src->list, + &dblist); + if (retval) + return retval; + dblist->sorted = src->sorted; + *dest = dblist; + return 0; +} + +/* + * Close a directory block list + * + * (moved to closefs.c) + */ + + +/* + * Add a directory block to the directory block list + */ +errcode_t ext2fs_add_dir_block(ext2_dblist dblist, ext2_ino_t ino, blk_t blk, + int blockcnt) +{ + struct ext2_db_entry *new_entry; + errcode_t retval; + unsigned long old_size; + + EXT2_CHECK_MAGIC(dblist, EXT2_ET_MAGIC_DBLIST); + + if (dblist->count >= dblist->size) { + old_size = dblist->size * sizeof(struct ext2_db_entry); + dblist->size += 100; + retval = ext2fs_resize_mem(old_size, (size_t) dblist->size * + sizeof(struct ext2_db_entry), + &dblist->list); + if (retval) { + dblist->size -= 100; + return retval; + } + } + new_entry = dblist->list + ( (int) dblist->count++); + new_entry->blk = blk; + new_entry->ino = ino; + new_entry->blockcnt = blockcnt; + + dblist->sorted = 0; + + return 0; +} + +/* + * Change the directory block to the directory block list + */ +errcode_t ext2fs_set_dir_block(ext2_dblist dblist, ext2_ino_t ino, blk_t blk, + int blockcnt) +{ + dgrp_t i; + + EXT2_CHECK_MAGIC(dblist, EXT2_ET_MAGIC_DBLIST); + + for (i=0; i < dblist->count; i++) { + if ((dblist->list[i].ino != ino) || + (dblist->list[i].blockcnt != blockcnt)) + continue; + dblist->list[i].blk = blk; + dblist->sorted = 0; + return 0; + } + return EXT2_ET_DB_NOT_FOUND; +} + +void ext2fs_dblist_sort(ext2_dblist dblist, + int (*sortfunc)(const void *, + const void *)) +{ + if (!sortfunc) + sortfunc = dir_block_cmp; + qsort(dblist->list, (size_t) dblist->count, + sizeof(struct ext2_db_entry), sortfunc); + dblist->sorted = 1; +} + +/* + * This function iterates over the directory block list + */ +errcode_t ext2fs_dblist_iterate(ext2_dblist dblist, + int (*func)(ext2_filsys fs, + struct ext2_db_entry *db_info, + void *priv_data), + void *priv_data) +{ + ext2_ino_t i; + int ret; + + EXT2_CHECK_MAGIC(dblist, EXT2_ET_MAGIC_DBLIST); + + if (!dblist->sorted) + ext2fs_dblist_sort(dblist, 0); + for (i=0; i < dblist->count; i++) { + ret = (*func)(dblist->fs, &dblist->list[(int)i], priv_data); + if (ret & DBLIST_ABORT) + return 0; + } + return 0; +} + +static int dir_block_cmp(const void *a, const void *b) +{ + const struct ext2_db_entry *db_a = + (const struct ext2_db_entry *) a; + const struct ext2_db_entry *db_b = + (const struct ext2_db_entry *) b; + + if (db_a->blk != db_b->blk) + return (int) (db_a->blk - db_b->blk); + + if (db_a->ino != db_b->ino) + return (int) (db_a->ino - db_b->ino); + + return (int) (db_a->blockcnt - db_b->blockcnt); +} + +int ext2fs_dblist_count(ext2_dblist dblist) +{ + return (int) dblist->count; +} diff --git a/e2fsprogs/ext2fs/dblist_dir.c b/e2fsprogs/ext2fs/dblist_dir.c new file mode 100644 index 000000000..b23920466 --- /dev/null +++ b/e2fsprogs/ext2fs/dblist_dir.c @@ -0,0 +1,76 @@ +/* vi: set sw=4 ts=4: */ +/* + * dblist_dir.c --- iterate by directory entry + * + * Copyright 1997 by Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fsP.h" + +static int db_dir_proc(ext2_filsys fs, struct ext2_db_entry *db_info, + void *priv_data); + +errcode_t ext2fs_dblist_dir_iterate(ext2_dblist dblist, + int flags, + char *block_buf, + int (*func)(ext2_ino_t dir, + int entry, + struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data) +{ + errcode_t retval; + struct dir_context ctx; + + EXT2_CHECK_MAGIC(dblist, EXT2_ET_MAGIC_DBLIST); + + ctx.dir = 0; + ctx.flags = flags; + if (block_buf) + ctx.buf = block_buf; + else { + retval = ext2fs_get_mem(dblist->fs->blocksize, &ctx.buf); + if (retval) + return retval; + } + ctx.func = func; + ctx.priv_data = priv_data; + ctx.errcode = 0; + + retval = ext2fs_dblist_iterate(dblist, db_dir_proc, &ctx); + + if (!block_buf) + ext2fs_free_mem(&ctx.buf); + if (retval) + return retval; + return ctx.errcode; +} + +static int db_dir_proc(ext2_filsys fs, struct ext2_db_entry *db_info, + void *priv_data) +{ + struct dir_context *ctx; + + ctx = (struct dir_context *) priv_data; + ctx->dir = db_info->ino; + + return ext2fs_process_dir_block(fs, &db_info->blk, + db_info->blockcnt, 0, 0, priv_data); +} diff --git a/e2fsprogs/ext2fs/dir_iterate.c b/e2fsprogs/ext2fs/dir_iterate.c new file mode 100644 index 000000000..b7d873595 --- /dev/null +++ b/e2fsprogs/ext2fs/dir_iterate.c @@ -0,0 +1,220 @@ +/* vi: set sw=4 ts=4: */ +/* + * dir_iterate.c --- ext2fs directory iteration operations + * + * Copyright (C) 1993, 1994, 1994, 1995, 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" + +/* + * This function checks to see whether or not a potential deleted + * directory entry looks valid. What we do is check the deleted entry + * and each successive entry to make sure that they all look valid and + * that the last deleted entry ends at the beginning of the next + * undeleted entry. Returns 1 if the deleted entry looks valid, zero + * if not valid. + */ +static int ext2fs_validate_entry(char *buf, int offset, int final_offset) +{ + struct ext2_dir_entry *dirent; + + while (offset < final_offset) { + dirent = (struct ext2_dir_entry *)(buf + offset); + offset += dirent->rec_len; + if ((dirent->rec_len < 8) || + ((dirent->rec_len % 4) != 0) || + (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) + return 0; + } + return (offset == final_offset); +} + +errcode_t ext2fs_dir_iterate2(ext2_filsys fs, + ext2_ino_t dir, + int flags, + char *block_buf, + int (*func)(ext2_ino_t dir, + int entry, + struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data) +{ + struct dir_context ctx; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_check_directory(fs, dir); + if (retval) + return retval; + + ctx.dir = dir; + ctx.flags = flags; + if (block_buf) + ctx.buf = block_buf; + else { + retval = ext2fs_get_mem(fs->blocksize, &ctx.buf); + if (retval) + return retval; + } + ctx.func = func; + ctx.priv_data = priv_data; + ctx.errcode = 0; + retval = ext2fs_block_iterate2(fs, dir, 0, 0, + ext2fs_process_dir_block, &ctx); + if (!block_buf) + ext2fs_free_mem(&ctx.buf); + if (retval) + return retval; + return ctx.errcode; +} + +struct xlate { + int (*func)(struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data); + void *real_private; +}; + +static int xlate_func(ext2_ino_t dir EXT2FS_ATTR((unused)), + int entry EXT2FS_ATTR((unused)), + struct ext2_dir_entry *dirent, int offset, + int blocksize, char *buf, void *priv_data) +{ + struct xlate *xl = (struct xlate *) priv_data; + + return (*xl->func)(dirent, offset, blocksize, buf, xl->real_private); +} + +extern errcode_t ext2fs_dir_iterate(ext2_filsys fs, + ext2_ino_t dir, + int flags, + char *block_buf, + int (*func)(struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data) +{ + struct xlate xl; + + xl.real_private = priv_data; + xl.func = func; + + return ext2fs_dir_iterate2(fs, dir, flags, block_buf, + xlate_func, &xl); +} + + +/* + * Helper function which is private to this module. Used by + * ext2fs_dir_iterate() and ext2fs_dblist_dir_iterate() + */ +int ext2fs_process_dir_block(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct dir_context *ctx = (struct dir_context *) priv_data; + unsigned int offset = 0; + unsigned int next_real_entry = 0; + int ret = 0; + int changed = 0; + int do_abort = 0; + int entry, size; + struct ext2_dir_entry *dirent; + + if (blockcnt < 0) + return 0; + + entry = blockcnt ? DIRENT_OTHER_FILE : DIRENT_DOT_FILE; + + ctx->errcode = ext2fs_read_dir_block(fs, *blocknr, ctx->buf); + if (ctx->errcode) + return BLOCK_ABORT; + + while (offset < fs->blocksize) { + dirent = (struct ext2_dir_entry *) (ctx->buf + offset); + if (((offset + dirent->rec_len) > fs->blocksize) || + (dirent->rec_len < 8) || + ((dirent->rec_len % 4) != 0) || + (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) { + ctx->errcode = EXT2_ET_DIR_CORRUPTED; + return BLOCK_ABORT; + } + if (!dirent->inode && + !(ctx->flags & DIRENT_FLAG_INCLUDE_EMPTY)) + goto next; + + ret = (ctx->func)(ctx->dir, + (next_real_entry > offset) ? + DIRENT_DELETED_FILE : entry, + dirent, offset, + fs->blocksize, ctx->buf, + ctx->priv_data); + if (entry < DIRENT_OTHER_FILE) + entry++; + + if (ret & DIRENT_CHANGED) + changed++; + if (ret & DIRENT_ABORT) { + do_abort++; + break; + } +next: + if (next_real_entry == offset) + next_real_entry += dirent->rec_len; + + if (ctx->flags & DIRENT_FLAG_INCLUDE_REMOVED) { + size = ((dirent->name_len & 0xFF) + 11) & ~3; + + if (dirent->rec_len != size) { + unsigned int final_offset; + + final_offset = offset + dirent->rec_len; + offset += size; + while (offset < final_offset && + !ext2fs_validate_entry(ctx->buf, + offset, + final_offset)) + offset += 4; + continue; + } + } + offset += dirent->rec_len; + } + + if (changed) { + ctx->errcode = ext2fs_write_dir_block(fs, *blocknr, ctx->buf); + if (ctx->errcode) + return BLOCK_ABORT; + } + if (do_abort) + return BLOCK_ABORT; + return 0; +} + diff --git a/e2fsprogs/ext2fs/dirblock.c b/e2fsprogs/ext2fs/dirblock.c new file mode 100644 index 000000000..5d3f6a1bc --- /dev/null +++ b/e2fsprogs/ext2fs/dirblock.c @@ -0,0 +1,133 @@ +/* vi: set sw=4 ts=4: */ +/* + * dirblock.c --- directory block routines. + * + * Copyright (C) 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_read_dir_block2(ext2_filsys fs, blk_t block, + void *buf, int flags EXT2FS_ATTR((unused))) +{ + errcode_t retval; + char *p, *end; + struct ext2_dir_entry *dirent; + unsigned int name_len, rec_len; +#if BB_BIG_ENDIAN + unsigned int do_swap; +#endif + + retval = io_channel_read_blk(fs->io, block, 1, buf); + if (retval) + return retval; +#if BB_BIG_ENDIAN + do_swap = (fs->flags & (EXT2_FLAG_SWAP_BYTES| + EXT2_FLAG_SWAP_BYTES_READ)) != 0; +#endif + p = (char *) buf; + end = (char *) buf + fs->blocksize; + while (p < end-8) { + dirent = (struct ext2_dir_entry *) p; +#if BB_BIG_ENDIAN + if (do_swap) { + dirent->inode = ext2fs_swab32(dirent->inode); + dirent->rec_len = ext2fs_swab16(dirent->rec_len); + dirent->name_len = ext2fs_swab16(dirent->name_len); + } +#endif + name_len = dirent->name_len; +#ifdef WORDS_BIGENDIAN + if (flags & EXT2_DIRBLOCK_V2_STRUCT) + dirent->name_len = ext2fs_swab16(dirent->name_len); +#endif + rec_len = dirent->rec_len; + if ((rec_len < 8) || (rec_len % 4)) { + rec_len = 8; + retval = EXT2_ET_DIR_CORRUPTED; + } + if (((name_len & 0xFF) + 8) > dirent->rec_len) + retval = EXT2_ET_DIR_CORRUPTED; + p += rec_len; + } + return retval; +} + +errcode_t ext2fs_read_dir_block(ext2_filsys fs, blk_t block, + void *buf) +{ + return ext2fs_read_dir_block2(fs, block, buf, 0); +} + + +errcode_t ext2fs_write_dir_block2(ext2_filsys fs, blk_t block, + void *inbuf, int flags EXT2FS_ATTR((unused))) +{ +#if BB_BIG_ENDIAN + int do_swap = 0; + errcode_t retval; + char *p, *end; + char *buf = 0; + struct ext2_dir_entry *dirent; + + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE)) + do_swap = 1; + +#ifndef WORDS_BIGENDIAN + if (!do_swap) + return io_channel_write_blk(fs->io, block, 1, (char *) inbuf); +#endif + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + memcpy(buf, inbuf, fs->blocksize); + p = buf; + end = buf + fs->blocksize; + while (p < end) { + dirent = (struct ext2_dir_entry *) p; + if ((dirent->rec_len < 8) || + (dirent->rec_len % 4)) { + ext2fs_free_mem(&buf); + return EXT2_ET_DIR_CORRUPTED; + } + p += dirent->rec_len; + if (do_swap) { + dirent->inode = ext2fs_swab32(dirent->inode); + dirent->rec_len = ext2fs_swab16(dirent->rec_len); + dirent->name_len = ext2fs_swab16(dirent->name_len); + } +#ifdef WORDS_BIGENDIAN + if (flags & EXT2_DIRBLOCK_V2_STRUCT) + dirent->name_len = ext2fs_swab16(dirent->name_len); +#endif + } + retval = io_channel_write_blk(fs->io, block, 1, buf); + ext2fs_free_mem(&buf); + return retval; +#else + return io_channel_write_blk(fs->io, block, 1, (char *) inbuf); +#endif +} + + +errcode_t ext2fs_write_dir_block(ext2_filsys fs, blk_t block, + void *inbuf) +{ + return ext2fs_write_dir_block2(fs, block, inbuf, 0); +} + diff --git a/e2fsprogs/ext2fs/dirhash.c b/e2fsprogs/ext2fs/dirhash.c new file mode 100644 index 000000000..ab3243fd7 --- /dev/null +++ b/e2fsprogs/ext2fs/dirhash.c @@ -0,0 +1,234 @@ +/* vi: set sw=4 ts=4: */ +/* + * dirhash.c -- Calculate the hash of a directory entry + * + * Copyright (c) 2001 Daniel Phillips + * + * Copyright (c) 2002 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * Keyed 32-bit hash function using TEA in a Davis-Meyer function + * H0 = Key + * Hi = E Mi(Hi-1) + Hi-1 + * + * (see Applied Cryptography, 2nd edition, p448). + * + * Jeremy Fitzhardinge 1998 + * + * This code is made available under the terms of the GPL + */ +#define DELTA 0x9E3779B9 + +static void TEA_transform(__u32 buf[4], __u32 const in[]) +{ + __u32 sum = 0; + __u32 b0 = buf[0], b1 = buf[1]; + __u32 a = in[0], b = in[1], c = in[2], d = in[3]; + int n = 16; + + do { + sum += DELTA; + b0 += ((b1 << 4)+a) ^ (b1+sum) ^ ((b1 >> 5)+b); + b1 += ((b0 << 4)+c) ^ (b0+sum) ^ ((b0 >> 5)+d); + } while(--n); + + buf[0] += b0; + buf[1] += b1; +} + +/* F, G and H are basic MD4 functions: selection, majority, parity */ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) (((x) & (y)) + (((x) ^ (y)) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* + * The generic round function. The application is so specific that + * we don't bother protecting all the arguments with parens, as is generally + * good macro practice, in favor of extra legibility. + * Rotation is separate from addition to prevent recomputation + */ +#define ROUND(f, a, b, c, d, x, s) \ + (a += f(b, c, d) + x, a = (a << s) | (a >> (32-s))) +#define K1 0 +#define K2 013240474631UL +#define K3 015666365641UL + +/* + * Basic cut-down MD4 transform. Returns only 32 bits of result. + */ +static void halfMD4Transform (__u32 buf[4], __u32 const in[]) +{ + __u32 a = buf[0], b = buf[1], c = buf[2], d = buf[3]; + + /* Round 1 */ + ROUND(F, a, b, c, d, in[0] + K1, 3); + ROUND(F, d, a, b, c, in[1] + K1, 7); + ROUND(F, c, d, a, b, in[2] + K1, 11); + ROUND(F, b, c, d, a, in[3] + K1, 19); + ROUND(F, a, b, c, d, in[4] + K1, 3); + ROUND(F, d, a, b, c, in[5] + K1, 7); + ROUND(F, c, d, a, b, in[6] + K1, 11); + ROUND(F, b, c, d, a, in[7] + K1, 19); + + /* Round 2 */ + ROUND(G, a, b, c, d, in[1] + K2, 3); + ROUND(G, d, a, b, c, in[3] + K2, 5); + ROUND(G, c, d, a, b, in[5] + K2, 9); + ROUND(G, b, c, d, a, in[7] + K2, 13); + ROUND(G, a, b, c, d, in[0] + K2, 3); + ROUND(G, d, a, b, c, in[2] + K2, 5); + ROUND(G, c, d, a, b, in[4] + K2, 9); + ROUND(G, b, c, d, a, in[6] + K2, 13); + + /* Round 3 */ + ROUND(H, a, b, c, d, in[3] + K3, 3); + ROUND(H, d, a, b, c, in[7] + K3, 9); + ROUND(H, c, d, a, b, in[2] + K3, 11); + ROUND(H, b, c, d, a, in[6] + K3, 15); + ROUND(H, a, b, c, d, in[1] + K3, 3); + ROUND(H, d, a, b, c, in[5] + K3, 9); + ROUND(H, c, d, a, b, in[0] + K3, 11); + ROUND(H, b, c, d, a, in[4] + K3, 15); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#undef ROUND +#undef F +#undef G +#undef H +#undef K1 +#undef K2 +#undef K3 + +/* The old legacy hash */ +static ext2_dirhash_t dx_hack_hash (const char *name, int len) +{ + __u32 hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9; + while (len--) { + __u32 hash = hash1 + (hash0 ^ (*name++ * 7152373)); + + if (hash & 0x80000000) hash -= 0x7fffffff; + hash1 = hash0; + hash0 = hash; + } + return (hash0 << 1); +} + +static void str2hashbuf(const char *msg, int len, __u32 *buf, int num) +{ + __u32 pad, val; + int i; + + pad = (__u32)len | ((__u32)len << 8); + pad |= pad << 16; + + val = pad; + if (len > num*4) + len = num * 4; + for (i=0; i < len; i++) { + if ((i % 4) == 0) + val = pad; + val = msg[i] + (val << 8); + if ((i % 4) == 3) { + *buf++ = val; + val = pad; + num--; + } + } + if (--num >= 0) + *buf++ = val; + while (--num >= 0) + *buf++ = pad; +} + +/* + * Returns the hash of a filename. If len is 0 and name is NULL, then + * this function can be used to test whether or not a hash version is + * supported. + * + * The seed is an 4 longword (32 bits) "secret" which can be used to + * uniquify a hash. If the seed is all zero's, then some default seed + * may be used. + * + * A particular hash version specifies whether or not the seed is + * represented, and whether or not the returned hash is 32 bits or 64 + * bits. 32 bit hashes will return 0 for the minor hash. + */ +errcode_t ext2fs_dirhash(int version, const char *name, int len, + const __u32 *seed, + ext2_dirhash_t *ret_hash, + ext2_dirhash_t *ret_minor_hash) +{ + __u32 hash; + __u32 minor_hash = 0; + const char *p; + int i; + __u32 in[8], buf[4]; + + /* Initialize the default seed for the hash checksum functions */ + buf[0] = 0x67452301; + buf[1] = 0xefcdab89; + buf[2] = 0x98badcfe; + buf[3] = 0x10325476; + + /* Check to see if the seed is all zero's */ + if (seed) { + for (i=0; i < 4; i++) { + if (seed[i]) + break; + } + if (i < 4) + memcpy(buf, seed, sizeof(buf)); + } + + switch (version) { + case EXT2_HASH_LEGACY: + hash = dx_hack_hash(name, len); + break; + case EXT2_HASH_HALF_MD4: + p = name; + while (len > 0) { + str2hashbuf(p, len, in, 8); + halfMD4Transform(buf, in); + len -= 32; + p += 32; + } + minor_hash = buf[2]; + hash = buf[1]; + break; + case EXT2_HASH_TEA: + p = name; + while (len > 0) { + str2hashbuf(p, len, in, 4); + TEA_transform(buf, in); + len -= 16; + p += 16; + } + hash = buf[0]; + minor_hash = buf[1]; + break; + default: + *ret_hash = 0; + return EXT2_ET_DIRHASH_UNSUPP; + } + *ret_hash = hash & ~1; + if (ret_minor_hash) + *ret_minor_hash = minor_hash; + return 0; +} diff --git a/e2fsprogs/ext2fs/dupfs.c b/e2fsprogs/ext2fs/dupfs.c new file mode 100644 index 000000000..203c29fe3 --- /dev/null +++ b/e2fsprogs/ext2fs/dupfs.c @@ -0,0 +1,97 @@ +/* vi: set sw=4 ts=4: */ +/* + * dupfs.c --- duplicate a ext2 filesystem handle + * + * Copyright (C) 1997, 1998, 2001, 2003, 2005 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fsP.h" + +errcode_t ext2fs_dup_handle(ext2_filsys src, ext2_filsys *dest) +{ + ext2_filsys fs; + errcode_t retval; + + EXT2_CHECK_MAGIC(src, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(sizeof(struct struct_ext2_filsys), &fs); + if (retval) + return retval; + + *fs = *src; + fs->device_name = 0; + fs->super = 0; + fs->orig_super = 0; + fs->group_desc = 0; + fs->inode_map = 0; + fs->block_map = 0; + fs->badblocks = 0; + fs->dblist = 0; + + io_channel_bumpcount(fs->io); + if (fs->icache) + fs->icache->refcount++; + + retval = ext2fs_get_mem(strlen(src->device_name)+1, &fs->device_name); + if (retval) + goto errout; + strcpy(fs->device_name, src->device_name); + + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &fs->super); + if (retval) + goto errout; + memcpy(fs->super, src->super, SUPERBLOCK_SIZE); + + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &fs->orig_super); + if (retval) + goto errout; + memcpy(fs->orig_super, src->orig_super, SUPERBLOCK_SIZE); + + retval = ext2fs_get_mem((size_t) fs->desc_blocks * fs->blocksize, + &fs->group_desc); + if (retval) + goto errout; + memcpy(fs->group_desc, src->group_desc, + (size_t) fs->desc_blocks * fs->blocksize); + + if (src->inode_map) { + retval = ext2fs_copy_bitmap(src->inode_map, &fs->inode_map); + if (retval) + goto errout; + } + if (src->block_map) { + retval = ext2fs_copy_bitmap(src->block_map, &fs->block_map); + if (retval) + goto errout; + } + if (src->badblocks) { + retval = ext2fs_badblocks_copy(src->badblocks, &fs->badblocks); + if (retval) + goto errout; + } + if (src->dblist) { + retval = ext2fs_copy_dblist(src->dblist, &fs->dblist); + if (retval) + goto errout; + } + *dest = fs; + return 0; +errout: + ext2fs_free(fs); + return retval; + +} + diff --git a/e2fsprogs/ext2fs/e2image.h b/e2fsprogs/ext2fs/e2image.h new file mode 100644 index 000000000..8d38ecc13 --- /dev/null +++ b/e2fsprogs/ext2fs/e2image.h @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: */ +/* + * e2image.h --- header file describing the ext2 image format + * + * Copyright (C) 2000 Theodore Ts'o. + * + * Note: this uses the POSIX IO interfaces, unlike most of the other + * functions in this library. So sue me. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + + +struct ext2_image_hdr { + __u32 magic_number; /* This must be EXT2_ET_MAGIC_E2IMAGE */ + char magic_descriptor[16]; /* "Ext2 Image 1.0", w/ null padding */ + char fs_hostname[64];/* Hostname of machine of image */ + char fs_netaddr[32]; /* Network address */ + __u32 fs_netaddr_type;/* 0 = IPV4, 1 = IPV6, etc. */ + __u32 fs_device; /* Device number of image */ + char fs_device_name[64]; /* Device name */ + char fs_uuid[16]; /* UUID of filesystem */ + __u32 fs_blocksize; /* Block size of the filesystem */ + __u32 fs_reserved[8]; + + __u32 image_device; /* Device number of image file */ + __u32 image_inode; /* Inode number of image file */ + __u32 image_time; /* Time of image creation */ + __u32 image_reserved[8]; + + __u32 offset_super; /* Byte offset of the sb and descriptors */ + __u32 offset_inode; /* Byte offset of the inode table */ + __u32 offset_inodemap; /* Byte offset of the inode bitmaps */ + __u32 offset_blockmap; /* Byte offset of the inode bitmaps */ + __u32 offset_reserved[8]; +}; + + + + + + + + + + + + + diff --git a/e2fsprogs/ext2fs/expanddir.c b/e2fsprogs/ext2fs/expanddir.c new file mode 100644 index 000000000..8a29ae584 --- /dev/null +++ b/e2fsprogs/ext2fs/expanddir.c @@ -0,0 +1,127 @@ +/* vi: set sw=4 ts=4: */ +/* + * expand.c --- expand an ext2fs directory + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct expand_dir_struct { + int done; + int newblocks; + errcode_t err; +}; + +static int expand_dir_proc(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct expand_dir_struct *es = (struct expand_dir_struct *) priv_data; + blk_t new_blk; + static blk_t last_blk = 0; + char *block; + errcode_t retval; + + if (*blocknr) { + last_blk = *blocknr; + return 0; + } + retval = ext2fs_new_block(fs, last_blk, 0, &new_blk); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + if (blockcnt > 0) { + retval = ext2fs_new_dir_block(fs, 0, 0, &block); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + es->done = 1; + retval = ext2fs_write_dir_block(fs, new_blk, block); + } else { + retval = ext2fs_get_mem(fs->blocksize, &block); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + memset(block, 0, fs->blocksize); + retval = io_channel_write_blk(fs->io, new_blk, 1, block); + } + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + ext2fs_free_mem(&block); + *blocknr = new_blk; + ext2fs_block_alloc_stats(fs, new_blk, +1); + es->newblocks++; + + if (es->done) + return (BLOCK_CHANGED | BLOCK_ABORT); + else + return BLOCK_CHANGED; +} + +errcode_t ext2fs_expand_dir(ext2_filsys fs, ext2_ino_t dir) +{ + errcode_t retval; + struct expand_dir_struct es; + struct ext2_inode inode; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + if (!fs->block_map) + return EXT2_ET_NO_BLOCK_BITMAP; + + retval = ext2fs_check_directory(fs, dir); + if (retval) + return retval; + + es.done = 0; + es.err = 0; + es.newblocks = 0; + + retval = ext2fs_block_iterate2(fs, dir, BLOCK_FLAG_APPEND, + 0, expand_dir_proc, &es); + + if (es.err) + return es.err; + if (!es.done) + return EXT2_ET_EXPAND_DIR_ERR; + + /* + * Update the size and block count fields in the inode. + */ + retval = ext2fs_read_inode(fs, dir, &inode); + if (retval) + return retval; + + inode.i_size += fs->blocksize; + inode.i_blocks += (fs->blocksize / 512) * es.newblocks; + + retval = ext2fs_write_inode(fs, dir, &inode); + if (retval) + return retval; + + return 0; +} diff --git a/e2fsprogs/ext2fs/ext2_err.h b/e2fsprogs/ext2fs/ext2_err.h new file mode 100644 index 000000000..ead352810 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2_err.h @@ -0,0 +1,116 @@ +/* vi: set sw=4 ts=4: */ +/* + * ext2_err.h: + * This file is automatically generated; please do not edit it. + */ + +#define EXT2_ET_BASE (2133571328L) +#define EXT2_ET_MAGIC_EXT2FS_FILSYS (2133571329L) +#define EXT2_ET_MAGIC_BADBLOCKS_LIST (2133571330L) +#define EXT2_ET_MAGIC_BADBLOCKS_ITERATE (2133571331L) +#define EXT2_ET_MAGIC_INODE_SCAN (2133571332L) +#define EXT2_ET_MAGIC_IO_CHANNEL (2133571333L) +#define EXT2_ET_MAGIC_UNIX_IO_CHANNEL (2133571334L) +#define EXT2_ET_MAGIC_IO_MANAGER (2133571335L) +#define EXT2_ET_MAGIC_BLOCK_BITMAP (2133571336L) +#define EXT2_ET_MAGIC_INODE_BITMAP (2133571337L) +#define EXT2_ET_MAGIC_GENERIC_BITMAP (2133571338L) +#define EXT2_ET_MAGIC_TEST_IO_CHANNEL (2133571339L) +#define EXT2_ET_MAGIC_DBLIST (2133571340L) +#define EXT2_ET_MAGIC_ICOUNT (2133571341L) +#define EXT2_ET_MAGIC_PQ_IO_CHANNEL (2133571342L) +#define EXT2_ET_MAGIC_EXT2_FILE (2133571343L) +#define EXT2_ET_MAGIC_E2IMAGE (2133571344L) +#define EXT2_ET_MAGIC_INODE_IO_CHANNEL (2133571345L) +#define EXT2_ET_MAGIC_RESERVED_9 (2133571346L) +#define EXT2_ET_BAD_MAGIC (2133571347L) +#define EXT2_ET_REV_TOO_HIGH (2133571348L) +#define EXT2_ET_RO_FILSYS (2133571349L) +#define EXT2_ET_GDESC_READ (2133571350L) +#define EXT2_ET_GDESC_WRITE (2133571351L) +#define EXT2_ET_GDESC_BAD_BLOCK_MAP (2133571352L) +#define EXT2_ET_GDESC_BAD_INODE_MAP (2133571353L) +#define EXT2_ET_GDESC_BAD_INODE_TABLE (2133571354L) +#define EXT2_ET_INODE_BITMAP_WRITE (2133571355L) +#define EXT2_ET_INODE_BITMAP_READ (2133571356L) +#define EXT2_ET_BLOCK_BITMAP_WRITE (2133571357L) +#define EXT2_ET_BLOCK_BITMAP_READ (2133571358L) +#define EXT2_ET_INODE_TABLE_WRITE (2133571359L) +#define EXT2_ET_INODE_TABLE_READ (2133571360L) +#define EXT2_ET_NEXT_INODE_READ (2133571361L) +#define EXT2_ET_UNEXPECTED_BLOCK_SIZE (2133571362L) +#define EXT2_ET_DIR_CORRUPTED (2133571363L) +#define EXT2_ET_SHORT_READ (2133571364L) +#define EXT2_ET_SHORT_WRITE (2133571365L) +#define EXT2_ET_DIR_NO_SPACE (2133571366L) +#define EXT2_ET_NO_INODE_BITMAP (2133571367L) +#define EXT2_ET_NO_BLOCK_BITMAP (2133571368L) +#define EXT2_ET_BAD_INODE_NUM (2133571369L) +#define EXT2_ET_BAD_BLOCK_NUM (2133571370L) +#define EXT2_ET_EXPAND_DIR_ERR (2133571371L) +#define EXT2_ET_TOOSMALL (2133571372L) +#define EXT2_ET_BAD_BLOCK_MARK (2133571373L) +#define EXT2_ET_BAD_BLOCK_UNMARK (2133571374L) +#define EXT2_ET_BAD_BLOCK_TEST (2133571375L) +#define EXT2_ET_BAD_INODE_MARK (2133571376L) +#define EXT2_ET_BAD_INODE_UNMARK (2133571377L) +#define EXT2_ET_BAD_INODE_TEST (2133571378L) +#define EXT2_ET_FUDGE_BLOCK_BITMAP_END (2133571379L) +#define EXT2_ET_FUDGE_INODE_BITMAP_END (2133571380L) +#define EXT2_ET_BAD_IND_BLOCK (2133571381L) +#define EXT2_ET_BAD_DIND_BLOCK (2133571382L) +#define EXT2_ET_BAD_TIND_BLOCK (2133571383L) +#define EXT2_ET_NEQ_BLOCK_BITMAP (2133571384L) +#define EXT2_ET_NEQ_INODE_BITMAP (2133571385L) +#define EXT2_ET_BAD_DEVICE_NAME (2133571386L) +#define EXT2_ET_MISSING_INODE_TABLE (2133571387L) +#define EXT2_ET_CORRUPT_SUPERBLOCK (2133571388L) +#define EXT2_ET_BAD_GENERIC_MARK (2133571389L) +#define EXT2_ET_BAD_GENERIC_UNMARK (2133571390L) +#define EXT2_ET_BAD_GENERIC_TEST (2133571391L) +#define EXT2_ET_SYMLINK_LOOP (2133571392L) +#define EXT2_ET_CALLBACK_NOTHANDLED (2133571393L) +#define EXT2_ET_BAD_BLOCK_IN_INODE_TABLE (2133571394L) +#define EXT2_ET_UNSUPP_FEATURE (2133571395L) +#define EXT2_ET_RO_UNSUPP_FEATURE (2133571396L) +#define EXT2_ET_LLSEEK_FAILED (2133571397L) +#define EXT2_ET_NO_MEMORY (2133571398L) +#define EXT2_ET_INVALID_ARGUMENT (2133571399L) +#define EXT2_ET_BLOCK_ALLOC_FAIL (2133571400L) +#define EXT2_ET_INODE_ALLOC_FAIL (2133571401L) +#define EXT2_ET_NO_DIRECTORY (2133571402L) +#define EXT2_ET_TOO_MANY_REFS (2133571403L) +#define EXT2_ET_FILE_NOT_FOUND (2133571404L) +#define EXT2_ET_FILE_RO (2133571405L) +#define EXT2_ET_DB_NOT_FOUND (2133571406L) +#define EXT2_ET_DIR_EXISTS (2133571407L) +#define EXT2_ET_UNIMPLEMENTED (2133571408L) +#define EXT2_ET_CANCEL_REQUESTED (2133571409L) +#define EXT2_ET_FILE_TOO_BIG (2133571410L) +#define EXT2_ET_JOURNAL_NOT_BLOCK (2133571411L) +#define EXT2_ET_NO_JOURNAL_SB (2133571412L) +#define EXT2_ET_JOURNAL_TOO_SMALL (2133571413L) +#define EXT2_ET_JOURNAL_UNSUPP_VERSION (2133571414L) +#define EXT2_ET_LOAD_EXT_JOURNAL (2133571415L) +#define EXT2_ET_NO_JOURNAL (2133571416L) +#define EXT2_ET_DIRHASH_UNSUPP (2133571417L) +#define EXT2_ET_BAD_EA_BLOCK_NUM (2133571418L) +#define EXT2_ET_TOO_MANY_INODES (2133571419L) +#define EXT2_ET_NOT_IMAGE_FILE (2133571420L) +#define EXT2_ET_RES_GDT_BLOCKS (2133571421L) +#define EXT2_ET_RESIZE_INODE_CORRUPT (2133571422L) +#define EXT2_ET_SET_BMAP_NO_IND (2133571423L) + +#if 0 +extern const struct error_table et_ext2_error_table; +extern void initialize_ext2_error_table(void); + +/* For compatibility with Heimdal */ +extern void initialize_ext2_error_table_r(struct et_list **list); + +#define ERROR_TABLE_BASE_ext2 (2133571328L) + +/* for compatibility with older versions... */ +#define init_ext2_err_tbl initialize_ext2_error_table +#define ext2_err_base ERROR_TABLE_BASE_ext2 +#endif diff --git a/e2fsprogs/ext2fs/ext2_ext_attr.h b/e2fsprogs/ext2fs/ext2_ext_attr.h new file mode 100644 index 000000000..cc91bb8d6 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2_ext_attr.h @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + File: linux/ext2_ext_attr.h + + On-disk format of extended attributes for the ext2 filesystem. + + (C) 2000 Andreas Gruenbacher, +*/ + +/* Magic value in attribute blocks */ +#define EXT2_EXT_ATTR_MAGIC_v1 0xEA010000 +#define EXT2_EXT_ATTR_MAGIC 0xEA020000 + +/* Maximum number of references to one attribute block */ +#define EXT2_EXT_ATTR_REFCOUNT_MAX 1024 + +struct ext2_ext_attr_header { + __u32 h_magic; /* magic number for identification */ + __u32 h_refcount; /* reference count */ + __u32 h_blocks; /* number of disk blocks used */ + __u32 h_hash; /* hash value of all attributes */ + __u32 h_reserved[4]; /* zero right now */ +}; + +struct ext2_ext_attr_entry { + __u8 e_name_len; /* length of name */ + __u8 e_name_index; /* attribute name index */ + __u16 e_value_offs; /* offset in disk block of value */ + __u32 e_value_block; /* disk block attribute is stored on (n/i) */ + __u32 e_value_size; /* size of attribute value */ + __u32 e_hash; /* hash value of name and value */ +}; + +#define EXT2_EXT_ATTR_PAD_BITS 2 +#define EXT2_EXT_ATTR_PAD (1<e_name_len)) ) +#define EXT2_EXT_ATTR_SIZE(size) \ + (((size) + EXT2_EXT_ATTR_ROUND) & ~EXT2_EXT_ATTR_ROUND) +#define EXT2_EXT_IS_LAST_ENTRY(entry) (*((__u32 *)(entry)) == 0UL) +#define EXT2_EXT_ATTR_NAME(entry) \ + (((char *) (entry)) + sizeof(struct ext2_ext_attr_entry)) +#define EXT2_XATTR_LEN(name_len) \ + (((name_len) + EXT2_EXT_ATTR_ROUND + \ + sizeof(struct ext2_xattr_entry)) & ~EXT2_EXT_ATTR_ROUND) +#define EXT2_XATTR_SIZE(size) \ + (((size) + EXT2_EXT_ATTR_ROUND) & ~EXT2_EXT_ATTR_ROUND) + diff --git a/e2fsprogs/ext2fs/ext2_fs.h b/e2fsprogs/ext2fs/ext2_fs.h new file mode 100644 index 000000000..cb49d7a60 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2_fs.h @@ -0,0 +1,570 @@ +/* vi: set sw=4 ts=4: */ +/* + * linux/include/linux/ext2_fs.h + * + * Copyright (C) 1992, 1993, 1994, 1995 + * Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * from + * + * linux/include/linux/minix_fs.h + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +#ifndef _LINUX_EXT2_FS_H +#define _LINUX_EXT2_FS_H + +#include "ext2_types.h" /* Changed from linux/types.h */ + +/* + * Special inode numbers + */ +#define EXT2_BAD_INO 1 /* Bad blocks inode */ +#define EXT2_ROOT_INO 2 /* Root inode */ +#define EXT2_ACL_IDX_INO 3 /* ACL inode */ +#define EXT2_ACL_DATA_INO 4 /* ACL inode */ +#define EXT2_BOOT_LOADER_INO 5 /* Boot loader inode */ +#define EXT2_UNDEL_DIR_INO 6 /* Undelete directory inode */ +#define EXT2_RESIZE_INO 7 /* Reserved group descriptors inode */ +#define EXT2_JOURNAL_INO 8 /* Journal inode */ + +/* First non-reserved inode for old ext2 filesystems */ +#define EXT2_GOOD_OLD_FIRST_INO 11 + +/* + * The second extended file system magic number + */ +#define EXT2_SUPER_MAGIC 0xEF53 + +/* Assume that user mode programs are passing in an ext2fs superblock, not + * a kernel struct super_block. This will allow us to call the feature-test + * macros from user land. */ +#define EXT2_SB(sb) (sb) + +/* + * Maximal count of links to a file + */ +#define EXT2_LINK_MAX 32000 + +/* + * Macro-instructions used to manage several block sizes + */ +#define EXT2_MIN_BLOCK_LOG_SIZE 10 /* 1024 */ +#define EXT2_MAX_BLOCK_LOG_SIZE 16 /* 65536 */ +#define EXT2_MIN_BLOCK_SIZE (1 << EXT2_MIN_BLOCK_LOG_SIZE) +#define EXT2_MAX_BLOCK_SIZE (1 << EXT2_MAX_BLOCK_LOG_SIZE) +#define EXT2_BLOCK_SIZE(s) (EXT2_MIN_BLOCK_SIZE << (s)->s_log_block_size) +#define EXT2_BLOCK_SIZE_BITS(s) ((s)->s_log_block_size + 10) +#define EXT2_INODE_SIZE(s) (((s)->s_rev_level == EXT2_GOOD_OLD_REV) ? \ + EXT2_GOOD_OLD_INODE_SIZE : (s)->s_inode_size) +#define EXT2_FIRST_INO(s) (((s)->s_rev_level == EXT2_GOOD_OLD_REV) ? \ + EXT2_GOOD_OLD_FIRST_INO : (s)->s_first_ino) +#define EXT2_ADDR_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s) / sizeof(__u32)) + +/* + * Macro-instructions used to manage fragments + */ +#define EXT2_MIN_FRAG_SIZE EXT2_MIN_BLOCK_SIZE +#define EXT2_MAX_FRAG_SIZE EXT2_MAX_BLOCK_SIZE +#define EXT2_MIN_FRAG_LOG_SIZE EXT2_MIN_BLOCK_LOG_SIZE +# define EXT2_FRAG_SIZE(s) (EXT2_MIN_FRAG_SIZE << (s)->s_log_frag_size) +# define EXT2_FRAGS_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s) / EXT2_FRAG_SIZE(s)) + +/* + * ACL structures + */ +struct ext2_acl_header /* Header of Access Control Lists */ +{ + __u32 aclh_size; + __u32 aclh_file_count; + __u32 aclh_acle_count; + __u32 aclh_first_acle; +}; + +struct ext2_acl_entry /* Access Control List Entry */ +{ + __u32 acle_size; + __u16 acle_perms; /* Access permissions */ + __u16 acle_type; /* Type of entry */ + __u16 acle_tag; /* User or group identity */ + __u16 acle_pad1; + __u32 acle_next; /* Pointer on next entry for the */ + /* same inode or on next free entry */ +}; + +/* + * Structure of a blocks group descriptor + */ +struct ext2_group_desc +{ + __u32 bg_block_bitmap; /* Blocks bitmap block */ + __u32 bg_inode_bitmap; /* Inodes bitmap block */ + __u32 bg_inode_table; /* Inodes table block */ + __u16 bg_free_blocks_count; /* Free blocks count */ + __u16 bg_free_inodes_count; /* Free inodes count */ + __u16 bg_used_dirs_count; /* Directories count */ + __u16 bg_pad; + __u32 bg_reserved[3]; +}; + +/* + * Data structures used by the directory indexing feature + * + * Note: all of the multibyte integer fields are little endian. + */ + +/* + * Note: dx_root_info is laid out so that if it should somehow get + * overlaid by a dirent the two low bits of the hash version will be + * zero. Therefore, the hash version mod 4 should never be 0. + * Sincerely, the paranoia department. + */ +struct ext2_dx_root_info { + __u32 reserved_zero; + __u8 hash_version; /* 0 now, 1 at release */ + __u8 info_length; /* 8 */ + __u8 indirect_levels; + __u8 unused_flags; +}; + +#define EXT2_HASH_LEGACY 0 +#define EXT2_HASH_HALF_MD4 1 +#define EXT2_HASH_TEA 2 + +#define EXT2_HASH_FLAG_INCOMPAT 0x1 + +struct ext2_dx_entry { + __u32 hash; + __u32 block; +}; + +struct ext2_dx_countlimit { + __u16 limit; + __u16 count; +}; + + +/* + * Macro-instructions used to manage group descriptors + */ +#define EXT2_BLOCKS_PER_GROUP(s) (EXT2_SB(s)->s_blocks_per_group) +#define EXT2_INODES_PER_GROUP(s) (EXT2_SB(s)->s_inodes_per_group) +#define EXT2_INODES_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s)/EXT2_INODE_SIZE(s)) +/* limits imposed by 16-bit value gd_free_{blocks,inode}_count */ +#define EXT2_MAX_BLOCKS_PER_GROUP(s) ((1 << 16) - 8) +#define EXT2_MAX_INODES_PER_GROUP(s) ((1 << 16) - EXT2_INODES_PER_BLOCK(s)) +#define EXT2_DESC_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s) / sizeof (struct ext2_group_desc)) + +/* + * Constants relative to the data blocks + */ +#define EXT2_NDIR_BLOCKS 12 +#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS +#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1) +#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1) +#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1) + +/* + * Inode flags + */ +#define EXT2_SECRM_FL 0x00000001 /* Secure deletion */ +#define EXT2_UNRM_FL 0x00000002 /* Undelete */ +#define EXT2_COMPR_FL 0x00000004 /* Compress file */ +#define EXT2_SYNC_FL 0x00000008 /* Synchronous updates */ +#define EXT2_IMMUTABLE_FL 0x00000010 /* Immutable file */ +#define EXT2_APPEND_FL 0x00000020 /* writes to file may only append */ +#define EXT2_NODUMP_FL 0x00000040 /* do not dump file */ +#define EXT2_NOATIME_FL 0x00000080 /* do not update atime */ +/* Reserved for compression usage... */ +#define EXT2_DIRTY_FL 0x00000100 +#define EXT2_COMPRBLK_FL 0x00000200 /* One or more compressed clusters */ +#define EXT2_NOCOMPR_FL 0x00000400 /* Access raw compressed data */ +#define EXT2_ECOMPR_FL 0x00000800 /* Compression error */ +/* End compression flags --- maybe not all used */ +#define EXT2_BTREE_FL 0x00001000 /* btree format dir */ +#define EXT2_INDEX_FL 0x00001000 /* hash-indexed directory */ +#define EXT2_IMAGIC_FL 0x00002000 +#define EXT3_JOURNAL_DATA_FL 0x00004000 /* file data should be journaled */ +#define EXT2_NOTAIL_FL 0x00008000 /* file tail should not be merged */ +#define EXT2_DIRSYNC_FL 0x00010000 /* Synchronous directory modifications */ +#define EXT2_TOPDIR_FL 0x00020000 /* Top of directory hierarchies*/ +#define EXT3_EXTENTS_FL 0x00080000 /* Inode uses extents */ +#define EXT2_RESERVED_FL 0x80000000 /* reserved for ext2 lib */ + +#define EXT2_FL_USER_VISIBLE 0x0003DFFF /* User visible flags */ +#define EXT2_FL_USER_MODIFIABLE 0x000080FF /* User modifiable flags */ + +/* + * ioctl commands + */ +#define EXT2_IOC_GETFLAGS _IOR('f', 1, long) +#define EXT2_IOC_SETFLAGS _IOW('f', 2, long) +#define EXT2_IOC_GETVERSION _IOR('v', 1, long) +#define EXT2_IOC_SETVERSION _IOW('v', 2, long) + +/* + * Structure of an inode on the disk + */ +struct ext2_inode { + __u16 i_mode; /* File mode */ + __u16 i_uid; /* Low 16 bits of Owner Uid */ + __u32 i_size; /* Size in bytes */ + __u32 i_atime; /* Access time */ + __u32 i_ctime; /* Creation time */ + __u32 i_mtime; /* Modification time */ + __u32 i_dtime; /* Deletion Time */ + __u16 i_gid; /* Low 16 bits of Group Id */ + __u16 i_links_count; /* Links count */ + __u32 i_blocks; /* Blocks count */ + __u32 i_flags; /* File flags */ + union { + struct { + __u32 l_i_reserved1; + } linux1; + struct { + __u32 h_i_translator; + } hurd1; + struct { + __u32 m_i_reserved1; + } masix1; + } osd1; /* OS dependent 1 */ + __u32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ + __u32 i_generation; /* File version (for NFS) */ + __u32 i_file_acl; /* File ACL */ + __u32 i_dir_acl; /* Directory ACL */ + __u32 i_faddr; /* Fragment address */ + union { + struct { + __u8 l_i_frag; /* Fragment number */ + __u8 l_i_fsize; /* Fragment size */ + __u16 i_pad1; + __u16 l_i_uid_high; /* these 2 fields */ + __u16 l_i_gid_high; /* were reserved2[0] */ + __u32 l_i_reserved2; + } linux2; + struct { + __u8 h_i_frag; /* Fragment number */ + __u8 h_i_fsize; /* Fragment size */ + __u16 h_i_mode_high; + __u16 h_i_uid_high; + __u16 h_i_gid_high; + __u32 h_i_author; + } hurd2; + struct { + __u8 m_i_frag; /* Fragment number */ + __u8 m_i_fsize; /* Fragment size */ + __u16 m_pad1; + __u32 m_i_reserved2[2]; + } masix2; + } osd2; /* OS dependent 2 */ +}; + +/* + * Permanent part of an large inode on the disk + */ +struct ext2_inode_large { + __u16 i_mode; /* File mode */ + __u16 i_uid; /* Low 16 bits of Owner Uid */ + __u32 i_size; /* Size in bytes */ + __u32 i_atime; /* Access time */ + __u32 i_ctime; /* Creation time */ + __u32 i_mtime; /* Modification time */ + __u32 i_dtime; /* Deletion Time */ + __u16 i_gid; /* Low 16 bits of Group Id */ + __u16 i_links_count; /* Links count */ + __u32 i_blocks; /* Blocks count */ + __u32 i_flags; /* File flags */ + union { + struct { + __u32 l_i_reserved1; + } linux1; + struct { + __u32 h_i_translator; + } hurd1; + struct { + __u32 m_i_reserved1; + } masix1; + } osd1; /* OS dependent 1 */ + __u32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ + __u32 i_generation; /* File version (for NFS) */ + __u32 i_file_acl; /* File ACL */ + __u32 i_dir_acl; /* Directory ACL */ + __u32 i_faddr; /* Fragment address */ + union { + struct { + __u8 l_i_frag; /* Fragment number */ + __u8 l_i_fsize; /* Fragment size */ + __u16 i_pad1; + __u16 l_i_uid_high; /* these 2 fields */ + __u16 l_i_gid_high; /* were reserved2[0] */ + __u32 l_i_reserved2; + } linux2; + struct { + __u8 h_i_frag; /* Fragment number */ + __u8 h_i_fsize; /* Fragment size */ + __u16 h_i_mode_high; + __u16 h_i_uid_high; + __u16 h_i_gid_high; + __u32 h_i_author; + } hurd2; + struct { + __u8 m_i_frag; /* Fragment number */ + __u8 m_i_fsize; /* Fragment size */ + __u16 m_pad1; + __u32 m_i_reserved2[2]; + } masix2; + } osd2; /* OS dependent 2 */ + __u16 i_extra_isize; + __u16 i_pad1; +}; + +#define i_size_high i_dir_acl + +/* + * File system states + */ +#define EXT2_VALID_FS 0x0001 /* Unmounted cleanly */ +#define EXT2_ERROR_FS 0x0002 /* Errors detected */ + +/* + * Mount flags + */ +#define EXT2_MOUNT_CHECK 0x0001 /* Do mount-time checks */ +#define EXT2_MOUNT_GRPID 0x0004 /* Create files with directory's group */ +#define EXT2_MOUNT_DEBUG 0x0008 /* Some debugging messages */ +#define EXT2_MOUNT_ERRORS_CONT 0x0010 /* Continue on errors */ +#define EXT2_MOUNT_ERRORS_RO 0x0020 /* Remount fs ro on errors */ +#define EXT2_MOUNT_ERRORS_PANIC 0x0040 /* Panic on errors */ +#define EXT2_MOUNT_MINIX_DF 0x0080 /* Mimics the Minix statfs */ +#define EXT2_MOUNT_NO_UID32 0x0200 /* Disable 32-bit UIDs */ + +#define clear_opt(o, opt) o &= ~EXT2_MOUNT_##opt +#define set_opt(o, opt) o |= EXT2_MOUNT_##opt +#define test_opt(sb, opt) (EXT2_SB(sb)->s_mount_opt & \ + EXT2_MOUNT_##opt) +/* + * Maximal mount counts between two filesystem checks + */ +#define EXT2_DFL_MAX_MNT_COUNT 20 /* Allow 20 mounts */ +#define EXT2_DFL_CHECKINTERVAL 0 /* Don't use interval check */ + +/* + * Behaviour when detecting errors + */ +#define EXT2_ERRORS_CONTINUE 1 /* Continue execution */ +#define EXT2_ERRORS_RO 2 /* Remount fs read-only */ +#define EXT2_ERRORS_PANIC 3 /* Panic */ +#define EXT2_ERRORS_DEFAULT EXT2_ERRORS_CONTINUE + +/* + * Structure of the super block + */ +struct ext2_super_block { + __u32 s_inodes_count; /* Inodes count */ + __u32 s_blocks_count; /* Blocks count */ + __u32 s_r_blocks_count; /* Reserved blocks count */ + __u32 s_free_blocks_count; /* Free blocks count */ + __u32 s_free_inodes_count; /* Free inodes count */ + __u32 s_first_data_block; /* First Data Block */ + __u32 s_log_block_size; /* Block size */ + __s32 s_log_frag_size; /* Fragment size */ + __u32 s_blocks_per_group; /* # Blocks per group */ + __u32 s_frags_per_group; /* # Fragments per group */ + __u32 s_inodes_per_group; /* # Inodes per group */ + __u32 s_mtime; /* Mount time */ + __u32 s_wtime; /* Write time */ + __u16 s_mnt_count; /* Mount count */ + __s16 s_max_mnt_count; /* Maximal mount count */ + __u16 s_magic; /* Magic signature */ + __u16 s_state; /* File system state */ + __u16 s_errors; /* Behaviour when detecting errors */ + __u16 s_minor_rev_level; /* minor revision level */ + __u32 s_lastcheck; /* time of last check */ + __u32 s_checkinterval; /* max. time between checks */ + __u32 s_creator_os; /* OS */ + __u32 s_rev_level; /* Revision level */ + __u16 s_def_resuid; /* Default uid for reserved blocks */ + __u16 s_def_resgid; /* Default gid for reserved blocks */ + /* + * These fields are for EXT2_DYNAMIC_REV superblocks only. + * + * Note: the difference between the compatible feature set and + * the incompatible feature set is that if there is a bit set + * in the incompatible feature set that the kernel doesn't + * know about, it should refuse to mount the filesystem. + * + * e2fsck's requirements are more strict; if it doesn't know + * about a feature in either the compatible or incompatible + * feature set, it must abort and not try to meddle with + * things it doesn't understand... + */ + __u32 s_first_ino; /* First non-reserved inode */ + __u16 s_inode_size; /* size of inode structure */ + __u16 s_block_group_nr; /* block group # of this superblock */ + __u32 s_feature_compat; /* compatible feature set */ + __u32 s_feature_incompat; /* incompatible feature set */ + __u32 s_feature_ro_compat; /* readonly-compatible feature set */ + __u8 s_uuid[16]; /* 128-bit uuid for volume */ + char s_volume_name[16]; /* volume name */ + char s_last_mounted[64]; /* directory where last mounted */ + __u32 s_algorithm_usage_bitmap; /* For compression */ + /* + * Performance hints. Directory preallocation should only + * happen if the EXT2_FEATURE_COMPAT_DIR_PREALLOC flag is on. + */ + __u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/ + __u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */ + __u16 s_reserved_gdt_blocks; /* Per group table for online growth */ + /* + * Journaling support valid if EXT2_FEATURE_COMPAT_HAS_JOURNAL set. + */ + __u8 s_journal_uuid[16]; /* uuid of journal superblock */ + __u32 s_journal_inum; /* inode number of journal file */ + __u32 s_journal_dev; /* device number of journal file */ + __u32 s_last_orphan; /* start of list of inodes to delete */ + __u32 s_hash_seed[4]; /* HTREE hash seed */ + __u8 s_def_hash_version; /* Default hash version to use */ + __u8 s_jnl_backup_type; /* Default type of journal backup */ + __u16 s_reserved_word_pad; + __u32 s_default_mount_opts; + __u32 s_first_meta_bg; /* First metablock group */ + __u32 s_mkfs_time; /* When the filesystem was created */ + __u32 s_jnl_blocks[17]; /* Backup of the journal inode */ + __u32 s_reserved[172]; /* Padding to the end of the block */ +}; + +/* + * Codes for operating systems + */ +#define EXT2_OS_LINUX 0 +#define EXT2_OS_HURD 1 +#define EXT2_OS_MASIX 2 +#define EXT2_OS_FREEBSD 3 +#define EXT2_OS_LITES 4 + +/* + * Revision levels + */ +#define EXT2_GOOD_OLD_REV 0 /* The good old (original) format */ +#define EXT2_DYNAMIC_REV 1 /* V2 format w/ dynamic inode sizes */ + +#define EXT2_CURRENT_REV EXT2_GOOD_OLD_REV +#define EXT2_MAX_SUPP_REV EXT2_DYNAMIC_REV + +#define EXT2_GOOD_OLD_INODE_SIZE 128 + +/* + * Journal inode backup types + */ +#define EXT3_JNL_BACKUP_BLOCKS 1 + +/* + * Feature set definitions + */ + +#define EXT2_HAS_COMPAT_FEATURE(sb,mask) \ + ( EXT2_SB(sb)->s_feature_compat & (mask) ) +#define EXT2_HAS_RO_COMPAT_FEATURE(sb,mask) \ + ( EXT2_SB(sb)->s_feature_ro_compat & (mask) ) +#define EXT2_HAS_INCOMPAT_FEATURE(sb,mask) \ + ( EXT2_SB(sb)->s_feature_incompat & (mask) ) + +#define EXT2_FEATURE_COMPAT_DIR_PREALLOC 0x0001 +#define EXT2_FEATURE_COMPAT_IMAGIC_INODES 0x0002 +#define EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x0004 +#define EXT2_FEATURE_COMPAT_EXT_ATTR 0x0008 +#define EXT2_FEATURE_COMPAT_RESIZE_INODE 0x0010 +#define EXT2_FEATURE_COMPAT_DIR_INDEX 0x0020 + +#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 +#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 +/* #define EXT2_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 not used */ + +#define EXT2_FEATURE_INCOMPAT_COMPRESSION 0x0001 +#define EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002 +#define EXT3_FEATURE_INCOMPAT_RECOVER 0x0004 /* Needs recovery */ +#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008 /* Journal device */ +#define EXT2_FEATURE_INCOMPAT_META_BG 0x0010 +#define EXT3_FEATURE_INCOMPAT_EXTENTS 0x0040 + + +#define EXT2_FEATURE_COMPAT_SUPP 0 +#define EXT2_FEATURE_INCOMPAT_SUPP (EXT2_FEATURE_INCOMPAT_FILETYPE) +#define EXT2_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \ + EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \ + EXT2_FEATURE_RO_COMPAT_BTREE_DIR) + +/* + * Default values for user and/or group using reserved blocks + */ +#define EXT2_DEF_RESUID 0 +#define EXT2_DEF_RESGID 0 + +/* + * Default mount options + */ +#define EXT2_DEFM_DEBUG 0x0001 +#define EXT2_DEFM_BSDGROUPS 0x0002 +#define EXT2_DEFM_XATTR_USER 0x0004 +#define EXT2_DEFM_ACL 0x0008 +#define EXT2_DEFM_UID16 0x0010 +#define EXT3_DEFM_JMODE 0x0060 +#define EXT3_DEFM_JMODE_DATA 0x0020 +#define EXT3_DEFM_JMODE_ORDERED 0x0040 +#define EXT3_DEFM_JMODE_WBACK 0x0060 + +/* + * Structure of a directory entry + */ +#define EXT2_NAME_LEN 255 + +struct ext2_dir_entry { + __u32 inode; /* Inode number */ + __u16 rec_len; /* Directory entry length */ + __u16 name_len; /* Name length */ + char name[EXT2_NAME_LEN]; /* File name */ +}; + +/* + * The new version of the directory entry. Since EXT2 structures are + * stored in intel byte order, and the name_len field could never be + * bigger than 255 chars, it's safe to reclaim the extra byte for the + * file_type field. + */ +struct ext2_dir_entry_2 { + __u32 inode; /* Inode number */ + __u16 rec_len; /* Directory entry length */ + __u8 name_len; /* Name length */ + __u8 file_type; + char name[EXT2_NAME_LEN]; /* File name */ +}; + +/* + * Ext2 directory file types. Only the low 3 bits are used. The + * other bits are reserved for now. + */ +#define EXT2_FT_UNKNOWN 0 +#define EXT2_FT_REG_FILE 1 +#define EXT2_FT_DIR 2 +#define EXT2_FT_CHRDEV 3 +#define EXT2_FT_BLKDEV 4 +#define EXT2_FT_FIFO 5 +#define EXT2_FT_SOCK 6 +#define EXT2_FT_SYMLINK 7 + +#define EXT2_FT_MAX 8 + +/* + * EXT2_DIR_PAD defines the directory entries boundaries + * + * NOTE: It must be a multiple of 4 + */ +#define EXT2_DIR_PAD 4 +#define EXT2_DIR_ROUND (EXT2_DIR_PAD - 1) +#define EXT2_DIR_REC_LEN(name_len) (((name_len) + 8 + EXT2_DIR_ROUND) & \ + ~EXT2_DIR_ROUND) + +#endif /* _LINUX_EXT2_FS_H */ diff --git a/e2fsprogs/ext2fs/ext2_io.h b/e2fsprogs/ext2fs/ext2_io.h new file mode 100644 index 000000000..e6c9630e2 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2_io.h @@ -0,0 +1,114 @@ +/* vi: set sw=4 ts=4: */ +/* + * io.h --- the I/O manager abstraction + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#ifndef _EXT2FS_EXT2_IO_H +#define _EXT2FS_EXT2_IO_H + +/* + * ext2_loff_t is defined here since unix_io.c needs it. + */ +#if defined(__GNUC__) || defined(HAS_LONG_LONG) +typedef long long ext2_loff_t; +#else +typedef long ext2_loff_t; +#endif + +/* llseek.c */ +/* ext2_loff_t ext2fs_llseek (int, ext2_loff_t, int); */ +#ifdef CONFIG_LFS +# define ext2fs_llseek lseek64 +#else +# define ext2fs_llseek lseek +#endif + +typedef struct struct_io_manager *io_manager; +typedef struct struct_io_channel *io_channel; + +#define CHANNEL_FLAGS_WRITETHROUGH 0x01 + +struct struct_io_channel { + errcode_t magic; + io_manager manager; + char *name; + int block_size; + errcode_t (*read_error)(io_channel channel, + unsigned long block, + int count, + void *data, + size_t size, + int actual_bytes_read, + errcode_t error); + errcode_t (*write_error)(io_channel channel, + unsigned long block, + int count, + const void *data, + size_t size, + int actual_bytes_written, + errcode_t error); + int refcount; + int flags; + int reserved[14]; + void *private_data; + void *app_data; +}; + +struct struct_io_manager { + errcode_t magic; + const char *name; + errcode_t (*open)(const char *name, int flags, io_channel *channel); + errcode_t (*close)(io_channel channel); + errcode_t (*set_blksize)(io_channel channel, int blksize); + errcode_t (*read_blk)(io_channel channel, unsigned long block, + int count, void *data); + errcode_t (*write_blk)(io_channel channel, unsigned long block, + int count, const void *data); + errcode_t (*flush)(io_channel channel); + errcode_t (*write_byte)(io_channel channel, unsigned long offset, + int count, const void *data); + errcode_t (*set_option)(io_channel channel, const char *option, + const char *arg); + int reserved[14]; +}; + +#define IO_FLAG_RW 1 + +/* + * Convenience functions.... + */ +#define io_channel_close(c) ((c)->manager->close((c))) +#define io_channel_set_blksize(c,s) ((c)->manager->set_blksize((c),s)) +#define io_channel_read_blk(c,b,n,d) ((c)->manager->read_blk((c),b,n,d)) +#define io_channel_write_blk(c,b,n,d) ((c)->manager->write_blk((c),b,n,d)) +#define io_channel_flush(c) ((c)->manager->flush((c))) +#define io_channel_bumpcount(c) ((c)->refcount++) + +/* io_manager.c */ +extern errcode_t io_channel_set_options(io_channel channel, + const char *options); +extern errcode_t io_channel_write_byte(io_channel channel, + unsigned long offset, + int count, const void *data); + +/* unix_io.c */ +extern io_manager unix_io_manager; + +/* test_io.c */ +extern io_manager test_io_manager, test_io_backing_manager; +extern void (*test_io_cb_read_blk) + (unsigned long block, int count, errcode_t err); +extern void (*test_io_cb_write_blk) + (unsigned long block, int count, errcode_t err); +extern void (*test_io_cb_set_blksize) + (int blksize, errcode_t err); + +#endif /* _EXT2FS_EXT2_IO_H */ + diff --git a/e2fsprogs/ext2fs/ext2_types.h b/e2fsprogs/ext2fs/ext2_types.h new file mode 100644 index 000000000..2c1196b8b --- /dev/null +++ b/e2fsprogs/ext2fs/ext2_types.h @@ -0,0 +1,2 @@ +/* vi: set sw=4 ts=4: */ +#include diff --git a/e2fsprogs/ext2fs/ext2fs.h b/e2fsprogs/ext2fs/ext2fs.h new file mode 100644 index 000000000..133fb1f1b --- /dev/null +++ b/e2fsprogs/ext2fs/ext2fs.h @@ -0,0 +1,923 @@ +/* vi: set sw=4 ts=4: */ +/* + * ext2fs.h --- ext2fs + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#ifndef _EXT2FS_EXT2FS_H +#define _EXT2FS_EXT2FS_H + + +#define EXT2FS_ATTR(x) + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Where the master copy of the superblock is located, and how big + * superblocks are supposed to be. We define SUPERBLOCK_SIZE because + * the size of the superblock structure is not necessarily trustworthy + * (some versions have the padding set up so that the superblock is + * 1032 bytes long). + */ +#define SUPERBLOCK_OFFSET 1024 +#define SUPERBLOCK_SIZE 1024 + +/* + * The last ext2fs revision level that this version of the library is + * able to support. + */ +#define EXT2_LIB_CURRENT_REV EXT2_DYNAMIC_REV + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#include +#include + +#include "ext2_types.h" +#include "ext2_fs.h" + +typedef __u32 ext2_ino_t; +typedef __u32 blk_t; +typedef __u32 dgrp_t; +typedef __u32 ext2_off_t; +typedef __s64 e2_blkcnt_t; +typedef __u32 ext2_dirhash_t; + +#include "ext2_io.h" +#include "ext2_err.h" + +typedef struct struct_ext2_filsys *ext2_filsys; + +struct ext2fs_struct_generic_bitmap { + errcode_t magic; + ext2_filsys fs; + __u32 start, end; + __u32 real_end; + char * description; + char * bitmap; + errcode_t base_error_code; + __u32 reserved[7]; +}; + +#define EXT2FS_MARK_ERROR 0 +#define EXT2FS_UNMARK_ERROR 1 +#define EXT2FS_TEST_ERROR 2 + +typedef struct ext2fs_struct_generic_bitmap *ext2fs_generic_bitmap; +typedef struct ext2fs_struct_generic_bitmap *ext2fs_inode_bitmap; +typedef struct ext2fs_struct_generic_bitmap *ext2fs_block_bitmap; + +#define EXT2_FIRST_INODE(s) EXT2_FIRST_INO(s) + +/* + * badblocks list definitions + */ + +typedef struct ext2_struct_u32_list *ext2_badblocks_list; +typedef struct ext2_struct_u32_iterate *ext2_badblocks_iterate; + +typedef struct ext2_struct_u32_list *ext2_u32_list; +typedef struct ext2_struct_u32_iterate *ext2_u32_iterate; + +/* old */ +typedef struct ext2_struct_u32_list *badblocks_list; +typedef struct ext2_struct_u32_iterate *badblocks_iterate; + +#define BADBLOCKS_FLAG_DIRTY 1 + +/* + * ext2_dblist structure and abstractions (see dblist.c) + */ +struct ext2_db_entry { + ext2_ino_t ino; + blk_t blk; + int blockcnt; +}; + +typedef struct ext2_struct_dblist *ext2_dblist; + +#define DBLIST_ABORT 1 + +/* + * ext2_fileio definitions + */ + +#define EXT2_FILE_WRITE 0x0001 +#define EXT2_FILE_CREATE 0x0002 + +#define EXT2_FILE_MASK 0x00FF + +#define EXT2_FILE_BUF_DIRTY 0x4000 +#define EXT2_FILE_BUF_VALID 0x2000 + +typedef struct ext2_file *ext2_file_t; + +#define EXT2_SEEK_SET 0 +#define EXT2_SEEK_CUR 1 +#define EXT2_SEEK_END 2 + +/* + * Flags for the ext2_filsys structure and for ext2fs_open() + */ +#define EXT2_FLAG_RW 0x01 +#define EXT2_FLAG_CHANGED 0x02 +#define EXT2_FLAG_DIRTY 0x04 +#define EXT2_FLAG_VALID 0x08 +#define EXT2_FLAG_IB_DIRTY 0x10 +#define EXT2_FLAG_BB_DIRTY 0x20 +#define EXT2_FLAG_SWAP_BYTES 0x40 +#define EXT2_FLAG_SWAP_BYTES_READ 0x80 +#define EXT2_FLAG_SWAP_BYTES_WRITE 0x100 +#define EXT2_FLAG_MASTER_SB_ONLY 0x200 +#define EXT2_FLAG_FORCE 0x400 +#define EXT2_FLAG_SUPER_ONLY 0x800 +#define EXT2_FLAG_JOURNAL_DEV_OK 0x1000 +#define EXT2_FLAG_IMAGE_FILE 0x2000 + +/* + * Special flag in the ext2 inode i_flag field that means that this is + * a new inode. (So that ext2_write_inode() can clear extra fields.) + */ +#define EXT2_NEW_INODE_FL 0x80000000 + +/* + * Flags for mkjournal + * + * EXT2_MKJOURNAL_V1_SUPER Make a (deprecated) V1 journal superblock + */ +#define EXT2_MKJOURNAL_V1_SUPER 0x0000001 + +struct struct_ext2_filsys { + errcode_t magic; + io_channel io; + int flags; + char * device_name; + struct ext2_super_block * super; + unsigned int blocksize; + int fragsize; + dgrp_t group_desc_count; + unsigned long desc_blocks; + struct ext2_group_desc * group_desc; + int inode_blocks_per_group; + ext2fs_inode_bitmap inode_map; + ext2fs_block_bitmap block_map; + errcode_t (*get_blocks)(ext2_filsys fs, ext2_ino_t ino, blk_t *blocks); + errcode_t (*check_directory)(ext2_filsys fs, ext2_ino_t ino); + errcode_t (*write_bitmaps)(ext2_filsys fs); + errcode_t (*read_inode)(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode); + errcode_t (*write_inode)(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode); + ext2_badblocks_list badblocks; + ext2_dblist dblist; + __u32 stride; /* for mke2fs */ + struct ext2_super_block * orig_super; + struct ext2_image_hdr * image_header; + __u32 umask; + /* + * Reserved for future expansion + */ + __u32 reserved[8]; + + /* + * Reserved for the use of the calling application. + */ + void * priv_data; + + /* + * Inode cache + */ + struct ext2_inode_cache *icache; + io_channel image_io; +}; + +#include "bitops.h" + +/* + * Return flags for the block iterator functions + */ +#define BLOCK_CHANGED 1 +#define BLOCK_ABORT 2 +#define BLOCK_ERROR 4 + +/* + * Block interate flags + * + * BLOCK_FLAG_APPEND, or BLOCK_FLAG_HOLE, indicates that the interator + * function should be called on blocks where the block number is zero. + * This is used by ext2fs_expand_dir() to be able to add a new block + * to an inode. It can also be used for programs that want to be able + * to deal with files that contain "holes". + * + * BLOCK_FLAG_TRAVERSE indicates that the iterator function for the + * indirect, doubly indirect, etc. blocks should be called after all + * of the blocks containined in the indirect blocks are processed. + * This is useful if you are going to be deallocating blocks from an + * inode. + * + * BLOCK_FLAG_DATA_ONLY indicates that the iterator function should be + * called for data blocks only. + * + * BLOCK_FLAG_NO_LARGE is for internal use only. It informs + * ext2fs_block_iterate2 that large files won't be accepted. + */ +#define BLOCK_FLAG_APPEND 1 +#define BLOCK_FLAG_HOLE 1 +#define BLOCK_FLAG_DEPTH_TRAVERSE 2 +#define BLOCK_FLAG_DATA_ONLY 4 + +#define BLOCK_FLAG_NO_LARGE 0x1000 + +/* + * Magic "block count" return values for the block iterator function. + */ +#define BLOCK_COUNT_IND (-1) +#define BLOCK_COUNT_DIND (-2) +#define BLOCK_COUNT_TIND (-3) +#define BLOCK_COUNT_TRANSLATOR (-4) + +#if 0 +/* + * Flags for ext2fs_move_blocks + */ +#define EXT2_BMOVE_GET_DBLIST 0x0001 +#define EXT2_BMOVE_DEBUG 0x0002 +#endif + +/* + * Flags for directory block reading and writing functions + */ +#define EXT2_DIRBLOCK_V2_STRUCT 0x0001 + +/* + * Return flags for the directory iterator functions + */ +#define DIRENT_CHANGED 1 +#define DIRENT_ABORT 2 +#define DIRENT_ERROR 3 + +/* + * Directory iterator flags + */ + +#define DIRENT_FLAG_INCLUDE_EMPTY 1 +#define DIRENT_FLAG_INCLUDE_REMOVED 2 + +#define DIRENT_DOT_FILE 1 +#define DIRENT_DOT_DOT_FILE 2 +#define DIRENT_OTHER_FILE 3 +#define DIRENT_DELETED_FILE 4 + +/* + * Inode scan definitions + */ +typedef struct ext2_struct_inode_scan *ext2_inode_scan; + +/* + * ext2fs_scan flags + */ +#define EXT2_SF_CHK_BADBLOCKS 0x0001 +#define EXT2_SF_BAD_INODE_BLK 0x0002 +#define EXT2_SF_BAD_EXTRA_BYTES 0x0004 +#define EXT2_SF_SKIP_MISSING_ITABLE 0x0008 + +/* + * ext2fs_check_if_mounted flags + */ +#define EXT2_MF_MOUNTED 1 +#define EXT2_MF_ISROOT 2 +#define EXT2_MF_READONLY 4 +#define EXT2_MF_SWAP 8 +#define EXT2_MF_BUSY 16 + +/* + * Ext2/linux mode flags. We define them here so that we don't need + * to depend on the OS's sys/stat.h, since we may be compiling on a + * non-Linux system. + */ +#define LINUX_S_IFMT 00170000 +#define LINUX_S_IFSOCK 0140000 +#define LINUX_S_IFLNK 0120000 +#define LINUX_S_IFREG 0100000 +#define LINUX_S_IFBLK 0060000 +#define LINUX_S_IFDIR 0040000 +#define LINUX_S_IFCHR 0020000 +#define LINUX_S_IFIFO 0010000 +#define LINUX_S_ISUID 0004000 +#define LINUX_S_ISGID 0002000 +#define LINUX_S_ISVTX 0001000 + +#define LINUX_S_IRWXU 00700 +#define LINUX_S_IRUSR 00400 +#define LINUX_S_IWUSR 00200 +#define LINUX_S_IXUSR 00100 + +#define LINUX_S_IRWXG 00070 +#define LINUX_S_IRGRP 00040 +#define LINUX_S_IWGRP 00020 +#define LINUX_S_IXGRP 00010 + +#define LINUX_S_IRWXO 00007 +#define LINUX_S_IROTH 00004 +#define LINUX_S_IWOTH 00002 +#define LINUX_S_IXOTH 00001 + +#define LINUX_S_ISLNK(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFLNK) +#define LINUX_S_ISREG(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFREG) +#define LINUX_S_ISDIR(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFDIR) +#define LINUX_S_ISCHR(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFCHR) +#define LINUX_S_ISBLK(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFBLK) +#define LINUX_S_ISFIFO(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFIFO) +#define LINUX_S_ISSOCK(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFSOCK) + +/* + * ext2 size of an inode + */ +#define EXT2_I_SIZE(i) ((i)->i_size | ((__u64) (i)->i_size_high << 32)) + +/* + * ext2_icount_t abstraction + */ +#define EXT2_ICOUNT_OPT_INCREMENT 0x01 + +typedef struct ext2_icount *ext2_icount_t; + +/* + * Flags for ext2fs_bmap + */ +#define BMAP_ALLOC 0x0001 +#define BMAP_SET 0x0002 + +/* + * Flags for imager.c functions + */ +#define IMAGER_FLAG_INODEMAP 1 +#define IMAGER_FLAG_SPARSEWRITE 2 + +/* + * For checking structure magic numbers... + */ + +#define EXT2_CHECK_MAGIC(struct, code) \ + if ((struct)->magic != (code)) return (code) + + +/* + * For ext2 compression support + */ +#define EXT2FS_COMPRESSED_BLKADDR ((blk_t) 0xffffffff) +#define HOLE_BLKADDR(_b) ((_b) == 0 || (_b) == EXT2FS_COMPRESSED_BLKADDR) + +/* + * Features supported by this version of the library + */ +#define EXT2_LIB_FEATURE_COMPAT_SUPP (EXT2_FEATURE_COMPAT_DIR_PREALLOC|\ + EXT2_FEATURE_COMPAT_IMAGIC_INODES|\ + EXT3_FEATURE_COMPAT_HAS_JOURNAL|\ + EXT2_FEATURE_COMPAT_RESIZE_INODE|\ + EXT2_FEATURE_COMPAT_DIR_INDEX|\ + EXT2_FEATURE_COMPAT_EXT_ATTR) + +/* This #ifdef is temporary until compression is fully supported */ +#ifdef ENABLE_COMPRESSION +#ifndef I_KNOW_THAT_COMPRESSION_IS_EXPERIMENTAL +/* If the below warning bugs you, then have + `CPPFLAGS=-DI_KNOW_THAT_COMPRESSION_IS_EXPERIMENTAL' in your + environment at configure time. */ + #warning "Compression support is experimental" +#endif +#define EXT2_LIB_FEATURE_INCOMPAT_SUPP (EXT2_FEATURE_INCOMPAT_FILETYPE|\ + EXT2_FEATURE_INCOMPAT_COMPRESSION|\ + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV|\ + EXT2_FEATURE_INCOMPAT_META_BG|\ + EXT3_FEATURE_INCOMPAT_RECOVER) +#else +#define EXT2_LIB_FEATURE_INCOMPAT_SUPP (EXT2_FEATURE_INCOMPAT_FILETYPE|\ + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV|\ + EXT2_FEATURE_INCOMPAT_META_BG|\ + EXT3_FEATURE_INCOMPAT_RECOVER) +#endif +#define EXT2_LIB_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER|\ + EXT2_FEATURE_RO_COMPAT_LARGE_FILE) +/* + * function prototypes + */ + +/* alloc.c */ +extern errcode_t ext2fs_new_inode(ext2_filsys fs, ext2_ino_t dir, int mode, + ext2fs_inode_bitmap map, ext2_ino_t *ret); +extern errcode_t ext2fs_new_block(ext2_filsys fs, blk_t goal, + ext2fs_block_bitmap map, blk_t *ret); +extern errcode_t ext2fs_get_free_blocks(ext2_filsys fs, blk_t start, + blk_t finish, int num, + ext2fs_block_bitmap map, + blk_t *ret); +extern errcode_t ext2fs_alloc_block(ext2_filsys fs, blk_t goal, + char *block_buf, blk_t *ret); + +/* alloc_sb.c */ +extern int ext2fs_reserve_super_and_bgd(ext2_filsys fs, + dgrp_t group, + ext2fs_block_bitmap bmap); + +/* alloc_stats.c */ +void ext2fs_inode_alloc_stats(ext2_filsys fs, ext2_ino_t ino, int inuse); +void ext2fs_inode_alloc_stats2(ext2_filsys fs, ext2_ino_t ino, + int inuse, int isdir); +void ext2fs_block_alloc_stats(ext2_filsys fs, blk_t blk, int inuse); + +/* alloc_tables.c */ +extern errcode_t ext2fs_allocate_tables(ext2_filsys fs); +extern errcode_t ext2fs_allocate_group_table(ext2_filsys fs, dgrp_t group, + ext2fs_block_bitmap bmap); + +/* badblocks.c */ +extern errcode_t ext2fs_u32_list_create(ext2_u32_list *ret, int size); +extern errcode_t ext2fs_u32_list_add(ext2_u32_list bb, __u32 blk); +extern int ext2fs_u32_list_find(ext2_u32_list bb, __u32 blk); +extern int ext2fs_u32_list_test(ext2_u32_list bb, blk_t blk); +extern errcode_t ext2fs_u32_list_iterate_begin(ext2_u32_list bb, + ext2_u32_iterate *ret); +extern int ext2fs_u32_list_iterate(ext2_u32_iterate iter, blk_t *blk); +extern void ext2fs_u32_list_iterate_end(ext2_u32_iterate iter); +extern errcode_t ext2fs_u32_copy(ext2_u32_list src, ext2_u32_list *dest); +extern int ext2fs_u32_list_equal(ext2_u32_list bb1, ext2_u32_list bb2); + +extern errcode_t ext2fs_badblocks_list_create(ext2_badblocks_list *ret, + int size); +extern errcode_t ext2fs_badblocks_list_add(ext2_badblocks_list bb, + blk_t blk); +extern int ext2fs_badblocks_list_test(ext2_badblocks_list bb, + blk_t blk); +extern int ext2fs_u32_list_del(ext2_u32_list bb, __u32 blk); +extern void ext2fs_badblocks_list_del(ext2_u32_list bb, __u32 blk); +extern errcode_t + ext2fs_badblocks_list_iterate_begin(ext2_badblocks_list bb, + ext2_badblocks_iterate *ret); +extern int ext2fs_badblocks_list_iterate(ext2_badblocks_iterate iter, + blk_t *blk); +extern void ext2fs_badblocks_list_iterate_end(ext2_badblocks_iterate iter); +extern errcode_t ext2fs_badblocks_copy(ext2_badblocks_list src, + ext2_badblocks_list *dest); +extern int ext2fs_badblocks_equal(ext2_badblocks_list bb1, + ext2_badblocks_list bb2); +extern int ext2fs_u32_list_count(ext2_u32_list bb); + +/* bb_compat */ +extern errcode_t badblocks_list_create(badblocks_list *ret, int size); +extern errcode_t badblocks_list_add(badblocks_list bb, blk_t blk); +extern int badblocks_list_test(badblocks_list bb, blk_t blk); +extern errcode_t badblocks_list_iterate_begin(badblocks_list bb, + badblocks_iterate *ret); +extern int badblocks_list_iterate(badblocks_iterate iter, blk_t *blk); +extern void badblocks_list_iterate_end(badblocks_iterate iter); +extern void badblocks_list_free(badblocks_list bb); + +/* bb_inode.c */ +extern errcode_t ext2fs_update_bb_inode(ext2_filsys fs, + ext2_badblocks_list bb_list); + +/* bitmaps.c */ +extern errcode_t ext2fs_write_inode_bitmap(ext2_filsys fs); +extern errcode_t ext2fs_write_block_bitmap (ext2_filsys fs); +extern errcode_t ext2fs_read_inode_bitmap (ext2_filsys fs); +extern errcode_t ext2fs_read_block_bitmap(ext2_filsys fs); +extern errcode_t ext2fs_allocate_generic_bitmap(__u32 start, + __u32 end, + __u32 real_end, + const char *descr, + ext2fs_generic_bitmap *ret); +extern errcode_t ext2fs_allocate_block_bitmap(ext2_filsys fs, + const char *descr, + ext2fs_block_bitmap *ret); +extern errcode_t ext2fs_allocate_inode_bitmap(ext2_filsys fs, + const char *descr, + ext2fs_inode_bitmap *ret); +extern errcode_t ext2fs_fudge_inode_bitmap_end(ext2fs_inode_bitmap bitmap, + ext2_ino_t end, ext2_ino_t *oend); +extern errcode_t ext2fs_fudge_block_bitmap_end(ext2fs_block_bitmap bitmap, + blk_t end, blk_t *oend); +extern void ext2fs_clear_inode_bitmap(ext2fs_inode_bitmap bitmap); +extern void ext2fs_clear_block_bitmap(ext2fs_block_bitmap bitmap); +extern errcode_t ext2fs_read_bitmaps(ext2_filsys fs); +extern errcode_t ext2fs_write_bitmaps(ext2_filsys fs); + +/* block.c */ +extern errcode_t ext2fs_block_iterate(ext2_filsys fs, + ext2_ino_t ino, + int flags, + char *block_buf, + int (*func)(ext2_filsys fs, + blk_t *blocknr, + int blockcnt, + void *priv_data), + void *priv_data); +errcode_t ext2fs_block_iterate2(ext2_filsys fs, + ext2_ino_t ino, + int flags, + char *block_buf, + int (*func)(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_blk, + int ref_offset, + void *priv_data), + void *priv_data); + +/* bmap.c */ +extern errcode_t ext2fs_bmap(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + char *block_buf, int bmap_flags, + blk_t block, blk_t *phys_blk); + + +#if 0 +/* bmove.c */ +extern errcode_t ext2fs_move_blocks(ext2_filsys fs, + ext2fs_block_bitmap reserve, + ext2fs_block_bitmap alloc_map, + int flags); +#endif + +/* check_desc.c */ +extern errcode_t ext2fs_check_desc(ext2_filsys fs); + +/* closefs.c */ +extern errcode_t ext2fs_close(ext2_filsys fs); +extern errcode_t ext2fs_flush(ext2_filsys fs); +extern int ext2fs_bg_has_super(ext2_filsys fs, int group_block); +extern int ext2fs_super_and_bgd_loc(ext2_filsys fs, + dgrp_t group, + blk_t *ret_super_blk, + blk_t *ret_old_desc_blk, + blk_t *ret_new_desc_blk, + int *ret_meta_bg); +extern void ext2fs_update_dynamic_rev(ext2_filsys fs); + +/* cmp_bitmaps.c */ +extern errcode_t ext2fs_compare_block_bitmap(ext2fs_block_bitmap bm1, + ext2fs_block_bitmap bm2); +extern errcode_t ext2fs_compare_inode_bitmap(ext2fs_inode_bitmap bm1, + ext2fs_inode_bitmap bm2); + +/* dblist.c */ + +extern errcode_t ext2fs_get_num_dirs(ext2_filsys fs, ext2_ino_t *ret_num_dirs); +extern errcode_t ext2fs_init_dblist(ext2_filsys fs, ext2_dblist *ret_dblist); +extern errcode_t ext2fs_add_dir_block(ext2_dblist dblist, ext2_ino_t ino, + blk_t blk, int blockcnt); +extern void ext2fs_dblist_sort(ext2_dblist dblist, + int (*sortfunc)(const void *, + const void *)); +extern errcode_t ext2fs_dblist_iterate(ext2_dblist dblist, + int (*func)(ext2_filsys fs, struct ext2_db_entry *db_info, + void *priv_data), + void *priv_data); +extern errcode_t ext2fs_set_dir_block(ext2_dblist dblist, ext2_ino_t ino, + blk_t blk, int blockcnt); +extern errcode_t ext2fs_copy_dblist(ext2_dblist src, + ext2_dblist *dest); +extern int ext2fs_dblist_count(ext2_dblist dblist); + +/* dblist_dir.c */ +extern errcode_t + ext2fs_dblist_dir_iterate(ext2_dblist dblist, + int flags, + char *block_buf, + int (*func)(ext2_ino_t dir, + int entry, + struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data); + +/* dirblock.c */ +extern errcode_t ext2fs_read_dir_block(ext2_filsys fs, blk_t block, + void *buf); +extern errcode_t ext2fs_read_dir_block2(ext2_filsys fs, blk_t block, + void *buf, int flags); +extern errcode_t ext2fs_write_dir_block(ext2_filsys fs, blk_t block, + void *buf); +extern errcode_t ext2fs_write_dir_block2(ext2_filsys fs, blk_t block, + void *buf, int flags); + +/* dirhash.c */ +extern errcode_t ext2fs_dirhash(int version, const char *name, int len, + const __u32 *seed, + ext2_dirhash_t *ret_hash, + ext2_dirhash_t *ret_minor_hash); + + +/* dir_iterate.c */ +extern errcode_t ext2fs_dir_iterate(ext2_filsys fs, + ext2_ino_t dir, + int flags, + char *block_buf, + int (*func)(struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data); +extern errcode_t ext2fs_dir_iterate2(ext2_filsys fs, + ext2_ino_t dir, + int flags, + char *block_buf, + int (*func)(ext2_ino_t dir, + int entry, + struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data); + +/* dupfs.c */ +extern errcode_t ext2fs_dup_handle(ext2_filsys src, ext2_filsys *dest); + +/* expanddir.c */ +extern errcode_t ext2fs_expand_dir(ext2_filsys fs, ext2_ino_t dir); + +/* ext_attr.c */ +extern errcode_t ext2fs_read_ext_attr(ext2_filsys fs, blk_t block, void *buf); +extern errcode_t ext2fs_write_ext_attr(ext2_filsys fs, blk_t block, + void *buf); +extern errcode_t ext2fs_adjust_ea_refcount(ext2_filsys fs, blk_t blk, + char *block_buf, + int adjust, __u32 *newcount); + +/* fileio.c */ +extern errcode_t ext2fs_file_open2(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + int flags, ext2_file_t *ret); +extern errcode_t ext2fs_file_open(ext2_filsys fs, ext2_ino_t ino, + int flags, ext2_file_t *ret); +extern ext2_filsys ext2fs_file_get_fs(ext2_file_t file); +extern errcode_t ext2fs_file_close(ext2_file_t file); +extern errcode_t ext2fs_file_flush(ext2_file_t file); +extern errcode_t ext2fs_file_read(ext2_file_t file, void *buf, + unsigned int wanted, unsigned int *got); +extern errcode_t ext2fs_file_write(ext2_file_t file, const void *buf, + unsigned int nbytes, unsigned int *written); +extern errcode_t ext2fs_file_llseek(ext2_file_t file, __u64 offset, + int whence, __u64 *ret_pos); +extern errcode_t ext2fs_file_lseek(ext2_file_t file, ext2_off_t offset, + int whence, ext2_off_t *ret_pos); +errcode_t ext2fs_file_get_lsize(ext2_file_t file, __u64 *ret_size); +extern ext2_off_t ext2fs_file_get_size(ext2_file_t file); +extern errcode_t ext2fs_file_set_size(ext2_file_t file, ext2_off_t size); + +/* finddev.c */ +extern char *ext2fs_find_block_device(dev_t device); + +/* flushb.c */ +extern errcode_t ext2fs_sync_device(int fd, int flushb); + +/* freefs.c */ +extern void ext2fs_free(ext2_filsys fs); +extern void ext2fs_free_generic_bitmap(ext2fs_inode_bitmap bitmap); +extern void ext2fs_free_block_bitmap(ext2fs_block_bitmap bitmap); +extern void ext2fs_free_inode_bitmap(ext2fs_inode_bitmap bitmap); +extern void ext2fs_free_dblist(ext2_dblist dblist); +extern void ext2fs_badblocks_list_free(ext2_badblocks_list bb); +extern void ext2fs_u32_list_free(ext2_u32_list bb); + +/* getsize.c */ +extern errcode_t ext2fs_get_device_size(const char *file, int blocksize, + blk_t *retblocks); + +/* getsectsize.c */ +errcode_t ext2fs_get_device_sectsize(const char *file, int *sectsize); + +/* imager.c */ +extern errcode_t ext2fs_image_inode_write(ext2_filsys fs, int fd, int flags); +extern errcode_t ext2fs_image_inode_read(ext2_filsys fs, int fd, int flags); +extern errcode_t ext2fs_image_super_write(ext2_filsys fs, int fd, int flags); +extern errcode_t ext2fs_image_super_read(ext2_filsys fs, int fd, int flags); +extern errcode_t ext2fs_image_bitmap_write(ext2_filsys fs, int fd, int flags); +extern errcode_t ext2fs_image_bitmap_read(ext2_filsys fs, int fd, int flags); + +/* ind_block.c */ +errcode_t ext2fs_read_ind_block(ext2_filsys fs, blk_t blk, void *buf); +errcode_t ext2fs_write_ind_block(ext2_filsys fs, blk_t blk, void *buf); + +/* initialize.c */ +extern errcode_t ext2fs_initialize(const char *name, int flags, + struct ext2_super_block *param, + io_manager manager, ext2_filsys *ret_fs); + +/* icount.c */ +extern void ext2fs_free_icount(ext2_icount_t icount); +extern errcode_t ext2fs_create_icount2(ext2_filsys fs, int flags, + unsigned int size, + ext2_icount_t hint, ext2_icount_t *ret); +extern errcode_t ext2fs_create_icount(ext2_filsys fs, int flags, + unsigned int size, + ext2_icount_t *ret); +extern errcode_t ext2fs_icount_fetch(ext2_icount_t icount, ext2_ino_t ino, + __u16 *ret); +extern errcode_t ext2fs_icount_increment(ext2_icount_t icount, ext2_ino_t ino, + __u16 *ret); +extern errcode_t ext2fs_icount_decrement(ext2_icount_t icount, ext2_ino_t ino, + __u16 *ret); +extern errcode_t ext2fs_icount_store(ext2_icount_t icount, ext2_ino_t ino, + __u16 count); +extern ext2_ino_t ext2fs_get_icount_size(ext2_icount_t icount); +errcode_t ext2fs_icount_validate(ext2_icount_t icount, FILE *); + +/* inode.c */ +extern errcode_t ext2fs_flush_icache(ext2_filsys fs); +extern errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, + ext2_ino_t *ino, + struct ext2_inode *inode, + int bufsize); +extern errcode_t ext2fs_open_inode_scan(ext2_filsys fs, int buffer_blocks, + ext2_inode_scan *ret_scan); +extern void ext2fs_close_inode_scan(ext2_inode_scan scan); +extern errcode_t ext2fs_get_next_inode(ext2_inode_scan scan, ext2_ino_t *ino, + struct ext2_inode *inode); +extern errcode_t ext2fs_inode_scan_goto_blockgroup(ext2_inode_scan scan, + int group); +extern void ext2fs_set_inode_callback + (ext2_inode_scan scan, + errcode_t (*done_group)(ext2_filsys fs, + dgrp_t group, + void * priv_data), + void *done_group_data); +extern int ext2fs_inode_scan_flags(ext2_inode_scan scan, int set_flags, + int clear_flags); +extern errcode_t ext2fs_read_inode_full(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode, + int bufsize); +extern errcode_t ext2fs_read_inode (ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode); +extern errcode_t ext2fs_write_inode_full(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode, + int bufsize); +extern errcode_t ext2fs_write_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode); +extern errcode_t ext2fs_write_new_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode); +extern errcode_t ext2fs_get_blocks(ext2_filsys fs, ext2_ino_t ino, blk_t *blocks); +extern errcode_t ext2fs_check_directory(ext2_filsys fs, ext2_ino_t ino); + +/* inode_io.c */ +extern io_manager inode_io_manager; +extern errcode_t ext2fs_inode_io_intern(ext2_filsys fs, ext2_ino_t ino, + char **name); +extern errcode_t ext2fs_inode_io_intern2(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + char **name); + +/* ismounted.c */ +extern errcode_t ext2fs_check_if_mounted(const char *file, int *mount_flags); +extern errcode_t ext2fs_check_mount_point(const char *device, int *mount_flags, + char *mtpt, int mtlen); + +/* namei.c */ +extern errcode_t ext2fs_lookup(ext2_filsys fs, ext2_ino_t dir, const char *name, + int namelen, char *buf, ext2_ino_t *inode); +extern errcode_t ext2fs_namei(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + const char *name, ext2_ino_t *inode); +errcode_t ext2fs_namei_follow(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + const char *name, ext2_ino_t *inode); +extern errcode_t ext2fs_follow_link(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + ext2_ino_t inode, ext2_ino_t *res_inode); + +/* native.c */ +int ext2fs_native_flag(void); + +/* newdir.c */ +extern errcode_t ext2fs_new_dir_block(ext2_filsys fs, ext2_ino_t dir_ino, + ext2_ino_t parent_ino, char **block); + +/* mkdir.c */ +extern errcode_t ext2fs_mkdir(ext2_filsys fs, ext2_ino_t parent, ext2_ino_t inum, + const char *name); + +/* mkjournal.c */ +extern errcode_t ext2fs_create_journal_superblock(ext2_filsys fs, + __u32 size, int flags, + char **ret_jsb); +extern errcode_t ext2fs_add_journal_device(ext2_filsys fs, + ext2_filsys journal_dev); +extern errcode_t ext2fs_add_journal_inode(ext2_filsys fs, blk_t size, + int flags); + +/* openfs.c */ +extern errcode_t ext2fs_open(const char *name, int flags, int superblock, + unsigned int block_size, io_manager manager, + ext2_filsys *ret_fs); +extern errcode_t ext2fs_open2(const char *name, const char *io_options, + int flags, int superblock, + unsigned int block_size, io_manager manager, + ext2_filsys *ret_fs); +extern blk_t ext2fs_descriptor_block_loc(ext2_filsys fs, blk_t group_block, + dgrp_t i); +errcode_t ext2fs_get_data_io(ext2_filsys fs, io_channel *old_io); +errcode_t ext2fs_set_data_io(ext2_filsys fs, io_channel new_io); +errcode_t ext2fs_rewrite_to_io(ext2_filsys fs, io_channel new_io); + +/* get_pathname.c */ +extern errcode_t ext2fs_get_pathname(ext2_filsys fs, ext2_ino_t dir, ext2_ino_t ino, + char **name); + +/* link.c */ +errcode_t ext2fs_link(ext2_filsys fs, ext2_ino_t dir, const char *name, + ext2_ino_t ino, int flags); +errcode_t ext2fs_unlink(ext2_filsys fs, ext2_ino_t dir, const char *name, + ext2_ino_t ino, int flags); + +/* read_bb.c */ +extern errcode_t ext2fs_read_bb_inode(ext2_filsys fs, + ext2_badblocks_list *bb_list); + +/* read_bb_file.c */ +extern errcode_t ext2fs_read_bb_FILE2(ext2_filsys fs, FILE *f, + ext2_badblocks_list *bb_list, + void *priv_data, + void (*invalid)(ext2_filsys fs, + blk_t blk, + char *badstr, + void *priv_data)); +extern errcode_t ext2fs_read_bb_FILE(ext2_filsys fs, FILE *f, + ext2_badblocks_list *bb_list, + void (*invalid)(ext2_filsys fs, + blk_t blk)); + +/* res_gdt.c */ +extern errcode_t ext2fs_create_resize_inode(ext2_filsys fs); + +/* rs_bitmap.c */ +extern errcode_t ext2fs_resize_generic_bitmap(__u32 new_end, + __u32 new_real_end, + ext2fs_generic_bitmap bmap); +extern errcode_t ext2fs_resize_inode_bitmap(__u32 new_end, __u32 new_real_end, + ext2fs_inode_bitmap bmap); +extern errcode_t ext2fs_resize_block_bitmap(__u32 new_end, __u32 new_real_end, + ext2fs_block_bitmap bmap); +extern errcode_t ext2fs_copy_bitmap(ext2fs_generic_bitmap src, + ext2fs_generic_bitmap *dest); + +/* swapfs.c */ +extern void ext2fs_swap_ext_attr(char *to, char *from, int bufsize, + int has_header); +extern void ext2fs_swap_super(struct ext2_super_block * super); +extern void ext2fs_swap_group_desc(struct ext2_group_desc *gdp); +extern void ext2fs_swap_inode_full(ext2_filsys fs, struct ext2_inode_large *t, + struct ext2_inode_large *f, int hostorder, + int bufsize); +extern void ext2fs_swap_inode(ext2_filsys fs,struct ext2_inode *t, + struct ext2_inode *f, int hostorder); + +/* valid_blk.c */ +extern int ext2fs_inode_has_valid_blocks(struct ext2_inode *inode); + +/* version.c */ +extern int ext2fs_parse_version_string(const char *ver_string); +extern int ext2fs_get_library_version(const char **ver_string, + const char **date_string); + +/* write_bb_file.c */ +extern errcode_t ext2fs_write_bb_FILE(ext2_badblocks_list bb_list, + unsigned int flags, + FILE *f); + + +/* inline functions */ +extern errcode_t ext2fs_get_mem(unsigned long size, void *ptr); +extern errcode_t ext2fs_free_mem(void *ptr); +extern errcode_t ext2fs_resize_mem(unsigned long old_size, + unsigned long size, void *ptr); +extern void ext2fs_mark_super_dirty(ext2_filsys fs); +extern void ext2fs_mark_changed(ext2_filsys fs); +extern int ext2fs_test_changed(ext2_filsys fs); +extern void ext2fs_mark_valid(ext2_filsys fs); +extern void ext2fs_unmark_valid(ext2_filsys fs); +extern int ext2fs_test_valid(ext2_filsys fs); +extern void ext2fs_mark_ib_dirty(ext2_filsys fs); +extern void ext2fs_mark_bb_dirty(ext2_filsys fs); +extern int ext2fs_test_ib_dirty(ext2_filsys fs); +extern int ext2fs_test_bb_dirty(ext2_filsys fs); +extern int ext2fs_group_of_blk(ext2_filsys fs, blk_t blk); +extern int ext2fs_group_of_ino(ext2_filsys fs, ext2_ino_t ino); +extern blk_t ext2fs_inode_data_blocks(ext2_filsys fs, + struct ext2_inode *inode); + +#ifdef __cplusplus +} +#endif + +#endif /* _EXT2FS_EXT2FS_H */ diff --git a/e2fsprogs/ext2fs/ext2fsP.h b/e2fsprogs/ext2fs/ext2fsP.h new file mode 100644 index 000000000..908b5d9a4 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2fsP.h @@ -0,0 +1,89 @@ +/* vi: set sw=4 ts=4: */ +/* + * ext2fsP.h --- private header file for ext2 library + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include "ext2fs.h" + +/* + * Badblocks list + */ +struct ext2_struct_u32_list { + int magic; + int num; + int size; + __u32 *list; + int badblocks_flags; +}; + +struct ext2_struct_u32_iterate { + int magic; + ext2_u32_list bb; + int ptr; +}; + + +/* + * Directory block iterator definition + */ +struct ext2_struct_dblist { + int magic; + ext2_filsys fs; + ext2_ino_t size; + ext2_ino_t count; + int sorted; + struct ext2_db_entry * list; +}; + +/* + * For directory iterators + */ +struct dir_context { + ext2_ino_t dir; + int flags; + char *buf; + int (*func)(ext2_ino_t dir, + int entry, + struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data); + void *priv_data; + errcode_t errcode; +}; + +/* + * Inode cache structure + */ +struct ext2_inode_cache { + void * buffer; + blk_t buffer_blk; + int cache_last; + int cache_size; + int refcount; + struct ext2_inode_cache_ent *cache; +}; + +struct ext2_inode_cache_ent { + ext2_ino_t ino; + struct ext2_inode inode; +}; + +/* Function prototypes */ + +extern int ext2fs_process_dir_block(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_block, + int ref_offset, + void *priv_data); + + diff --git a/e2fsprogs/ext2fs/ext2fs_inline.c b/e2fsprogs/ext2fs/ext2fs_inline.c new file mode 100644 index 000000000..da1cf5be5 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2fs_inline.c @@ -0,0 +1,367 @@ +/* vi: set sw=4 ts=4: */ +/* + * ext2fs.h --- ext2fs + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include "ext2fs.h" +#include "bitops.h" +#include + +/* + * Allocate memory + */ +errcode_t ext2fs_get_mem(unsigned long size, void *ptr) +{ + void **pp = (void **)ptr; + + *pp = malloc(size); + if (!*pp) + return EXT2_ET_NO_MEMORY; + return 0; +} + +/* + * Free memory + */ +errcode_t ext2fs_free_mem(void *ptr) +{ + void **pp = (void **)ptr; + + free(*pp); + *pp = 0; + return 0; +} + +/* + * Resize memory + */ +errcode_t ext2fs_resize_mem(unsigned long EXT2FS_ATTR((unused)) old_size, + unsigned long size, void *ptr) +{ + void *p; + + /* Use "memcpy" for pointer assignments here to avoid problems + * with C99 strict type aliasing rules. */ + memcpy(&p, ptr, sizeof (p)); + p = realloc(p, size); + if (!p) + return EXT2_ET_NO_MEMORY; + memcpy(ptr, &p, sizeof (p)); + return 0; +} + +/* + * Mark a filesystem superblock as dirty + */ +void ext2fs_mark_super_dirty(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_DIRTY | EXT2_FLAG_CHANGED; +} + +/* + * Mark a filesystem as changed + */ +void ext2fs_mark_changed(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_CHANGED; +} + +/* + * Check to see if a filesystem has changed + */ +int ext2fs_test_changed(ext2_filsys fs) +{ + return (fs->flags & EXT2_FLAG_CHANGED); +} + +/* + * Mark a filesystem as valid + */ +void ext2fs_mark_valid(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_VALID; +} + +/* + * Mark a filesystem as NOT valid + */ +void ext2fs_unmark_valid(ext2_filsys fs) +{ + fs->flags &= ~EXT2_FLAG_VALID; +} + +/* + * Check to see if a filesystem is valid + */ +int ext2fs_test_valid(ext2_filsys fs) +{ + return (fs->flags & EXT2_FLAG_VALID); +} + +/* + * Mark the inode bitmap as dirty + */ +void ext2fs_mark_ib_dirty(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_IB_DIRTY | EXT2_FLAG_CHANGED; +} + +/* + * Mark the block bitmap as dirty + */ +void ext2fs_mark_bb_dirty(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_BB_DIRTY | EXT2_FLAG_CHANGED; +} + +/* + * Check to see if a filesystem's inode bitmap is dirty + */ +int ext2fs_test_ib_dirty(ext2_filsys fs) +{ + return (fs->flags & EXT2_FLAG_IB_DIRTY); +} + +/* + * Check to see if a filesystem's block bitmap is dirty + */ +int ext2fs_test_bb_dirty(ext2_filsys fs) +{ + return (fs->flags & EXT2_FLAG_BB_DIRTY); +} + +/* + * Return the group # of a block + */ +int ext2fs_group_of_blk(ext2_filsys fs, blk_t blk) +{ + return (blk - fs->super->s_first_data_block) / + fs->super->s_blocks_per_group; +} + +/* + * Return the group # of an inode number + */ +int ext2fs_group_of_ino(ext2_filsys fs, ext2_ino_t ino) +{ + return (ino - 1) / fs->super->s_inodes_per_group; +} + +blk_t ext2fs_inode_data_blocks(ext2_filsys fs, + struct ext2_inode *inode) +{ + return inode->i_blocks - + (inode->i_file_acl ? fs->blocksize >> 9 : 0); +} + + + + + + + + + +__u16 ext2fs_swab16(__u16 val) +{ + return (val >> 8) | (val << 8); +} + +__u32 ext2fs_swab32(__u32 val) +{ + return ((val>>24) | ((val>>8)&0xFF00) | + ((val<<8)&0xFF0000) | (val<<24)); +} + +int ext2fs_test_generic_bitmap(ext2fs_generic_bitmap bitmap, + blk_t bitno); + +int ext2fs_test_generic_bitmap(ext2fs_generic_bitmap bitmap, + blk_t bitno) +{ + if ((bitno < bitmap->start) || (bitno > bitmap->end)) { + ext2fs_warn_bitmap2(bitmap, EXT2FS_TEST_ERROR, bitno); + return 0; + } + return ext2fs_test_bit(bitno - bitmap->start, bitmap->bitmap); +} + +int ext2fs_mark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + return ext2fs_mark_generic_bitmap((ext2fs_generic_bitmap) + bitmap, + block); +} + +int ext2fs_unmark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + return ext2fs_unmark_generic_bitmap((ext2fs_generic_bitmap) bitmap, + block); +} + +int ext2fs_test_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + return ext2fs_test_generic_bitmap((ext2fs_generic_bitmap) bitmap, + block); +} + +int ext2fs_mark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + return ext2fs_mark_generic_bitmap((ext2fs_generic_bitmap) bitmap, + inode); +} + +int ext2fs_unmark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + return ext2fs_unmark_generic_bitmap((ext2fs_generic_bitmap) bitmap, + inode); +} + +int ext2fs_test_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + return ext2fs_test_generic_bitmap((ext2fs_generic_bitmap) bitmap, + inode); +} + +void ext2fs_fast_mark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + ext2fs_set_bit(block - bitmap->start, bitmap->bitmap); +} + +void ext2fs_fast_unmark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + ext2fs_clear_bit(block - bitmap->start, bitmap->bitmap); +} + +int ext2fs_fast_test_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + return ext2fs_test_bit(block - bitmap->start, bitmap->bitmap); +} + +void ext2fs_fast_mark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + ext2fs_set_bit(inode - bitmap->start, bitmap->bitmap); +} + +void ext2fs_fast_unmark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + ext2fs_clear_bit(inode - bitmap->start, bitmap->bitmap); +} + +int ext2fs_fast_test_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + return ext2fs_test_bit(inode - bitmap->start, bitmap->bitmap); +} + +blk_t ext2fs_get_block_bitmap_start(ext2fs_block_bitmap bitmap) +{ + return bitmap->start; +} + +ext2_ino_t ext2fs_get_inode_bitmap_start(ext2fs_inode_bitmap bitmap) +{ + return bitmap->start; +} + +blk_t ext2fs_get_block_bitmap_end(ext2fs_block_bitmap bitmap) +{ + return bitmap->end; +} + +ext2_ino_t ext2fs_get_inode_bitmap_end(ext2fs_inode_bitmap bitmap) +{ + return bitmap->end; +} + +int ext2fs_test_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + + if ((block < bitmap->start) || (block+num-1 > bitmap->end)) { + ext2fs_warn_bitmap(EXT2_ET_BAD_BLOCK_TEST, + block, bitmap->description); + return 0; + } + for (i=0; i < num; i++) { + if (ext2fs_fast_test_block_bitmap(bitmap, block+i)) + return 0; + } + return 1; +} + +int ext2fs_fast_test_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + + for (i=0; i < num; i++) { + if (ext2fs_fast_test_block_bitmap(bitmap, block+i)) + return 0; + } + return 1; +} + +void ext2fs_mark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + + if ((block < bitmap->start) || (block+num-1 > bitmap->end)) { + ext2fs_warn_bitmap(EXT2_ET_BAD_BLOCK_MARK, block, + bitmap->description); + return; + } + for (i=0; i < num; i++) + ext2fs_set_bit(block + i - bitmap->start, bitmap->bitmap); +} + +void ext2fs_fast_mark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + + for (i=0; i < num; i++) + ext2fs_set_bit(block + i - bitmap->start, bitmap->bitmap); +} + +void ext2fs_unmark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + + if ((block < bitmap->start) || (block+num-1 > bitmap->end)) { + ext2fs_warn_bitmap(EXT2_ET_BAD_BLOCK_UNMARK, block, + bitmap->description); + return; + } + for (i=0; i < num; i++) + ext2fs_clear_bit(block + i - bitmap->start, bitmap->bitmap); +} + +void ext2fs_fast_unmark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + for (i=0; i < num; i++) + ext2fs_clear_bit(block + i - bitmap->start, bitmap->bitmap); +} diff --git a/e2fsprogs/ext2fs/ext_attr.c b/e2fsprogs/ext2fs/ext_attr.c new file mode 100644 index 000000000..7ee41f234 --- /dev/null +++ b/e2fsprogs/ext2fs/ext_attr.c @@ -0,0 +1,101 @@ +/* vi: set sw=4 ts=4: */ +/* + * ext_attr.c --- extended attribute blocks + * + * Copyright (C) 2001 Andreas Gruenbacher, + * + * Copyright (C) 2002 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include + +#include "ext2_fs.h" +#include "ext2_ext_attr.h" +#include "ext2fs.h" + +errcode_t ext2fs_read_ext_attr(ext2_filsys fs, blk_t block, void *buf) +{ + errcode_t retval; + + retval = io_channel_read_blk(fs->io, block, 1, buf); + if (retval) + return retval; +#if BB_BIG_ENDIAN + if ((fs->flags & (EXT2_FLAG_SWAP_BYTES| + EXT2_FLAG_SWAP_BYTES_READ)) != 0) + ext2fs_swap_ext_attr(buf, buf, fs->blocksize, 1); +#endif + return 0; +} + +errcode_t ext2fs_write_ext_attr(ext2_filsys fs, blk_t block, void *inbuf) +{ + errcode_t retval; + char *write_buf; + char *buf = NULL; + + if (BB_BIG_ENDIAN && ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE))) { + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + write_buf = buf; + ext2fs_swap_ext_attr(buf, inbuf, fs->blocksize, 1); + } else + write_buf = (char *) inbuf; + retval = io_channel_write_blk(fs->io, block, 1, write_buf); + if (buf) + ext2fs_free_mem(&buf); + if (!retval) + ext2fs_mark_changed(fs); + return retval; +} + +/* + * This function adjusts the reference count of the EA block. + */ +errcode_t ext2fs_adjust_ea_refcount(ext2_filsys fs, blk_t blk, + char *block_buf, int adjust, + __u32 *newcount) +{ + errcode_t retval; + struct ext2_ext_attr_header *header; + char *buf = 0; + + if ((blk >= fs->super->s_blocks_count) || + (blk < fs->super->s_first_data_block)) + return EXT2_ET_BAD_EA_BLOCK_NUM; + + if (!block_buf) { + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + block_buf = buf; + } + + retval = ext2fs_read_ext_attr(fs, blk, block_buf); + if (retval) + goto errout; + + header = (struct ext2_ext_attr_header *) block_buf; + header->h_refcount += adjust; + if (newcount) + *newcount = header->h_refcount; + + retval = ext2fs_write_ext_attr(fs, blk, block_buf); + if (retval) + goto errout; + +errout: + if (buf) + ext2fs_free_mem(&buf); + return retval; +} diff --git a/e2fsprogs/ext2fs/fileio.c b/e2fsprogs/ext2fs/fileio.c new file mode 100644 index 000000000..c56a21aa8 --- /dev/null +++ b/e2fsprogs/ext2fs/fileio.c @@ -0,0 +1,377 @@ +/* vi: set sw=4 ts=4: */ +/* + * fileio.c --- Simple file I/O routines + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct ext2_file { + errcode_t magic; + ext2_filsys fs; + ext2_ino_t ino; + struct ext2_inode inode; + int flags; + __u64 pos; + blk_t blockno; + blk_t physblock; + char *buf; +}; + +#define BMAP_BUFFER (file->buf + fs->blocksize) + +errcode_t ext2fs_file_open2(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + int flags, ext2_file_t *ret) +{ + ext2_file_t file; + errcode_t retval; + + /* + * Don't let caller create or open a file for writing if the + * filesystem is read-only. + */ + if ((flags & (EXT2_FILE_WRITE | EXT2_FILE_CREATE)) && + !(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + retval = ext2fs_get_mem(sizeof(struct ext2_file), &file); + if (retval) + return retval; + + memset(file, 0, sizeof(struct ext2_file)); + file->magic = EXT2_ET_MAGIC_EXT2_FILE; + file->fs = fs; + file->ino = ino; + file->flags = flags & EXT2_FILE_MASK; + + if (inode) { + memcpy(&file->inode, inode, sizeof(struct ext2_inode)); + } else { + retval = ext2fs_read_inode(fs, ino, &file->inode); + if (retval) + goto fail; + } + + retval = ext2fs_get_mem(fs->blocksize * 3, &file->buf); + if (retval) + goto fail; + + *ret = file; + return 0; + +fail: + ext2fs_free_mem(&file->buf); + ext2fs_free_mem(&file); + return retval; +} + +errcode_t ext2fs_file_open(ext2_filsys fs, ext2_ino_t ino, + int flags, ext2_file_t *ret) +{ + return ext2fs_file_open2(fs, ino, NULL, flags, ret); +} + +/* + * This function returns the filesystem handle of a file from the structure + */ +ext2_filsys ext2fs_file_get_fs(ext2_file_t file) +{ + if (file->magic != EXT2_ET_MAGIC_EXT2_FILE) + return 0; + return file->fs; +} + +/* + * This function flushes the dirty block buffer out to disk if + * necessary. + */ +errcode_t ext2fs_file_flush(ext2_file_t file) +{ + errcode_t retval; + ext2_filsys fs; + + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + fs = file->fs; + + if (!(file->flags & EXT2_FILE_BUF_VALID) || + !(file->flags & EXT2_FILE_BUF_DIRTY)) + return 0; + + /* + * OK, the physical block hasn't been allocated yet. + * Allocate it. + */ + if (!file->physblock) { + retval = ext2fs_bmap(fs, file->ino, &file->inode, + BMAP_BUFFER, file->ino ? BMAP_ALLOC : 0, + file->blockno, &file->physblock); + if (retval) + return retval; + } + + retval = io_channel_write_blk(fs->io, file->physblock, + 1, file->buf); + if (retval) + return retval; + + file->flags &= ~EXT2_FILE_BUF_DIRTY; + + return retval; +} + +/* + * This function synchronizes the file's block buffer and the current + * file position, possibly invalidating block buffer if necessary + */ +static errcode_t sync_buffer_position(ext2_file_t file) +{ + blk_t b; + errcode_t retval; + + b = file->pos / file->fs->blocksize; + if (b != file->blockno) { + retval = ext2fs_file_flush(file); + if (retval) + return retval; + file->flags &= ~EXT2_FILE_BUF_VALID; + } + file->blockno = b; + return 0; +} + +/* + * This function loads the file's block buffer with valid data from + * the disk as necessary. + * + * If dontfill is true, then skip initializing the buffer since we're + * going to be replacing its entire contents anyway. If set, then the + * function basically only sets file->physblock and EXT2_FILE_BUF_VALID + */ +#define DONTFILL 1 +static errcode_t load_buffer(ext2_file_t file, int dontfill) +{ + ext2_filsys fs = file->fs; + errcode_t retval; + + if (!(file->flags & EXT2_FILE_BUF_VALID)) { + retval = ext2fs_bmap(fs, file->ino, &file->inode, + BMAP_BUFFER, 0, file->blockno, + &file->physblock); + if (retval) + return retval; + if (!dontfill) { + if (file->physblock) { + retval = io_channel_read_blk(fs->io, + file->physblock, + 1, file->buf); + if (retval) + return retval; + } else + memset(file->buf, 0, fs->blocksize); + } + file->flags |= EXT2_FILE_BUF_VALID; + } + return 0; +} + + +errcode_t ext2fs_file_close(ext2_file_t file) +{ + errcode_t retval; + + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + + retval = ext2fs_file_flush(file); + + ext2fs_free_mem(&file->buf); + ext2fs_free_mem(&file); + + return retval; +} + + +errcode_t ext2fs_file_read(ext2_file_t file, void *buf, + unsigned int wanted, unsigned int *got) +{ + ext2_filsys fs; + errcode_t retval = 0; + unsigned int start, c, count = 0; + __u64 left; + char *ptr = (char *) buf; + + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + fs = file->fs; + + while ((file->pos < EXT2_I_SIZE(&file->inode)) && (wanted > 0)) { + retval = sync_buffer_position(file); + if (retval) + goto fail; + retval = load_buffer(file, 0); + if (retval) + goto fail; + + start = file->pos % fs->blocksize; + c = fs->blocksize - start; + if (c > wanted) + c = wanted; + left = EXT2_I_SIZE(&file->inode) - file->pos ; + if (c > left) + c = left; + + memcpy(ptr, file->buf+start, c); + file->pos += c; + ptr += c; + count += c; + wanted -= c; + } + +fail: + if (got) + *got = count; + return retval; +} + + +errcode_t ext2fs_file_write(ext2_file_t file, const void *buf, + unsigned int nbytes, unsigned int *written) +{ + ext2_filsys fs; + errcode_t retval = 0; + unsigned int start, c, count = 0; + const char *ptr = (const char *) buf; + + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + fs = file->fs; + + if (!(file->flags & EXT2_FILE_WRITE)) + return EXT2_ET_FILE_RO; + + while (nbytes > 0) { + retval = sync_buffer_position(file); + if (retval) + goto fail; + + start = file->pos % fs->blocksize; + c = fs->blocksize - start; + if (c > nbytes) + c = nbytes; + + /* + * We only need to do a read-modify-update cycle if + * we're doing a partial write. + */ + retval = load_buffer(file, (c == fs->blocksize)); + if (retval) + goto fail; + + file->flags |= EXT2_FILE_BUF_DIRTY; + memcpy(file->buf+start, ptr, c); + file->pos += c; + ptr += c; + count += c; + nbytes -= c; + } + +fail: + if (written) + *written = count; + return retval; +} + +errcode_t ext2fs_file_llseek(ext2_file_t file, __u64 offset, + int whence, __u64 *ret_pos) +{ + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + + if (whence == EXT2_SEEK_SET) + file->pos = offset; + else if (whence == EXT2_SEEK_CUR) + file->pos += offset; + else if (whence == EXT2_SEEK_END) + file->pos = EXT2_I_SIZE(&file->inode) + offset; + else + return EXT2_ET_INVALID_ARGUMENT; + + if (ret_pos) + *ret_pos = file->pos; + + return 0; +} + +errcode_t ext2fs_file_lseek(ext2_file_t file, ext2_off_t offset, + int whence, ext2_off_t *ret_pos) +{ + __u64 loffset, ret_loffset; + errcode_t retval; + + loffset = offset; + retval = ext2fs_file_llseek(file, loffset, whence, &ret_loffset); + if (ret_pos) + *ret_pos = (ext2_off_t) ret_loffset; + return retval; +} + + +/* + * This function returns the size of the file, according to the inode + */ +errcode_t ext2fs_file_get_lsize(ext2_file_t file, __u64 *ret_size) +{ + if (file->magic != EXT2_ET_MAGIC_EXT2_FILE) + return EXT2_ET_MAGIC_EXT2_FILE; + *ret_size = EXT2_I_SIZE(&file->inode); + return 0; +} + +/* + * This function returns the size of the file, according to the inode + */ +ext2_off_t ext2fs_file_get_size(ext2_file_t file) +{ + __u64 size; + + if (ext2fs_file_get_lsize(file, &size)) + return 0; + if ((size >> 32) != 0) + return 0; + return size; +} + +/* + * This function sets the size of the file, truncating it if necessary + * + * XXX still need to call truncate + */ +errcode_t ext2fs_file_set_size(ext2_file_t file, ext2_off_t size) +{ + errcode_t retval; + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + + file->inode.i_size = size; + file->inode.i_size_high = 0; + if (file->ino) { + retval = ext2fs_write_inode(file->fs, file->ino, &file->inode); + if (retval) + return retval; + } + + /* + * XXX truncate inode if necessary + */ + + return 0; +} diff --git a/e2fsprogs/ext2fs/finddev.c b/e2fsprogs/ext2fs/finddev.c new file mode 100644 index 000000000..5e2cce940 --- /dev/null +++ b/e2fsprogs/ext2fs/finddev.c @@ -0,0 +1,199 @@ +/* vi: set sw=4 ts=4: */ +/* + * finddev.c -- this routine attempts to find a particular device in + * /dev + * + * Copyright (C) 2000 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#include +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_SYS_MKDEV_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct dir_list { + char *name; + struct dir_list *next; +}; + +/* + * This function adds an entry to the directory list + */ +static void add_to_dirlist(const char *name, struct dir_list **list) +{ + struct dir_list *dp; + + dp = xmalloc(sizeof(struct dir_list)); + dp->name = xmalloc(strlen(name)+1); + strcpy(dp->name, name); + dp->next = *list; + *list = dp; +} + +/* + * This function frees a directory list + */ +static void free_dirlist(struct dir_list **list) +{ + struct dir_list *dp, *next; + + for (dp = *list; dp; dp = next) { + next = dp->next; + free(dp->name); + free(dp); + } + *list = 0; +} + +static int scan_dir(char *dir_name, dev_t device, struct dir_list **list, + char **ret_path) +{ + DIR *dir; + struct dirent *dp; + char path[1024], *cp; + int dirlen; + struct stat st; + + dirlen = strlen(dir_name); + if ((dir = opendir(dir_name)) == NULL) + return errno; + dp = readdir(dir); + while (dp) { + if (dirlen + strlen(dp->d_name) + 2 >= sizeof(path)) + goto skip_to_next; + if (dp->d_name[0] == '.' && + ((dp->d_name[1] == 0) || + ((dp->d_name[1] == '.') && (dp->d_name[2] == 0)))) + goto skip_to_next; + sprintf(path, "%s/%s", dir_name, dp->d_name); + if (stat(path, &st) < 0) + goto skip_to_next; + if (S_ISDIR(st.st_mode)) + add_to_dirlist(path, list); + if (S_ISBLK(st.st_mode) && st.st_rdev == device) { + cp = xmalloc(strlen(path)+1); + strcpy(cp, path); + *ret_path = cp; + goto success; + } + skip_to_next: + dp = readdir(dir); + } +success: + closedir(dir); + return 0; +} + +/* + * This function finds the pathname to a block device with a given + * device number. It returns a pointer to allocated memory to the + * pathname on success, and NULL on failure. + */ +char *ext2fs_find_block_device(dev_t device) +{ + struct dir_list *list = 0, *new_list = 0; + struct dir_list *current; + char *ret_path = 0; + + /* + * Add the starting directories to search... + */ + add_to_dirlist("/devices", &list); + add_to_dirlist("/devfs", &list); + add_to_dirlist("/dev", &list); + + while (list) { + current = list; + list = list->next; +#ifdef DEBUG + printf("Scanning directory %s\n", current->name); +#endif + scan_dir(current->name, device, &new_list, &ret_path); + free(current->name); + free(current); + if (ret_path) + break; + /* + * If we're done checking at this level, descend to + * the next level of subdirectories. (breadth-first) + */ + if (list == 0) { + list = new_list; + new_list = 0; + } + } + free_dirlist(&list); + free_dirlist(&new_list); + return ret_path; +} + + +#ifdef DEBUG +int main(int argc, char** argv) +{ + char *devname, *tmp; + int major, minor; + dev_t device; + const char *errmsg = "Cannot parse %s: %s\n"; + + if ((argc != 2) && (argc != 3)) { + fprintf(stderr, "Usage: %s device_number\n", argv[0]); + fprintf(stderr, "\t: %s major minor\n", argv[0]); + exit(1); + } + if (argc == 2) { + device = strtoul(argv[1], &tmp, 0); + if (*tmp) { + fprintf(stderr, errmsg, "device number", argv[1]); + exit(1); + } + } else { + major = strtoul(argv[1], &tmp, 0); + if (*tmp) { + fprintf(stderr, errmsg, "major number", argv[1]); + exit(1); + } + minor = strtoul(argv[2], &tmp, 0); + if (*tmp) { + fprintf(stderr, errmsg, "minor number", argv[2]); + exit(1); + } + device = makedev(major, minor); + printf("Looking for device 0x%04x (%d:%d)\n", device, + major, minor); + } + devname = ext2fs_find_block_device(device); + if (devname) { + printf("Found device! %s\n", devname); + free(devname); + } else { + printf("Cannot find device.\n"); + } + return 0; +} + +#endif diff --git a/e2fsprogs/ext2fs/flushb.c b/e2fsprogs/ext2fs/flushb.c new file mode 100644 index 000000000..e42982653 --- /dev/null +++ b/e2fsprogs/ext2fs/flushb.c @@ -0,0 +1,83 @@ +/* vi: set sw=4 ts=4: */ +/* + * flushb.c --- Hides system-dependent information for both syncing a + * device to disk and to flush any buffers from disk cache. + * + * Copyright (C) 2000 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_ERRNO_H +#include +#endif +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_SYS_IOCTL_H +#include +#endif +#if HAVE_SYS_MOUNT_H +#include +#include /* This may define BLKFLSBUF */ +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * For Linux, define BLKFLSBUF and FDFLUSH if necessary, since + * not all portable header file does so for us. This really should be + * fixed in the glibc header files. (Recent glibcs appear to define + * BLKFLSBUF in sys/mount.h, but FDFLUSH still doesn't seem to be + * defined anywhere portable.) Until then.... + */ +#ifdef __linux__ +#ifndef BLKFLSBUF +#define BLKFLSBUF _IO(0x12,97) /* flush buffer cache */ +#endif +#ifndef FDFLUSH +#define FDFLUSH _IO(2,0x4b) /* flush floppy disk */ +#endif +#endif + +/* + * This function will sync a device/file, and optionally attempt to + * flush the buffer cache. The latter is basically only useful for + * system benchmarks and for torturing systems in burn-in tests. :) + */ +errcode_t ext2fs_sync_device(int fd, int flushb) +{ + /* + * We always sync the device in case we're running on old + * kernels for which we can lose data if we don't. (There + * still is a race condition for those kernels, but this + * reduces it greatly.) + */ + if (fsync (fd) == -1) + return errno; + + if (flushb) { + +#ifdef BLKFLSBUF + if (ioctl (fd, BLKFLSBUF, 0) == 0) + return 0; +#else +#ifdef __GNUC__ +# warning BLKFLSBUF not defined +#endif /* __GNUC__ */ +#endif +#ifdef FDFLUSH + ioctl (fd, FDFLUSH, 0); /* In case this is a floppy */ +#else +#ifdef __GNUC__ +# warning FDFLUSH not defined +#endif /* __GNUC__ */ +#endif + } + return 0; +} diff --git a/e2fsprogs/ext2fs/freefs.c b/e2fsprogs/ext2fs/freefs.c new file mode 100644 index 000000000..65c4ee794 --- /dev/null +++ b/e2fsprogs/ext2fs/freefs.c @@ -0,0 +1,128 @@ +/* vi: set sw=4 ts=4: */ +/* + * freefs.c --- free an ext2 filesystem + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" + +static void ext2fs_free_inode_cache(struct ext2_inode_cache *icache); + +void ext2fs_free(ext2_filsys fs) +{ + if (!fs || (fs->magic != EXT2_ET_MAGIC_EXT2FS_FILSYS)) + return; + if (fs->image_io != fs->io) { + if (fs->image_io) + io_channel_close(fs->image_io); + } + if (fs->io) { + io_channel_close(fs->io); + } + ext2fs_free_mem(&fs->device_name); + ext2fs_free_mem(&fs->super); + ext2fs_free_mem(&fs->orig_super); + ext2fs_free_mem(&fs->group_desc); + ext2fs_free_block_bitmap(fs->block_map); + ext2fs_free_inode_bitmap(fs->inode_map); + + ext2fs_badblocks_list_free(fs->badblocks); + fs->badblocks = 0; + + ext2fs_free_dblist(fs->dblist); + + if (fs->icache) + ext2fs_free_inode_cache(fs->icache); + + fs->magic = 0; + + ext2fs_free_mem(&fs); +} + +void ext2fs_free_generic_bitmap(ext2fs_inode_bitmap bitmap) +{ + if (!bitmap || (bitmap->magic != EXT2_ET_MAGIC_GENERIC_BITMAP)) + return; + + bitmap->magic = 0; + ext2fs_free_mem(&bitmap->description); + ext2fs_free_mem(&bitmap->bitmap); + ext2fs_free_mem(&bitmap); +} + +void ext2fs_free_inode_bitmap(ext2fs_inode_bitmap bitmap) +{ + if (!bitmap || (bitmap->magic != EXT2_ET_MAGIC_INODE_BITMAP)) + return; + + bitmap->magic = EXT2_ET_MAGIC_GENERIC_BITMAP; + ext2fs_free_generic_bitmap(bitmap); +} + +void ext2fs_free_block_bitmap(ext2fs_block_bitmap bitmap) +{ + if (!bitmap || (bitmap->magic != EXT2_ET_MAGIC_BLOCK_BITMAP)) + return; + + bitmap->magic = EXT2_ET_MAGIC_GENERIC_BITMAP; + ext2fs_free_generic_bitmap(bitmap); +} + +/* + * Free the inode cache structure + */ +static void ext2fs_free_inode_cache(struct ext2_inode_cache *icache) +{ + if (--icache->refcount) + return; + ext2fs_free_mem(&icache->buffer); + ext2fs_free_mem(&icache->cache); + icache->buffer_blk = 0; + ext2fs_free_mem(&icache); +} + +/* + * This procedure frees a badblocks list. + */ +void ext2fs_u32_list_free(ext2_u32_list bb) +{ + if (!bb || bb->magic != EXT2_ET_MAGIC_BADBLOCKS_LIST) + return; + + ext2fs_free_mem(&bb->list); + ext2fs_free_mem(&bb); +} + +void ext2fs_badblocks_list_free(ext2_badblocks_list bb) +{ + ext2fs_u32_list_free((ext2_u32_list) bb); +} + + +/* + * Free a directory block list + */ +void ext2fs_free_dblist(ext2_dblist dblist) +{ + if (!dblist || (dblist->magic != EXT2_ET_MAGIC_DBLIST)) + return; + + ext2fs_free_mem(&dblist->list); + if (dblist->fs && dblist->fs->dblist == dblist) + dblist->fs->dblist = 0; + dblist->magic = 0; + ext2fs_free_mem(&dblist); +} + diff --git a/e2fsprogs/ext2fs/gen_bitmap.c b/e2fsprogs/ext2fs/gen_bitmap.c new file mode 100644 index 000000000..d0869c919 --- /dev/null +++ b/e2fsprogs/ext2fs/gen_bitmap.c @@ -0,0 +1,49 @@ +/* vi: set sw=4 ts=4: */ +/* + * gen_bitmap.c --- Generic bitmap routines that used to be inlined. + * + * Copyright (C) 2001 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +int ext2fs_mark_generic_bitmap(ext2fs_generic_bitmap bitmap, + __u32 bitno) +{ + if ((bitno < bitmap->start) || (bitno > bitmap->end)) { + ext2fs_warn_bitmap2(bitmap, EXT2FS_MARK_ERROR, bitno); + return 0; + } + return ext2fs_set_bit(bitno - bitmap->start, bitmap->bitmap); +} + +int ext2fs_unmark_generic_bitmap(ext2fs_generic_bitmap bitmap, + blk_t bitno) +{ + if ((bitno < bitmap->start) || (bitno > bitmap->end)) { + ext2fs_warn_bitmap2(bitmap, EXT2FS_UNMARK_ERROR, bitno); + return 0; + } + return ext2fs_clear_bit(bitno - bitmap->start, bitmap->bitmap); +} diff --git a/e2fsprogs/ext2fs/get_pathname.c b/e2fsprogs/ext2fs/get_pathname.c new file mode 100644 index 000000000..a98b2b9e5 --- /dev/null +++ b/e2fsprogs/ext2fs/get_pathname.c @@ -0,0 +1,157 @@ +/* vi: set sw=4 ts=4: */ +/* + * get_pathname.c --- do directry/inode -> name translation + * + * Copyright (C) 1993, 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + * ext2fs_get_pathname(fs, dir, ino, name) + * + * This function translates takes two inode numbers into a + * string, placing the result in . is the containing + * directory inode, and is the inode number itself. If + * is zero, then ext2fs_get_pathname will return pathname + * of the the directory . + * + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct get_pathname_struct { + ext2_ino_t search_ino; + ext2_ino_t parent; + char *name; + errcode_t errcode; +}; + +#ifdef __TURBOC__ +# pragma argsused +#endif +static int get_pathname_proc(struct ext2_dir_entry *dirent, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct get_pathname_struct *gp; + errcode_t retval; + + gp = (struct get_pathname_struct *) priv_data; + + if (((dirent->name_len & 0xFF) == 2) && + !strncmp(dirent->name, "..", 2)) + gp->parent = dirent->inode; + if (dirent->inode == gp->search_ino) { + retval = ext2fs_get_mem((dirent->name_len & 0xFF) + 1, + &gp->name); + if (retval) { + gp->errcode = retval; + return DIRENT_ABORT; + } + strncpy(gp->name, dirent->name, (dirent->name_len & 0xFF)); + gp->name[dirent->name_len & 0xFF] = '\0'; + return DIRENT_ABORT; + } + return 0; +} + +static errcode_t ext2fs_get_pathname_int(ext2_filsys fs, ext2_ino_t dir, + ext2_ino_t ino, int maxdepth, + char *buf, char **name) +{ + struct get_pathname_struct gp; + char *parent_name, *ret; + errcode_t retval; + + if (dir == ino) { + retval = ext2fs_get_mem(2, name); + if (retval) + return retval; + strcpy(*name, (dir == EXT2_ROOT_INO) ? "/" : "."); + return 0; + } + + if (!dir || (maxdepth < 0)) { + retval = ext2fs_get_mem(4, name); + if (retval) + return retval; + strcpy(*name, "..."); + return 0; + } + + gp.search_ino = ino; + gp.parent = 0; + gp.name = 0; + gp.errcode = 0; + + retval = ext2fs_dir_iterate(fs, dir, 0, buf, get_pathname_proc, &gp); + if (retval) + goto cleanup; + if (gp.errcode) { + retval = gp.errcode; + goto cleanup; + } + + retval = ext2fs_get_pathname_int(fs, gp.parent, dir, maxdepth-1, + buf, &parent_name); + if (retval) + goto cleanup; + if (!ino) { + *name = parent_name; + return 0; + } + + if (gp.name) + retval = ext2fs_get_mem(strlen(parent_name)+strlen(gp.name)+2, + &ret); + else + retval = ext2fs_get_mem(strlen(parent_name)+5, &ret); + if (retval) + goto cleanup; + + ret[0] = 0; + if (parent_name[1]) + strcat(ret, parent_name); + strcat(ret, "/"); + if (gp.name) + strcat(ret, gp.name); + else + strcat(ret, "???"); + *name = ret; + ext2fs_free_mem(&parent_name); + retval = 0; + +cleanup: + ext2fs_free_mem(&gp.name); + return retval; +} + +errcode_t ext2fs_get_pathname(ext2_filsys fs, ext2_ino_t dir, ext2_ino_t ino, + char **name) +{ + char *buf; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + if (dir == ino) + ino = 0; + retval = ext2fs_get_pathname_int(fs, dir, ino, 32, buf, name); + ext2fs_free_mem(&buf); + return retval; + +} diff --git a/e2fsprogs/ext2fs/getsectsize.c b/e2fsprogs/ext2fs/getsectsize.c new file mode 100644 index 000000000..163ec65e5 --- /dev/null +++ b/e2fsprogs/ext2fs/getsectsize.c @@ -0,0 +1,58 @@ +/* vi: set sw=4 ts=4: */ +/* + * getsectsize.c --- get the sector size of a device. + * + * Copyright (C) 1995, 1995 Theodore Ts'o. + * Copyright (C) 2003 VMware, Inc. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_LINUX_FD_H +#include +#include +#endif + +#if defined(__linux__) && defined(_IO) && !defined(BLKSSZGET) +#define BLKSSZGET _IO(0x12,104)/* get block device sector size */ +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * Returns the number of blocks in a partition + */ +errcode_t ext2fs_get_device_sectsize(const char *file, int *sectsize) +{ + int fd; + +#ifdef CONFIG_LFS + fd = open64(file, O_RDONLY); +#else + fd = open(file, O_RDONLY); +#endif + if (fd < 0) + return errno; + +#ifdef BLKSSZGET + if (ioctl(fd, BLKSSZGET, sectsize) >= 0) { + close(fd); + return 0; + } +#endif + *sectsize = 0; + close(fd); + return 0; +} diff --git a/e2fsprogs/ext2fs/getsize.c b/e2fsprogs/ext2fs/getsize.c new file mode 100644 index 000000000..516886c1f --- /dev/null +++ b/e2fsprogs/ext2fs/getsize.c @@ -0,0 +1,291 @@ +/* vi: set sw=4 ts=4: */ +/* + * getsize.c --- get the size of a partition. + * + * Copyright (C) 1995, 1995 Theodore Ts'o. + * Copyright (C) 2003 VMware, Inc. + * + * Windows version of ext2fs_get_device_size by Chris Li, VMware. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_LINUX_FD_H +#include +#endif +#ifdef HAVE_SYS_DISKLABEL_H +#include +#endif +#ifdef HAVE_SYS_DISK_H +#ifdef HAVE_SYS_QUEUE_H +#include /* for LIST_HEAD */ +#endif +#include +#endif +#ifdef __linux__ +#include +#endif + +#if defined(__linux__) && defined(_IO) && !defined(BLKGETSIZE) +#define BLKGETSIZE _IO(0x12,96) /* return device size */ +#endif + +#if defined(__linux__) && defined(_IOR) && !defined(BLKGETSIZE64) +#define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size in bytes (u64 *arg) */ +#endif + +#ifdef APPLE_DARWIN +#define BLKGETSIZE DKIOCGETBLOCKCOUNT32 +#endif /* APPLE_DARWIN */ + +#include "ext2_fs.h" +#include "ext2fs.h" + +#if defined(__CYGWIN__) || defined (WIN32) +#include +#include + +#if (_WIN32_WINNT >= 0x0500) +#define HAVE_GET_FILE_SIZE_EX 1 +#endif + +errcode_t ext2fs_get_device_size(const char *file, int blocksize, + blk_t *retblocks) +{ + HANDLE dev; + PARTITION_INFORMATION pi; + DISK_GEOMETRY gi; + DWORD retbytes; +#ifdef HAVE_GET_FILE_SIZE_EX + LARGE_INTEGER filesize; +#else + DWORD filesize; +#endif /* HAVE_GET_FILE_SIZE_EX */ + + dev = CreateFile(file, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE , + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (dev == INVALID_HANDLE_VALUE) + return EBADF; + if (DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO, + &pi, sizeof(PARTITION_INFORMATION), + &pi, sizeof(PARTITION_INFORMATION), + &retbytes, NULL)) { + + *retblocks = pi.PartitionLength.QuadPart / blocksize; + + } else if (DeviceIoControl(dev, IOCTL_DISK_GET_DRIVE_GEOMETRY, + &gi, sizeof(DISK_GEOMETRY), + &gi, sizeof(DISK_GEOMETRY), + &retbytes, NULL)) { + + *retblocks = gi.BytesPerSector * + gi.SectorsPerTrack * + gi.TracksPerCylinder * + gi.Cylinders.QuadPart / blocksize; + +#ifdef HAVE_GET_FILE_SIZE_EX + } else if (GetFileSizeEx(dev, &filesize)) { + *retblocks = filesize.QuadPart / blocksize; + } +#else + } else { + filesize = GetFileSize(dev, NULL); + if (INVALID_FILE_SIZE != filesize) { + *retblocks = filesize / blocksize; + } + } +#endif /* HAVE_GET_FILE_SIZE_EX */ + + CloseHandle(dev); + return 0; +} + +#else + +static int valid_offset (int fd, ext2_loff_t offset) +{ + char ch; + + if (ext2fs_llseek (fd, offset, 0) < 0) + return 0; + if (read (fd, &ch, 1) < 1) + return 0; + return 1; +} + +/* + * Returns the number of blocks in a partition + */ +errcode_t ext2fs_get_device_size(const char *file, int blocksize, + blk_t *retblocks) +{ + int fd; + int valid_blkgetsize64 = 1; +#ifdef __linux__ + struct utsname ut; +#endif + unsigned long long size64; + unsigned long size; + ext2_loff_t high, low; +#ifdef FDGETPRM + struct floppy_struct this_floppy; +#endif +#ifdef HAVE_SYS_DISKLABEL_H + int part; + struct disklabel lab; + struct partition *pp; + char ch; +#endif /* HAVE_SYS_DISKLABEL_H */ + +#ifdef CONFIG_LFS + fd = open64(file, O_RDONLY); +#else + fd = open(file, O_RDONLY); +#endif + if (fd < 0) + return errno; + +#ifdef DKIOCGETBLOCKCOUNT /* For Apple Darwin */ + if (ioctl(fd, DKIOCGETBLOCKCOUNT, &size64) >= 0) { + if ((sizeof(*retblocks) < sizeof(unsigned long long)) + && ((size64 / (blocksize / 512)) > 0xFFFFFFFF)) + return EFBIG; + close(fd); + *retblocks = size64 / (blocksize / 512); + return 0; + } +#endif + +#ifdef BLKGETSIZE64 +#ifdef __linux__ + if ((uname(&ut) == 0) && + ((ut.release[0] == '2') && (ut.release[1] == '.') && + (ut.release[2] < '6') && (ut.release[3] == '.'))) + valid_blkgetsize64 = 0; +#endif + if (valid_blkgetsize64 && + ioctl(fd, BLKGETSIZE64, &size64) >= 0) { + if ((sizeof(*retblocks) < sizeof(unsigned long long)) + && ((size64 / blocksize) > 0xFFFFFFFF)) + return EFBIG; + close(fd); + *retblocks = size64 / blocksize; + return 0; + } +#endif + +#ifdef BLKGETSIZE + if (ioctl(fd, BLKGETSIZE, &size) >= 0) { + close(fd); + *retblocks = size / (blocksize / 512); + return 0; + } +#endif + +#ifdef FDGETPRM + if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) { + close(fd); + *retblocks = this_floppy.size / (blocksize / 512); + return 0; + } +#endif + +#ifdef HAVE_SYS_DISKLABEL_H +#if defined(DIOCGMEDIASIZE) + { + off_t ms; + u_int bs; + if (ioctl(fd, DIOCGMEDIASIZE, &ms) >= 0) { + *retblocks = ms / blocksize; + return 0; + } + } +#elif defined(DIOCGDINFO) + /* old disklabel interface */ + part = strlen(file) - 1; + if (part >= 0) { + ch = file[part]; + if (isdigit(ch)) + part = 0; + else if (ch >= 'a' && ch <= 'h') + part = ch - 'a'; + else + part = -1; + } + if (part >= 0 && (ioctl(fd, DIOCGDINFO, (char *)&lab) >= 0)) { + pp = &lab.d_partitions[part]; + if (pp->p_size) { + close(fd); + *retblocks = pp->p_size / (blocksize / 512); + return 0; + } + } +#endif /* defined(DIOCG*) */ +#endif /* HAVE_SYS_DISKLABEL_H */ + + /* + * OK, we couldn't figure it out by using a specialized ioctl, + * which is generally the best way. So do binary search to + * find the size of the partition. + */ + low = 0; + for (high = 1024; valid_offset (fd, high); high *= 2) + low = high; + while (low < high - 1) + { + const ext2_loff_t mid = (low + high) / 2; + + if (valid_offset (fd, mid)) + low = mid; + else + high = mid; + } + valid_offset (fd, 0); + close(fd); + size64 = low + 1; + if ((sizeof(*retblocks) < sizeof(unsigned long long)) + && ((size64 / blocksize) > 0xFFFFFFFF)) + return EFBIG; + *retblocks = size64 / blocksize; + return 0; +} + +#endif /* WIN32 */ + +#ifdef DEBUG +int main(int argc, char **argv) +{ + blk_t blocks; + int retval; + + if (argc < 2) { + fprintf(stderr, "Usage: %s device\n", argv[0]); + exit(1); + } + + retval = ext2fs_get_device_size(argv[1], 1024, &blocks); + if (retval) { + com_err(argv[0], retval, + "while calling ext2fs_get_device_size"); + exit(1); + } + printf("Device %s has %d 1k blocks.\n", argv[1], blocks); + exit(0); +} +#endif diff --git a/e2fsprogs/ext2fs/icount.c b/e2fsprogs/ext2fs/icount.c new file mode 100644 index 000000000..7ab5f51f4 --- /dev/null +++ b/e2fsprogs/ext2fs/icount.c @@ -0,0 +1,467 @@ +/* vi: set sw=4 ts=4: */ +/* + * icount.c --- an efficient inode count abstraction + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * The data storage strategy used by icount relies on the observation + * that most inode counts are either zero (for non-allocated inodes), + * one (for most files), and only a few that are two or more + * (directories and files that are linked to more than one directory). + * + * Also, e2fsck tends to load the icount data sequentially. + * + * So, we use an inode bitmap to indicate which inodes have a count of + * one, and then use a sorted list to store the counts for inodes + * which are greater than one. + * + * We also use an optional bitmap to indicate which inodes are already + * in the sorted list, to speed up the use of this abstraction by + * e2fsck's pass 2. Pass 2 increments inode counts as it finds them, + * so this extra bitmap avoids searching the sorted list to see if a + * particular inode is on the sorted list already. + */ + +struct ext2_icount_el { + ext2_ino_t ino; + __u16 count; +}; + +struct ext2_icount { + errcode_t magic; + ext2fs_inode_bitmap single; + ext2fs_inode_bitmap multiple; + ext2_ino_t count; + ext2_ino_t size; + ext2_ino_t num_inodes; + ext2_ino_t cursor; + struct ext2_icount_el *list; +}; + +void ext2fs_free_icount(ext2_icount_t icount) +{ + if (!icount) + return; + + icount->magic = 0; + ext2fs_free_mem(&icount->list); + ext2fs_free_inode_bitmap(icount->single); + ext2fs_free_inode_bitmap(icount->multiple); + ext2fs_free_mem(&icount); +} + +errcode_t ext2fs_create_icount2(ext2_filsys fs, int flags, unsigned int size, + ext2_icount_t hint, ext2_icount_t *ret) +{ + ext2_icount_t icount; + errcode_t retval; + size_t bytes; + ext2_ino_t i; + + if (hint) { + EXT2_CHECK_MAGIC(hint, EXT2_ET_MAGIC_ICOUNT); + if (hint->size > size) + size = (size_t) hint->size; + } + + retval = ext2fs_get_mem(sizeof(struct ext2_icount), &icount); + if (retval) + return retval; + memset(icount, 0, sizeof(struct ext2_icount)); + + retval = ext2fs_allocate_inode_bitmap(fs, 0, + &icount->single); + if (retval) + goto errout; + + if (flags & EXT2_ICOUNT_OPT_INCREMENT) { + retval = ext2fs_allocate_inode_bitmap(fs, 0, + &icount->multiple); + if (retval) + goto errout; + } else + icount->multiple = 0; + + if (size) { + icount->size = size; + } else { + /* + * Figure out how many special case inode counts we will + * have. We know we will need one for each directory; + * we also need to reserve some extra room for file links + */ + retval = ext2fs_get_num_dirs(fs, &icount->size); + if (retval) + goto errout; + icount->size += fs->super->s_inodes_count / 50; + } + + bytes = (size_t) (icount->size * sizeof(struct ext2_icount_el)); + retval = ext2fs_get_mem(bytes, &icount->list); + if (retval) + goto errout; + memset(icount->list, 0, bytes); + + icount->magic = EXT2_ET_MAGIC_ICOUNT; + icount->count = 0; + icount->cursor = 0; + icount->num_inodes = fs->super->s_inodes_count; + + /* + * Populate the sorted list with those entries which were + * found in the hint icount (since those are ones which will + * likely need to be in the sorted list this time around). + */ + if (hint) { + for (i=0; i < hint->count; i++) + icount->list[i].ino = hint->list[i].ino; + icount->count = hint->count; + } + + *ret = icount; + return 0; + +errout: + ext2fs_free_icount(icount); + return retval; +} + +errcode_t ext2fs_create_icount(ext2_filsys fs, int flags, + unsigned int size, + ext2_icount_t *ret) +{ + return ext2fs_create_icount2(fs, flags, size, 0, ret); +} + +/* + * insert_icount_el() --- Insert a new entry into the sorted list at a + * specified position. + */ +static struct ext2_icount_el *insert_icount_el(ext2_icount_t icount, + ext2_ino_t ino, int pos) +{ + struct ext2_icount_el *el; + errcode_t retval; + ext2_ino_t new_size = 0; + int num; + + if (icount->count >= icount->size) { + if (icount->count) { + new_size = icount->list[(unsigned)icount->count-1].ino; + new_size = (ext2_ino_t) (icount->count * + ((float) icount->num_inodes / new_size)); + } + if (new_size < (icount->size + 100)) + new_size = icount->size + 100; + retval = ext2fs_resize_mem((size_t) icount->size * + sizeof(struct ext2_icount_el), + (size_t) new_size * + sizeof(struct ext2_icount_el), + &icount->list); + if (retval) + return 0; + icount->size = new_size; + } + num = (int) icount->count - pos; + if (num < 0) + return 0; /* should never happen */ + if (num) { + memmove(&icount->list[pos+1], &icount->list[pos], + sizeof(struct ext2_icount_el) * num); + } + icount->count++; + el = &icount->list[pos]; + el->count = 0; + el->ino = ino; + return el; +} + +/* + * get_icount_el() --- given an inode number, try to find icount + * information in the sorted list. If the create flag is set, + * and we can't find an entry, create one in the sorted list. + */ +static struct ext2_icount_el *get_icount_el(ext2_icount_t icount, + ext2_ino_t ino, int create) +{ + float range; + int low, high, mid; + ext2_ino_t lowval, highval; + + if (!icount || !icount->list) + return 0; + + if (create && ((icount->count == 0) || + (ino > icount->list[(unsigned)icount->count-1].ino))) { + return insert_icount_el(icount, ino, (unsigned) icount->count); + } + if (icount->count == 0) + return 0; + + if (icount->cursor >= icount->count) + icount->cursor = 0; + if (ino == icount->list[icount->cursor].ino) + return &icount->list[icount->cursor++]; + low = 0; + high = (int) icount->count-1; + while (low <= high) { + if (low == high) + mid = low; + else { + /* Interpolate for efficiency */ + lowval = icount->list[low].ino; + highval = icount->list[high].ino; + + if (ino < lowval) + range = 0; + else if (ino > highval) + range = 1; + else + range = ((float) (ino - lowval)) / + (highval - lowval); + mid = low + ((int) (range * (high-low))); + } + if (ino == icount->list[mid].ino) { + icount->cursor = mid+1; + return &icount->list[mid]; + } + if (ino < icount->list[mid].ino) + high = mid-1; + else + low = mid+1; + } + /* + * If we need to create a new entry, it should be right at + * low (where high will be left at low-1). + */ + if (create) + return insert_icount_el(icount, ino, low); + return 0; +} + +errcode_t ext2fs_icount_validate(ext2_icount_t icount, FILE *out) +{ + errcode_t ret = 0; + unsigned int i; + const char *bad = "bad icount"; + + EXT2_CHECK_MAGIC(icount, EXT2_ET_MAGIC_ICOUNT); + + if (icount->count > icount->size) { + fprintf(out, "%s: count > size\n", bad); + return EXT2_ET_INVALID_ARGUMENT; + } + for (i=1; i < icount->count; i++) { + if (icount->list[i-1].ino >= icount->list[i].ino) { + fprintf(out, "%s: list[%d].ino=%u, list[%d].ino=%u\n", + bad, i-1, icount->list[i-1].ino, + i, icount->list[i].ino); + ret = EXT2_ET_INVALID_ARGUMENT; + } + } + return ret; +} + +errcode_t ext2fs_icount_fetch(ext2_icount_t icount, ext2_ino_t ino, __u16 *ret) +{ + struct ext2_icount_el *el; + + EXT2_CHECK_MAGIC(icount, EXT2_ET_MAGIC_ICOUNT); + + if (!ino || (ino > icount->num_inodes)) + return EXT2_ET_INVALID_ARGUMENT; + + if (ext2fs_test_inode_bitmap(icount->single, ino)) { + *ret = 1; + return 0; + } + if (icount->multiple && + !ext2fs_test_inode_bitmap(icount->multiple, ino)) { + *ret = 0; + return 0; + } + el = get_icount_el(icount, ino, 0); + if (!el) { + *ret = 0; + return 0; + } + *ret = el->count; + return 0; +} + +errcode_t ext2fs_icount_increment(ext2_icount_t icount, ext2_ino_t ino, + __u16 *ret) +{ + struct ext2_icount_el *el; + + EXT2_CHECK_MAGIC(icount, EXT2_ET_MAGIC_ICOUNT); + + if (!ino || (ino > icount->num_inodes)) + return EXT2_ET_INVALID_ARGUMENT; + + if (ext2fs_test_inode_bitmap(icount->single, ino)) { + /* + * If the existing count is 1, then we know there is + * no entry in the list. + */ + el = get_icount_el(icount, ino, 1); + if (!el) + return EXT2_ET_NO_MEMORY; + ext2fs_unmark_inode_bitmap(icount->single, ino); + el->count = 2; + } else if (icount->multiple) { + /* + * The count is either zero or greater than 1; if the + * inode is set in icount->multiple, then there should + * be an entry in the list, so find it using + * get_icount_el(). + */ + if (ext2fs_test_inode_bitmap(icount->multiple, ino)) { + el = get_icount_el(icount, ino, 1); + if (!el) + return EXT2_ET_NO_MEMORY; + el->count++; + } else { + /* + * The count was zero; mark the single bitmap + * and return. + */ + zero_count: + ext2fs_mark_inode_bitmap(icount->single, ino); + if (ret) + *ret = 1; + return 0; + } + } else { + /* + * The count is either zero or greater than 1; try to + * find an entry in the list to determine which. + */ + el = get_icount_el(icount, ino, 0); + if (!el) { + /* No entry means the count was zero */ + goto zero_count; + } + el = get_icount_el(icount, ino, 1); + if (!el) + return EXT2_ET_NO_MEMORY; + el->count++; + } + if (icount->multiple) + ext2fs_mark_inode_bitmap(icount->multiple, ino); + if (ret) + *ret = el->count; + return 0; +} + +errcode_t ext2fs_icount_decrement(ext2_icount_t icount, ext2_ino_t ino, + __u16 *ret) +{ + struct ext2_icount_el *el; + + if (!ino || (ino > icount->num_inodes)) + return EXT2_ET_INVALID_ARGUMENT; + + EXT2_CHECK_MAGIC(icount, EXT2_ET_MAGIC_ICOUNT); + + if (ext2fs_test_inode_bitmap(icount->single, ino)) { + ext2fs_unmark_inode_bitmap(icount->single, ino); + if (icount->multiple) + ext2fs_unmark_inode_bitmap(icount->multiple, ino); + else { + el = get_icount_el(icount, ino, 0); + if (el) + el->count = 0; + } + if (ret) + *ret = 0; + return 0; + } + + if (icount->multiple && + !ext2fs_test_inode_bitmap(icount->multiple, ino)) + return EXT2_ET_INVALID_ARGUMENT; + + el = get_icount_el(icount, ino, 0); + if (!el || el->count == 0) + return EXT2_ET_INVALID_ARGUMENT; + + el->count--; + if (el->count == 1) + ext2fs_mark_inode_bitmap(icount->single, ino); + if ((el->count == 0) && icount->multiple) + ext2fs_unmark_inode_bitmap(icount->multiple, ino); + + if (ret) + *ret = el->count; + return 0; +} + +errcode_t ext2fs_icount_store(ext2_icount_t icount, ext2_ino_t ino, + __u16 count) +{ + struct ext2_icount_el *el; + + if (!ino || (ino > icount->num_inodes)) + return EXT2_ET_INVALID_ARGUMENT; + + EXT2_CHECK_MAGIC(icount, EXT2_ET_MAGIC_ICOUNT); + + if (count == 1) { + ext2fs_mark_inode_bitmap(icount->single, ino); + if (icount->multiple) + ext2fs_unmark_inode_bitmap(icount->multiple, ino); + return 0; + } + if (count == 0) { + ext2fs_unmark_inode_bitmap(icount->single, ino); + if (icount->multiple) { + /* + * If the icount->multiple bitmap is enabled, + * we can just clear both bitmaps and we're done + */ + ext2fs_unmark_inode_bitmap(icount->multiple, ino); + } else { + el = get_icount_el(icount, ino, 0); + if (el) + el->count = 0; + } + return 0; + } + + /* + * Get the icount element + */ + el = get_icount_el(icount, ino, 1); + if (!el) + return EXT2_ET_NO_MEMORY; + el->count = count; + ext2fs_unmark_inode_bitmap(icount->single, ino); + if (icount->multiple) + ext2fs_mark_inode_bitmap(icount->multiple, ino); + return 0; +} + +ext2_ino_t ext2fs_get_icount_size(ext2_icount_t icount) +{ + if (!icount || icount->magic != EXT2_ET_MAGIC_ICOUNT) + return 0; + + return icount->size; +} diff --git a/e2fsprogs/ext2fs/imager.c b/e2fsprogs/ext2fs/imager.c new file mode 100644 index 000000000..e82321efa --- /dev/null +++ b/e2fsprogs/ext2fs/imager.c @@ -0,0 +1,377 @@ +/* vi: set sw=4 ts=4: */ +/* + * image.c --- writes out the critical parts of the filesystem as a + * flat file. + * + * Copyright (C) 2000 Theodore Ts'o. + * + * Note: this uses the POSIX IO interfaces, unlike most of the other + * functions in this library. So sue me. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +#ifndef HAVE_TYPE_SSIZE_T +typedef int ssize_t; +#endif + +/* + * This function returns 1 if the specified block is all zeros + */ +static int check_zero_block(char *buf, int blocksize) +{ + char *cp = buf; + int left = blocksize; + + while (left > 0) { + if (*cp++) + return 0; + left--; + } + return 1; +} + +/* + * Write the inode table out as a single block. + */ +#define BUF_BLOCKS 32 + +errcode_t ext2fs_image_inode_write(ext2_filsys fs, int fd, int flags) +{ + unsigned int group, left, c, d; + char *buf, *cp; + blk_t blk; + ssize_t actual; + errcode_t retval; + + buf = xmalloc(fs->blocksize * BUF_BLOCKS); + + for (group = 0; group < fs->group_desc_count; group++) { + blk = fs->group_desc[(unsigned)group].bg_inode_table; + if (!blk) + return EXT2_ET_MISSING_INODE_TABLE; + left = fs->inode_blocks_per_group; + while (left) { + c = BUF_BLOCKS; + if (c > left) + c = left; + retval = io_channel_read_blk(fs->io, blk, c, buf); + if (retval) + goto errout; + cp = buf; + while (c) { + if (!(flags & IMAGER_FLAG_SPARSEWRITE)) { + d = c; + goto skip_sparse; + } + /* Skip zero blocks */ + if (check_zero_block(cp, fs->blocksize)) { + c--; + blk++; + left--; + cp += fs->blocksize; + lseek(fd, fs->blocksize, SEEK_CUR); + continue; + } + /* Find non-zero blocks */ + for (d=1; d < c; d++) { + if (check_zero_block(cp + d*fs->blocksize, fs->blocksize)) + break; + } + skip_sparse: + actual = write(fd, cp, fs->blocksize * d); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != (ssize_t) (fs->blocksize * d)) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + blk += d; + left -= d; + cp += fs->blocksize * d; + c -= d; + } + } + } + retval = 0; + +errout: + free(buf); + return retval; +} + +/* + * Read in the inode table and stuff it into place + */ +errcode_t ext2fs_image_inode_read(ext2_filsys fs, int fd, + int flags EXT2FS_ATTR((unused))) +{ + unsigned int group, c, left; + char *buf; + blk_t blk; + ssize_t actual; + errcode_t retval; + + buf = xmalloc(fs->blocksize * BUF_BLOCKS); + + for (group = 0; group < fs->group_desc_count; group++) { + blk = fs->group_desc[(unsigned)group].bg_inode_table; + if (!blk) { + retval = EXT2_ET_MISSING_INODE_TABLE; + goto errout; + } + left = fs->inode_blocks_per_group; + while (left) { + c = BUF_BLOCKS; + if (c > left) + c = left; + actual = read(fd, buf, fs->blocksize * c); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != (ssize_t) (fs->blocksize * c)) { + retval = EXT2_ET_SHORT_READ; + goto errout; + } + retval = io_channel_write_blk(fs->io, blk, c, buf); + if (retval) + goto errout; + + blk += c; + left -= c; + } + } + retval = ext2fs_flush_icache(fs); + +errout: + free(buf); + return retval; +} + +/* + * Write out superblock and group descriptors + */ +errcode_t ext2fs_image_super_write(ext2_filsys fs, int fd, + int flags EXT2FS_ATTR((unused))) +{ + char *buf, *cp; + ssize_t actual; + errcode_t retval; + + buf = xmalloc(fs->blocksize); + + /* + * Write out the superblock + */ + memset(buf, 0, fs->blocksize); + memcpy(buf, fs->super, SUPERBLOCK_SIZE); + actual = write(fd, buf, fs->blocksize); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != (ssize_t) fs->blocksize) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + + /* + * Now write out the block group descriptors + */ + cp = (char *) fs->group_desc; + actual = write(fd, cp, fs->blocksize * fs->desc_blocks); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != (ssize_t) (fs->blocksize * fs->desc_blocks)) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + + retval = 0; + +errout: + free(buf); + return retval; +} + +/* + * Read the superblock and group descriptors and overwrite them. + */ +errcode_t ext2fs_image_super_read(ext2_filsys fs, int fd, + int flags EXT2FS_ATTR((unused))) +{ + char *buf; + ssize_t actual, size; + errcode_t retval; + + size = fs->blocksize * (fs->group_desc_count + 1); + buf = xmalloc(size); + + /* + * Read it all in. + */ + actual = read(fd, buf, size); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != size) { + retval = EXT2_ET_SHORT_READ; + goto errout; + } + + /* + * Now copy in the superblock and group descriptors + */ + memcpy(fs->super, buf, SUPERBLOCK_SIZE); + + memcpy(fs->group_desc, buf + fs->blocksize, + fs->blocksize * fs->group_desc_count); + + retval = 0; + +errout: + free(buf); + return retval; +} + +/* + * Write the block/inode bitmaps. + */ +errcode_t ext2fs_image_bitmap_write(ext2_filsys fs, int fd, int flags) +{ + char *ptr; + int c, size; + char zero_buf[1024]; + ssize_t actual; + errcode_t retval; + + if (flags & IMAGER_FLAG_INODEMAP) { + if (!fs->inode_map) { + retval = ext2fs_read_inode_bitmap(fs); + if (retval) + return retval; + } + ptr = fs->inode_map->bitmap; + size = (EXT2_INODES_PER_GROUP(fs->super) / 8); + } else { + if (!fs->block_map) { + retval = ext2fs_read_block_bitmap(fs); + if (retval) + return retval; + } + ptr = fs->block_map->bitmap; + size = EXT2_BLOCKS_PER_GROUP(fs->super) / 8; + } + size = size * fs->group_desc_count; + + actual = write(fd, ptr, size); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != size) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + size = size % fs->blocksize; + memset(zero_buf, 0, sizeof(zero_buf)); + if (size) { + size = fs->blocksize - size; + while (size) { + c = size; + if (c > (int) sizeof(zero_buf)) + c = sizeof(zero_buf); + actual = write(fd, zero_buf, c); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != c) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + size -= c; + } + } + retval = 0; +errout: + return retval; +} + + +/* + * Read the block/inode bitmaps. + */ +errcode_t ext2fs_image_bitmap_read(ext2_filsys fs, int fd, int flags) +{ + char *ptr, *buf = 0; + int size; + ssize_t actual; + errcode_t retval; + + if (flags & IMAGER_FLAG_INODEMAP) { + if (!fs->inode_map) { + retval = ext2fs_read_inode_bitmap(fs); + if (retval) + return retval; + } + ptr = fs->inode_map->bitmap; + size = (EXT2_INODES_PER_GROUP(fs->super) / 8); + } else { + if (!fs->block_map) { + retval = ext2fs_read_block_bitmap(fs); + if (retval) + return retval; + } + ptr = fs->block_map->bitmap; + size = EXT2_BLOCKS_PER_GROUP(fs->super) / 8; + } + size = size * fs->group_desc_count; + + buf = xmalloc(size); + + actual = read(fd, buf, size); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != size) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + memcpy(ptr, buf, size); + + retval = 0; +errout: + free(buf); + return retval; +} diff --git a/e2fsprogs/ext2fs/ind_block.c b/e2fsprogs/ext2fs/ind_block.c new file mode 100644 index 000000000..c86a1c59a --- /dev/null +++ b/e2fsprogs/ext2fs/ind_block.c @@ -0,0 +1,71 @@ +/* vi: set sw=4 ts=4: */ +/* + * ind_block.c --- indirect block I/O routines + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, + * 2001, 2002, 2003, 2004, 2005 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_read_ind_block(ext2_filsys fs, blk_t blk, void *buf) +{ + errcode_t retval; +#if BB_BIG_ENDIAN + blk_t *block_nr; + int i; + int limit = fs->blocksize >> 2; +#endif + + if ((fs->flags & EXT2_FLAG_IMAGE_FILE) && + (fs->io != fs->image_io)) + memset(buf, 0, fs->blocksize); + else { + retval = io_channel_read_blk(fs->io, blk, 1, buf); + if (retval) + return retval; + } +#if BB_BIG_ENDIAN + if (fs->flags & (EXT2_FLAG_SWAP_BYTES | EXT2_FLAG_SWAP_BYTES_READ)) { + block_nr = (blk_t *) buf; + for (i = 0; i < limit; i++, block_nr++) + *block_nr = ext2fs_swab32(*block_nr); + } +#endif + return 0; +} + +errcode_t ext2fs_write_ind_block(ext2_filsys fs, blk_t blk, void *buf) +{ +#if BB_BIG_ENDIAN + blk_t *block_nr; + int i; + int limit = fs->blocksize >> 2; +#endif + + if (fs->flags & EXT2_FLAG_IMAGE_FILE) + return 0; + +#if BB_BIG_ENDIAN + if (fs->flags & (EXT2_FLAG_SWAP_BYTES | EXT2_FLAG_SWAP_BYTES_WRITE)) { + block_nr = (blk_t *) buf; + for (i = 0; i < limit; i++, block_nr++) + *block_nr = ext2fs_swab32(*block_nr); + } +#endif + return io_channel_write_blk(fs->io, blk, 1, buf); +} + + diff --git a/e2fsprogs/ext2fs/initialize.c b/e2fsprogs/ext2fs/initialize.c new file mode 100644 index 000000000..ef1d34379 --- /dev/null +++ b/e2fsprogs/ext2fs/initialize.c @@ -0,0 +1,388 @@ +/* vi: set sw=4 ts=4: */ +/* + * initialize.c --- initialize a filesystem handle given superblock + * parameters. Used by mke2fs when initializing a filesystem. + * + * Copyright (C) 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +#if defined(__linux__) && defined(EXT2_OS_LINUX) +#define CREATOR_OS EXT2_OS_LINUX +#else +#if defined(__GNU__) && defined(EXT2_OS_HURD) +#define CREATOR_OS EXT2_OS_HURD +#else +#if defined(__FreeBSD__) && defined(EXT2_OS_FREEBSD) +#define CREATOR_OS EXT2_OS_FREEBSD +#else +#if defined(LITES) && defined(EXT2_OS_LITES) +#define CREATOR_OS EXT2_OS_LITES +#else +#define CREATOR_OS EXT2_OS_LINUX /* by default */ +#endif /* defined(LITES) && defined(EXT2_OS_LITES) */ +#endif /* defined(__FreeBSD__) && defined(EXT2_OS_FREEBSD) */ +#endif /* defined(__GNU__) && defined(EXT2_OS_HURD) */ +#endif /* defined(__linux__) && defined(EXT2_OS_LINUX) */ + +/* + * Note we override the kernel include file's idea of what the default + * check interval (never) should be. It's a good idea to check at + * least *occasionally*, specially since servers will never rarely get + * to reboot, since Linux is so robust these days. :-) + * + * 180 days (six months) seems like a good value. + */ +#ifdef EXT2_DFL_CHECKINTERVAL +#undef EXT2_DFL_CHECKINTERVAL +#endif +#define EXT2_DFL_CHECKINTERVAL (86400L * 180L) + +/* + * Calculate the number of GDT blocks to reserve for online filesystem growth. + * The absolute maximum number of GDT blocks we can reserve is determined by + * the number of block pointers that can fit into a single block. + */ +static int calc_reserved_gdt_blocks(ext2_filsys fs) +{ + struct ext2_super_block *sb = fs->super; + unsigned long bpg = sb->s_blocks_per_group; + unsigned int gdpb = fs->blocksize / sizeof(struct ext2_group_desc); + unsigned long max_blocks = 0xffffffff; + unsigned long rsv_groups; + int rsv_gdb; + + /* We set it at 1024x the current filesystem size, or + * the upper block count limit (2^32), whichever is lower. + */ + if (sb->s_blocks_count < max_blocks / 1024) + max_blocks = sb->s_blocks_count * 1024; + rsv_groups = (max_blocks - sb->s_first_data_block + bpg - 1) / bpg; + rsv_gdb = (rsv_groups + gdpb - 1) / gdpb - fs->desc_blocks; + if (rsv_gdb > EXT2_ADDR_PER_BLOCK(sb)) + rsv_gdb = EXT2_ADDR_PER_BLOCK(sb); +#ifdef RES_GDT_DEBUG + printf("max_blocks %lu, rsv_groups = %lu, rsv_gdb = %lu\n", + max_blocks, rsv_groups, rsv_gdb); +#endif + + return rsv_gdb; +} + +errcode_t ext2fs_initialize(const char *name, int flags, + struct ext2_super_block *param, + io_manager manager, ext2_filsys *ret_fs) +{ + ext2_filsys fs; + errcode_t retval; + struct ext2_super_block *super; + int frags_per_block; + unsigned int rem; + unsigned int overhead = 0; + blk_t group_block; + unsigned int ipg; + dgrp_t i; + blk_t numblocks; + int rsv_gdt; + char *buf; + + if (!param || !param->s_blocks_count) + return EXT2_ET_INVALID_ARGUMENT; + + retval = ext2fs_get_mem(sizeof(struct struct_ext2_filsys), &fs); + if (retval) + return retval; + + memset(fs, 0, sizeof(struct struct_ext2_filsys)); + fs->magic = EXT2_ET_MAGIC_EXT2FS_FILSYS; + fs->flags = flags | EXT2_FLAG_RW; + fs->umask = 022; +#ifdef WORDS_BIGENDIAN + fs->flags |= EXT2_FLAG_SWAP_BYTES; +#endif + retval = manager->open(name, IO_FLAG_RW, &fs->io); + if (retval) + goto cleanup; + fs->image_io = fs->io; + fs->io->app_data = fs; + retval = ext2fs_get_mem(strlen(name)+1, &fs->device_name); + if (retval) + goto cleanup; + + strcpy(fs->device_name, name); + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &super); + if (retval) + goto cleanup; + fs->super = super; + + memset(super, 0, SUPERBLOCK_SIZE); + +#define set_field(field, default) (super->field = param->field ? \ + param->field : (default)) + + super->s_magic = EXT2_SUPER_MAGIC; + super->s_state = EXT2_VALID_FS; + + set_field(s_log_block_size, 0); /* default blocksize: 1024 bytes */ + set_field(s_log_frag_size, 0); /* default fragsize: 1024 bytes */ + set_field(s_first_data_block, super->s_log_block_size ? 0 : 1); + set_field(s_max_mnt_count, EXT2_DFL_MAX_MNT_COUNT); + set_field(s_errors, EXT2_ERRORS_DEFAULT); + set_field(s_feature_compat, 0); + set_field(s_feature_incompat, 0); + set_field(s_feature_ro_compat, 0); + set_field(s_first_meta_bg, 0); + if (super->s_feature_incompat & ~EXT2_LIB_FEATURE_INCOMPAT_SUPP) { + retval = EXT2_ET_UNSUPP_FEATURE; + goto cleanup; + } + if (super->s_feature_ro_compat & ~EXT2_LIB_FEATURE_RO_COMPAT_SUPP) { + retval = EXT2_ET_RO_UNSUPP_FEATURE; + goto cleanup; + } + + set_field(s_rev_level, EXT2_GOOD_OLD_REV); + if (super->s_rev_level >= EXT2_DYNAMIC_REV) { + set_field(s_first_ino, EXT2_GOOD_OLD_FIRST_INO); + set_field(s_inode_size, EXT2_GOOD_OLD_INODE_SIZE); + } + + set_field(s_checkinterval, EXT2_DFL_CHECKINTERVAL); + super->s_mkfs_time = super->s_lastcheck = time(NULL); + + super->s_creator_os = CREATOR_OS; + + fs->blocksize = EXT2_BLOCK_SIZE(super); + fs->fragsize = EXT2_FRAG_SIZE(super); + frags_per_block = fs->blocksize / fs->fragsize; + + /* default: (fs->blocksize*8) blocks/group, up to 2^16 (GDT limit) */ + set_field(s_blocks_per_group, fs->blocksize * 8); + if (super->s_blocks_per_group > EXT2_MAX_BLOCKS_PER_GROUP(super)) + super->s_blocks_per_group = EXT2_MAX_BLOCKS_PER_GROUP(super); + super->s_frags_per_group = super->s_blocks_per_group * frags_per_block; + + super->s_blocks_count = param->s_blocks_count; + super->s_r_blocks_count = param->s_r_blocks_count; + if (super->s_r_blocks_count >= param->s_blocks_count) { + retval = EXT2_ET_INVALID_ARGUMENT; + goto cleanup; + } + + /* + * If we're creating an external journal device, we don't need + * to bother with the rest. + */ + if (super->s_feature_incompat & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { + fs->group_desc_count = 0; + ext2fs_mark_super_dirty(fs); + *ret_fs = fs; + return 0; + } + +retry: + fs->group_desc_count = (super->s_blocks_count - + super->s_first_data_block + + EXT2_BLOCKS_PER_GROUP(super) - 1) + / EXT2_BLOCKS_PER_GROUP(super); + if (fs->group_desc_count == 0) { + retval = EXT2_ET_TOOSMALL; + goto cleanup; + } + fs->desc_blocks = (fs->group_desc_count + + EXT2_DESC_PER_BLOCK(super) - 1) + / EXT2_DESC_PER_BLOCK(super); + + i = fs->blocksize >= 4096 ? 1 : 4096 / fs->blocksize; + set_field(s_inodes_count, super->s_blocks_count / i); + + /* + * Make sure we have at least EXT2_FIRST_INO + 1 inodes, so + * that we have enough inodes for the filesystem(!) + */ + if (super->s_inodes_count < EXT2_FIRST_INODE(super)+1) + super->s_inodes_count = EXT2_FIRST_INODE(super)+1; + + /* + * There should be at least as many inodes as the user + * requested. Figure out how many inodes per group that + * should be. But make sure that we don't allocate more than + * one bitmap's worth of inodes each group. + */ + ipg = (super->s_inodes_count + fs->group_desc_count - 1) / + fs->group_desc_count; + if (ipg > fs->blocksize * 8) { + if (super->s_blocks_per_group >= 256) { + /* Try again with slightly different parameters */ + super->s_blocks_per_group -= 8; + super->s_blocks_count = param->s_blocks_count; + super->s_frags_per_group = super->s_blocks_per_group * + frags_per_block; + goto retry; + } else + return EXT2_ET_TOO_MANY_INODES; + } + + if (ipg > (unsigned) EXT2_MAX_INODES_PER_GROUP(super)) + ipg = EXT2_MAX_INODES_PER_GROUP(super); + + super->s_inodes_per_group = ipg; + if (super->s_inodes_count > ipg * fs->group_desc_count) + super->s_inodes_count = ipg * fs->group_desc_count; + + /* + * Make sure the number of inodes per group completely fills + * the inode table blocks in the descriptor. If not, add some + * additional inodes/group. Waste not, want not... + */ + fs->inode_blocks_per_group = (((super->s_inodes_per_group * + EXT2_INODE_SIZE(super)) + + EXT2_BLOCK_SIZE(super) - 1) / + EXT2_BLOCK_SIZE(super)); + super->s_inodes_per_group = ((fs->inode_blocks_per_group * + EXT2_BLOCK_SIZE(super)) / + EXT2_INODE_SIZE(super)); + /* + * Finally, make sure the number of inodes per group is a + * multiple of 8. This is needed to simplify the bitmap + * splicing code. + */ + super->s_inodes_per_group &= ~7; + fs->inode_blocks_per_group = (((super->s_inodes_per_group * + EXT2_INODE_SIZE(super)) + + EXT2_BLOCK_SIZE(super) - 1) / + EXT2_BLOCK_SIZE(super)); + + /* + * adjust inode count to reflect the adjusted inodes_per_group + */ + super->s_inodes_count = super->s_inodes_per_group * + fs->group_desc_count; + super->s_free_inodes_count = super->s_inodes_count; + + /* + * check the number of reserved group descriptor table blocks + */ + if (super->s_feature_compat & EXT2_FEATURE_COMPAT_RESIZE_INODE) + rsv_gdt = calc_reserved_gdt_blocks(fs); + else + rsv_gdt = 0; + set_field(s_reserved_gdt_blocks, rsv_gdt); + if (super->s_reserved_gdt_blocks > EXT2_ADDR_PER_BLOCK(super)) { + retval = EXT2_ET_RES_GDT_BLOCKS; + goto cleanup; + } + + /* + * Overhead is the number of bookkeeping blocks per group. It + * includes the superblock backup, the group descriptor + * backups, the inode bitmap, the block bitmap, and the inode + * table. + */ + + overhead = (int) (2 + fs->inode_blocks_per_group); + + if (ext2fs_bg_has_super(fs, fs->group_desc_count - 1)) + overhead += 1 + fs->desc_blocks + super->s_reserved_gdt_blocks; + + /* This can only happen if the user requested too many inodes */ + if (overhead > super->s_blocks_per_group) + return EXT2_ET_TOO_MANY_INODES; + + /* + * See if the last group is big enough to support the + * necessary data structures. If not, we need to get rid of + * it. + */ + rem = ((super->s_blocks_count - super->s_first_data_block) % + super->s_blocks_per_group); + if ((fs->group_desc_count == 1) && rem && (rem < overhead)) + return EXT2_ET_TOOSMALL; + if (rem && (rem < overhead+50)) { + super->s_blocks_count -= rem; + goto retry; + } + + /* + * At this point we know how big the filesystem will be. So + * we can do any and all allocations that depend on the block + * count. + */ + + retval = ext2fs_get_mem(strlen(fs->device_name) + 80, &buf); + if (retval) + goto cleanup; + + sprintf(buf, "block bitmap for %s", fs->device_name); + retval = ext2fs_allocate_block_bitmap(fs, buf, &fs->block_map); + if (retval) + goto cleanup; + + sprintf(buf, "inode bitmap for %s", fs->device_name); + retval = ext2fs_allocate_inode_bitmap(fs, buf, &fs->inode_map); + if (retval) + goto cleanup; + + ext2fs_free_mem(&buf); + + retval = ext2fs_get_mem((size_t) fs->desc_blocks * fs->blocksize, + &fs->group_desc); + if (retval) + goto cleanup; + + memset(fs->group_desc, 0, (size_t) fs->desc_blocks * fs->blocksize); + + /* + * Reserve the superblock and group descriptors for each + * group, and fill in the correct group statistics for group. + * Note that although the block bitmap, inode bitmap, and + * inode table have not been allocated (and in fact won't be + * by this routine), they are accounted for nevertheless. + */ + group_block = super->s_first_data_block; + super->s_free_blocks_count = 0; + for (i = 0; i < fs->group_desc_count; i++) { + numblocks = ext2fs_reserve_super_and_bgd(fs, i, fs->block_map); + + super->s_free_blocks_count += numblocks; + fs->group_desc[i].bg_free_blocks_count = numblocks; + fs->group_desc[i].bg_free_inodes_count = + fs->super->s_inodes_per_group; + fs->group_desc[i].bg_used_dirs_count = 0; + + group_block += super->s_blocks_per_group; + } + + ext2fs_mark_super_dirty(fs); + ext2fs_mark_bb_dirty(fs); + ext2fs_mark_ib_dirty(fs); + + io_channel_set_blksize(fs->io, fs->blocksize); + + *ret_fs = fs; + return 0; +cleanup: + ext2fs_free(fs); + return retval; +} diff --git a/e2fsprogs/ext2fs/inline.c b/e2fsprogs/ext2fs/inline.c new file mode 100644 index 000000000..9b620a772 --- /dev/null +++ b/e2fsprogs/ext2fs/inline.c @@ -0,0 +1,33 @@ +/* vi: set sw=4 ts=4: */ +/* + * inline.c --- Includes the inlined functions defined in the header + * files as standalone functions, in case the application program + * is compiled with inlining turned off. + * + * Copyright (C) 1993, 1994 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#define INCLUDE_INLINE_FUNCS +#include "ext2fs.h" + diff --git a/e2fsprogs/ext2fs/inode.c b/e2fsprogs/ext2fs/inode.c new file mode 100644 index 000000000..2ff9fe6ca --- /dev/null +++ b/e2fsprogs/ext2fs/inode.c @@ -0,0 +1,768 @@ +/* vi: set sw=4 ts=4: */ +/* + * inode.c --- utility routines to read and write inodes + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" +#include "e2image.h" + +struct ext2_struct_inode_scan { + errcode_t magic; + ext2_filsys fs; + ext2_ino_t current_inode; + blk_t current_block; + dgrp_t current_group; + ext2_ino_t inodes_left; + blk_t blocks_left; + dgrp_t groups_left; + blk_t inode_buffer_blocks; + char * inode_buffer; + int inode_size; + char * ptr; + int bytes_left; + char *temp_buffer; + errcode_t (*done_group)(ext2_filsys fs, + dgrp_t group, + void * priv_data); + void * done_group_data; + int bad_block_ptr; + int scan_flags; + int reserved[6]; +}; + +/* + * This routine flushes the icache, if it exists. + */ +errcode_t ext2fs_flush_icache(ext2_filsys fs) +{ + int i; + + if (!fs->icache) + return 0; + + for (i=0; i < fs->icache->cache_size; i++) + fs->icache->cache[i].ino = 0; + + fs->icache->buffer_blk = 0; + return 0; +} + +static errcode_t create_icache(ext2_filsys fs) +{ + errcode_t retval; + + if (fs->icache) + return 0; + retval = ext2fs_get_mem(sizeof(struct ext2_inode_cache), &fs->icache); + if (retval) + return retval; + + memset(fs->icache, 0, sizeof(struct ext2_inode_cache)); + retval = ext2fs_get_mem(fs->blocksize, &fs->icache->buffer); + if (retval) { + ext2fs_free_mem(&fs->icache); + return retval; + } + fs->icache->buffer_blk = 0; + fs->icache->cache_last = -1; + fs->icache->cache_size = 4; + fs->icache->refcount = 1; + retval = ext2fs_get_mem(sizeof(struct ext2_inode_cache_ent) + * fs->icache->cache_size, + &fs->icache->cache); + if (retval) { + ext2fs_free_mem(&fs->icache->buffer); + ext2fs_free_mem(&fs->icache); + return retval; + } + ext2fs_flush_icache(fs); + return 0; +} + +errcode_t ext2fs_open_inode_scan(ext2_filsys fs, int buffer_blocks, + ext2_inode_scan *ret_scan) +{ + ext2_inode_scan scan; + errcode_t retval; + errcode_t (*save_get_blocks)(ext2_filsys f, ext2_ino_t ino, blk_t *blocks); + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + /* + * If fs->badblocks isn't set, then set it --- since the inode + * scanning functions require it. + */ + if (fs->badblocks == 0) { + /* + * Temporarly save fs->get_blocks and set it to zero, + * for compatibility with old e2fsck's. + */ + save_get_blocks = fs->get_blocks; + fs->get_blocks = 0; + retval = ext2fs_read_bb_inode(fs, &fs->badblocks); + if (retval) { + ext2fs_badblocks_list_free(fs->badblocks); + fs->badblocks = 0; + } + fs->get_blocks = save_get_blocks; + } + + retval = ext2fs_get_mem(sizeof(struct ext2_struct_inode_scan), &scan); + if (retval) + return retval; + memset(scan, 0, sizeof(struct ext2_struct_inode_scan)); + + scan->magic = EXT2_ET_MAGIC_INODE_SCAN; + scan->fs = fs; + scan->inode_size = EXT2_INODE_SIZE(fs->super); + scan->bytes_left = 0; + scan->current_group = 0; + scan->groups_left = fs->group_desc_count - 1; + scan->inode_buffer_blocks = buffer_blocks ? buffer_blocks : 8; + scan->current_block = scan->fs-> + group_desc[scan->current_group].bg_inode_table; + scan->inodes_left = EXT2_INODES_PER_GROUP(scan->fs->super); + scan->blocks_left = scan->fs->inode_blocks_per_group; + retval = ext2fs_get_mem((size_t) (scan->inode_buffer_blocks * + fs->blocksize), + &scan->inode_buffer); + scan->done_group = 0; + scan->done_group_data = 0; + scan->bad_block_ptr = 0; + if (retval) { + ext2fs_free_mem(&scan); + return retval; + } + retval = ext2fs_get_mem(scan->inode_size, &scan->temp_buffer); + if (retval) { + ext2fs_free_mem(&scan->inode_buffer); + ext2fs_free_mem(&scan); + return retval; + } + if (scan->fs->badblocks && scan->fs->badblocks->num) + scan->scan_flags |= EXT2_SF_CHK_BADBLOCKS; + *ret_scan = scan; + return 0; +} + +void ext2fs_close_inode_scan(ext2_inode_scan scan) +{ + if (!scan || (scan->magic != EXT2_ET_MAGIC_INODE_SCAN)) + return; + + ext2fs_free_mem(&scan->inode_buffer); + scan->inode_buffer = NULL; + ext2fs_free_mem(&scan->temp_buffer); + scan->temp_buffer = NULL; + ext2fs_free_mem(&scan); + return; +} + +void ext2fs_set_inode_callback(ext2_inode_scan scan, + errcode_t (*done_group)(ext2_filsys fs, + dgrp_t group, + void * priv_data), + void *done_group_data) +{ + if (!scan || (scan->magic != EXT2_ET_MAGIC_INODE_SCAN)) + return; + + scan->done_group = done_group; + scan->done_group_data = done_group_data; +} + +int ext2fs_inode_scan_flags(ext2_inode_scan scan, int set_flags, + int clear_flags) +{ + int old_flags; + + if (!scan || (scan->magic != EXT2_ET_MAGIC_INODE_SCAN)) + return 0; + + old_flags = scan->scan_flags; + scan->scan_flags &= ~clear_flags; + scan->scan_flags |= set_flags; + return old_flags; +} + +/* + * This function is called by ext2fs_get_next_inode when it needs to + * get ready to read in a new blockgroup. + */ +static errcode_t get_next_blockgroup(ext2_inode_scan scan) +{ + scan->current_group++; + scan->groups_left--; + + scan->current_block = scan->fs-> + group_desc[scan->current_group].bg_inode_table; + + scan->current_inode = scan->current_group * + EXT2_INODES_PER_GROUP(scan->fs->super); + + scan->bytes_left = 0; + scan->inodes_left = EXT2_INODES_PER_GROUP(scan->fs->super); + scan->blocks_left = scan->fs->inode_blocks_per_group; + return 0; +} + +errcode_t ext2fs_inode_scan_goto_blockgroup(ext2_inode_scan scan, + int group) +{ + scan->current_group = group - 1; + scan->groups_left = scan->fs->group_desc_count - group; + return get_next_blockgroup(scan); +} + +/* + * This function is called by get_next_blocks() to check for bad + * blocks in the inode table. + * + * This function assumes that badblocks_list->list is sorted in + * increasing order. + */ +static errcode_t check_for_inode_bad_blocks(ext2_inode_scan scan, + blk_t *num_blocks) +{ + blk_t blk = scan->current_block; + badblocks_list bb = scan->fs->badblocks; + + /* + * If the inode table is missing, then obviously there are no + * bad blocks. :-) + */ + if (blk == 0) + return 0; + + /* + * If the current block is greater than the bad block listed + * in the bad block list, then advance the pointer until this + * is no longer the case. If we run out of bad blocks, then + * we don't need to do any more checking! + */ + while (blk > bb->list[scan->bad_block_ptr]) { + if (++scan->bad_block_ptr >= bb->num) { + scan->scan_flags &= ~EXT2_SF_CHK_BADBLOCKS; + return 0; + } + } + + /* + * If the current block is equal to the bad block listed in + * the bad block list, then handle that one block specially. + * (We could try to handle runs of bad blocks, but that + * only increases CPU efficiency by a small amount, at the + * expense of a huge expense of code complexity, and for an + * uncommon case at that.) + */ + if (blk == bb->list[scan->bad_block_ptr]) { + scan->scan_flags |= EXT2_SF_BAD_INODE_BLK; + *num_blocks = 1; + if (++scan->bad_block_ptr >= bb->num) + scan->scan_flags &= ~EXT2_SF_CHK_BADBLOCKS; + return 0; + } + + /* + * If there is a bad block in the range that we're about to + * read in, adjust the number of blocks to read so that we we + * don't read in the bad block. (Then the next block to read + * will be the bad block, which is handled in the above case.) + */ + if ((blk + *num_blocks) > bb->list[scan->bad_block_ptr]) + *num_blocks = (int) (bb->list[scan->bad_block_ptr] - blk); + + return 0; +} + +/* + * This function is called by ext2fs_get_next_inode when it needs to + * read in more blocks from the current blockgroup's inode table. + */ +static errcode_t get_next_blocks(ext2_inode_scan scan) +{ + blk_t num_blocks; + errcode_t retval; + + /* + * Figure out how many blocks to read; we read at most + * inode_buffer_blocks, and perhaps less if there aren't that + * many blocks left to read. + */ + num_blocks = scan->inode_buffer_blocks; + if (num_blocks > scan->blocks_left) + num_blocks = scan->blocks_left; + + /* + * If the past block "read" was a bad block, then mark the + * left-over extra bytes as also being bad. + */ + if (scan->scan_flags & EXT2_SF_BAD_INODE_BLK) { + if (scan->bytes_left) + scan->scan_flags |= EXT2_SF_BAD_EXTRA_BYTES; + scan->scan_flags &= ~EXT2_SF_BAD_INODE_BLK; + } + + /* + * Do inode bad block processing, if necessary. + */ + if (scan->scan_flags & EXT2_SF_CHK_BADBLOCKS) { + retval = check_for_inode_bad_blocks(scan, &num_blocks); + if (retval) + return retval; + } + + if ((scan->scan_flags & EXT2_SF_BAD_INODE_BLK) || + (scan->current_block == 0)) { + memset(scan->inode_buffer, 0, + (size_t) num_blocks * scan->fs->blocksize); + } else { + retval = io_channel_read_blk(scan->fs->io, + scan->current_block, + (int) num_blocks, + scan->inode_buffer); + if (retval) + return EXT2_ET_NEXT_INODE_READ; + } + scan->ptr = scan->inode_buffer; + scan->bytes_left = num_blocks * scan->fs->blocksize; + + scan->blocks_left -= num_blocks; + if (scan->current_block) + scan->current_block += num_blocks; + return 0; +} + +errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino, + struct ext2_inode *inode, int bufsize) +{ + errcode_t retval; + int extra_bytes = 0; + + EXT2_CHECK_MAGIC(scan, EXT2_ET_MAGIC_INODE_SCAN); + + /* + * Do we need to start reading a new block group? + */ + if (scan->inodes_left <= 0) { + force_new_group: + if (scan->done_group) { + retval = (scan->done_group) + (scan->fs, scan->current_group, + scan->done_group_data); + if (retval) + return retval; + } + if (scan->groups_left <= 0) { + *ino = 0; + return 0; + } + retval = get_next_blockgroup(scan); + if (retval) + return retval; + } + /* + * This is done outside the above if statement so that the + * check can be done for block group #0. + */ + if (scan->current_block == 0) { + if (scan->scan_flags & EXT2_SF_SKIP_MISSING_ITABLE) { + goto force_new_group; + } else + return EXT2_ET_MISSING_INODE_TABLE; + } + + + /* + * Have we run out of space in the inode buffer? If so, we + * need to read in more blocks. + */ + if (scan->bytes_left < scan->inode_size) { + memcpy(scan->temp_buffer, scan->ptr, scan->bytes_left); + extra_bytes = scan->bytes_left; + + retval = get_next_blocks(scan); + if (retval) + return retval; +#if 0 + /* + * XXX test Need check for used inode somehow. + * (Note: this is hard.) + */ + if (is_empty_scan(scan)) + goto force_new_group; +#endif + } + + retval = 0; + if (extra_bytes) { + memcpy(scan->temp_buffer+extra_bytes, scan->ptr, + scan->inode_size - extra_bytes); + scan->ptr += scan->inode_size - extra_bytes; + scan->bytes_left -= scan->inode_size - extra_bytes; + +#if BB_BIG_ENDIAN + if ((scan->fs->flags & EXT2_FLAG_SWAP_BYTES) || + (scan->fs->flags & EXT2_FLAG_SWAP_BYTES_READ)) + ext2fs_swap_inode_full(scan->fs, + (struct ext2_inode_large *) inode, + (struct ext2_inode_large *) scan->temp_buffer, + 0, bufsize); + else +#endif + *inode = *((struct ext2_inode *) scan->temp_buffer); + if (scan->scan_flags & EXT2_SF_BAD_EXTRA_BYTES) + retval = EXT2_ET_BAD_BLOCK_IN_INODE_TABLE; + scan->scan_flags &= ~EXT2_SF_BAD_EXTRA_BYTES; + } else { +#if BB_BIG_ENDIAN + if ((scan->fs->flags & EXT2_FLAG_SWAP_BYTES) || + (scan->fs->flags & EXT2_FLAG_SWAP_BYTES_READ)) + ext2fs_swap_inode_full(scan->fs, + (struct ext2_inode_large *) inode, + (struct ext2_inode_large *) scan->ptr, + 0, bufsize); + else +#endif + memcpy(inode, scan->ptr, bufsize); + scan->ptr += scan->inode_size; + scan->bytes_left -= scan->inode_size; + if (scan->scan_flags & EXT2_SF_BAD_INODE_BLK) + retval = EXT2_ET_BAD_BLOCK_IN_INODE_TABLE; + } + + scan->inodes_left--; + scan->current_inode++; + *ino = scan->current_inode; + return retval; +} + +errcode_t ext2fs_get_next_inode(ext2_inode_scan scan, ext2_ino_t *ino, + struct ext2_inode *inode) +{ + return ext2fs_get_next_inode_full(scan, ino, inode, + sizeof(struct ext2_inode)); +} + +/* + * Functions to read and write a single inode. + */ +errcode_t ext2fs_read_inode_full(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode, int bufsize) +{ + unsigned long group, block, block_nr, offset; + char *ptr; + errcode_t retval; + int clen, i, inodes_per_block, length; + io_channel io; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + /* Check to see if user has an override function */ + if (fs->read_inode) { + retval = (fs->read_inode)(fs, ino, inode); + if (retval != EXT2_ET_CALLBACK_NOTHANDLED) + return retval; + } + /* Create inode cache if not present */ + if (!fs->icache) { + retval = create_icache(fs); + if (retval) + return retval; + } + /* Check to see if it's in the inode cache */ + if (bufsize == sizeof(struct ext2_inode)) { + /* only old good inode can be retrieve from the cache */ + for (i=0; i < fs->icache->cache_size; i++) { + if (fs->icache->cache[i].ino == ino) { + *inode = fs->icache->cache[i].inode; + return 0; + } + } + } + if ((ino == 0) || (ino > fs->super->s_inodes_count)) + return EXT2_ET_BAD_INODE_NUM; + if (fs->flags & EXT2_FLAG_IMAGE_FILE) { + inodes_per_block = fs->blocksize / EXT2_INODE_SIZE(fs->super); + block_nr = fs->image_header->offset_inode / fs->blocksize; + block_nr += (ino - 1) / inodes_per_block; + offset = ((ino - 1) % inodes_per_block) * + EXT2_INODE_SIZE(fs->super); + io = fs->image_io; + } else { + group = (ino - 1) / EXT2_INODES_PER_GROUP(fs->super); + offset = ((ino - 1) % EXT2_INODES_PER_GROUP(fs->super)) * + EXT2_INODE_SIZE(fs->super); + block = offset >> EXT2_BLOCK_SIZE_BITS(fs->super); + if (!fs->group_desc[(unsigned)group].bg_inode_table) + return EXT2_ET_MISSING_INODE_TABLE; + block_nr = fs->group_desc[(unsigned)group].bg_inode_table + + block; + io = fs->io; + } + offset &= (EXT2_BLOCK_SIZE(fs->super) - 1); + + length = EXT2_INODE_SIZE(fs->super); + if (bufsize < length) + length = bufsize; + + ptr = (char *) inode; + while (length) { + clen = length; + if ((offset + length) > fs->blocksize) + clen = fs->blocksize - offset; + + if (block_nr != fs->icache->buffer_blk) { + retval = io_channel_read_blk(io, block_nr, 1, + fs->icache->buffer); + if (retval) + return retval; + fs->icache->buffer_blk = block_nr; + } + + memcpy(ptr, ((char *) fs->icache->buffer) + (unsigned) offset, + clen); + + offset = 0; + length -= clen; + ptr += clen; + block_nr++; + } + +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_READ)) + ext2fs_swap_inode_full(fs, (struct ext2_inode_large *) inode, + (struct ext2_inode_large *) inode, + 0, length); +#endif + + /* Update the inode cache */ + fs->icache->cache_last = (fs->icache->cache_last + 1) % + fs->icache->cache_size; + fs->icache->cache[fs->icache->cache_last].ino = ino; + fs->icache->cache[fs->icache->cache_last].inode = *inode; + + return 0; +} + +errcode_t ext2fs_read_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode) +{ + return ext2fs_read_inode_full(fs, ino, inode, + sizeof(struct ext2_inode)); +} + +errcode_t ext2fs_write_inode_full(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode, int bufsize) +{ + unsigned long group, block, block_nr, offset; + errcode_t retval = 0; + struct ext2_inode_large temp_inode, *w_inode; + char *ptr; + int clen, i, length; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + /* Check to see if user provided an override function */ + if (fs->write_inode) { + retval = (fs->write_inode)(fs, ino, inode); + if (retval != EXT2_ET_CALLBACK_NOTHANDLED) + return retval; + } + + /* Check to see if the inode cache needs to be updated */ + if (fs->icache) { + for (i=0; i < fs->icache->cache_size; i++) { + if (fs->icache->cache[i].ino == ino) { + fs->icache->cache[i].inode = *inode; + break; + } + } + } else { + retval = create_icache(fs); + if (retval) + return retval; + } + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + if ((ino == 0) || (ino > fs->super->s_inodes_count)) + return EXT2_ET_BAD_INODE_NUM; + + length = bufsize; + if (length < EXT2_INODE_SIZE(fs->super)) + length = EXT2_INODE_SIZE(fs->super); + + if (length > (int) sizeof(struct ext2_inode_large)) { + w_inode = xmalloc(length); + } else + w_inode = &temp_inode; + memset(w_inode, 0, length); + +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE)) + ext2fs_swap_inode_full(fs, w_inode, + (struct ext2_inode_large *) inode, + 1, bufsize); + else +#endif + memcpy(w_inode, inode, bufsize); + + group = (ino - 1) / EXT2_INODES_PER_GROUP(fs->super); + offset = ((ino - 1) % EXT2_INODES_PER_GROUP(fs->super)) * + EXT2_INODE_SIZE(fs->super); + block = offset >> EXT2_BLOCK_SIZE_BITS(fs->super); + if (!fs->group_desc[(unsigned) group].bg_inode_table) + return EXT2_ET_MISSING_INODE_TABLE; + block_nr = fs->group_desc[(unsigned) group].bg_inode_table + block; + + offset &= (EXT2_BLOCK_SIZE(fs->super) - 1); + + length = EXT2_INODE_SIZE(fs->super); + if (length > bufsize) + length = bufsize; + + ptr = (char *) w_inode; + + while (length) { + clen = length; + if ((offset + length) > fs->blocksize) + clen = fs->blocksize - offset; + + if (fs->icache->buffer_blk != block_nr) { + retval = io_channel_read_blk(fs->io, block_nr, 1, + fs->icache->buffer); + if (retval) + goto errout; + fs->icache->buffer_blk = block_nr; + } + + + memcpy((char *) fs->icache->buffer + (unsigned) offset, + ptr, clen); + + retval = io_channel_write_blk(fs->io, block_nr, 1, + fs->icache->buffer); + if (retval) + goto errout; + + offset = 0; + ptr += clen; + length -= clen; + block_nr++; + } + + fs->flags |= EXT2_FLAG_CHANGED; +errout: + if (w_inode && w_inode != &temp_inode) + free(w_inode); + return retval; +} + +errcode_t ext2fs_write_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode) +{ + return ext2fs_write_inode_full(fs, ino, inode, + sizeof(struct ext2_inode)); +} + +/* + * This function should be called when writing a new inode. It makes + * sure that extra part of large inodes is initialized properly. + */ +errcode_t ext2fs_write_new_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode) +{ + struct ext2_inode *buf; + int size = EXT2_INODE_SIZE(fs->super); + struct ext2_inode_large *large_inode; + + if (size == sizeof(struct ext2_inode)) + return ext2fs_write_inode_full(fs, ino, inode, + sizeof(struct ext2_inode)); + + buf = xmalloc(size); + + memset(buf, 0, size); + *buf = *inode; + + large_inode = (struct ext2_inode_large *) buf; + large_inode->i_extra_isize = sizeof(struct ext2_inode_large) - + EXT2_GOOD_OLD_INODE_SIZE; + + return ext2fs_write_inode_full(fs, ino, buf, size); +} + + +errcode_t ext2fs_get_blocks(ext2_filsys fs, ext2_ino_t ino, blk_t *blocks) +{ + struct ext2_inode inode; + int i; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (ino > fs->super->s_inodes_count) + return EXT2_ET_BAD_INODE_NUM; + + if (fs->get_blocks) { + if (!(*fs->get_blocks)(fs, ino, blocks)) + return 0; + } + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + for (i=0; i < EXT2_N_BLOCKS; i++) + blocks[i] = inode.i_block[i]; + return 0; +} + +errcode_t ext2fs_check_directory(ext2_filsys fs, ext2_ino_t ino) +{ + struct ext2_inode inode; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (ino > fs->super->s_inodes_count) + return EXT2_ET_BAD_INODE_NUM; + + if (fs->check_directory) { + retval = (fs->check_directory)(fs, ino); + if (retval != EXT2_ET_CALLBACK_NOTHANDLED) + return retval; + } + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + if (!LINUX_S_ISDIR(inode.i_mode)) + return EXT2_ET_NO_DIRECTORY; + return 0; +} + diff --git a/e2fsprogs/ext2fs/inode_io.c b/e2fsprogs/ext2fs/inode_io.c new file mode 100644 index 000000000..4bfa93aef --- /dev/null +++ b/e2fsprogs/ext2fs/inode_io.c @@ -0,0 +1,271 @@ +/* vi: set sw=4 ts=4: */ +/* + * inode_io.c --- This is allows an inode in an ext2 filesystem image + * to be accessed via the I/O manager interface. + * + * Copyright (C) 2002 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * For checking structure magic numbers... + */ + +#define EXT2_CHECK_MAGIC(struct, code) \ + if ((struct)->magic != (code)) return (code) + +struct inode_private_data { + int magic; + char name[32]; + ext2_file_t file; + ext2_filsys fs; + ext2_ino_t ino; + struct ext2_inode inode; + int flags; + struct inode_private_data *next; +}; + +#define CHANNEL_HAS_INODE 0x8000 + +static struct inode_private_data *top_intern; +static int ino_unique = 0; + +static errcode_t inode_open(const char *name, int flags, io_channel *channel); +static errcode_t inode_close(io_channel channel); +static errcode_t inode_set_blksize(io_channel channel, int blksize); +static errcode_t inode_read_blk(io_channel channel, unsigned long block, + int count, void *data); +static errcode_t inode_write_blk(io_channel channel, unsigned long block, + int count, const void *data); +static errcode_t inode_flush(io_channel channel); +static errcode_t inode_write_byte(io_channel channel, unsigned long offset, + int size, const void *data); + +static struct struct_io_manager struct_inode_manager = { + EXT2_ET_MAGIC_IO_MANAGER, + "Inode I/O Manager", + inode_open, + inode_close, + inode_set_blksize, + inode_read_blk, + inode_write_blk, + inode_flush, + inode_write_byte +}; + +io_manager inode_io_manager = &struct_inode_manager; + +errcode_t ext2fs_inode_io_intern2(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + char **name) +{ + struct inode_private_data *data; + errcode_t retval; + + if ((retval = ext2fs_get_mem(sizeof(struct inode_private_data), + &data))) + return retval; + data->magic = EXT2_ET_MAGIC_INODE_IO_CHANNEL; + sprintf(data->name, "%u:%d", ino, ino_unique++); + data->file = 0; + data->fs = fs; + data->ino = ino; + data->flags = 0; + if (inode) { + memcpy(&data->inode, inode, sizeof(struct ext2_inode)); + data->flags |= CHANNEL_HAS_INODE; + } + data->next = top_intern; + top_intern = data; + *name = data->name; + return 0; +} + +errcode_t ext2fs_inode_io_intern(ext2_filsys fs, ext2_ino_t ino, + char **name) +{ + return ext2fs_inode_io_intern2(fs, ino, NULL, name); +} + + +static errcode_t inode_open(const char *name, int flags, io_channel *channel) +{ + io_channel io = NULL; + struct inode_private_data *prev, *data = NULL; + errcode_t retval; + int open_flags; + + if (name == 0) + return EXT2_ET_BAD_DEVICE_NAME; + + for (data = top_intern, prev = NULL; data; + prev = data, data = data->next) + if (strcmp(name, data->name) == 0) + break; + if (!data) + return ENOENT; + if (prev) + prev->next = data->next; + else + top_intern = data->next; + + retval = ext2fs_get_mem(sizeof(struct struct_io_channel), &io); + if (retval) + goto cleanup; + memset(io, 0, sizeof(struct struct_io_channel)); + + io->magic = EXT2_ET_MAGIC_IO_CHANNEL; + io->manager = inode_io_manager; + retval = ext2fs_get_mem(strlen(name)+1, &io->name); + if (retval) + goto cleanup; + + strcpy(io->name, name); + io->private_data = data; + io->block_size = 1024; + io->read_error = 0; + io->write_error = 0; + io->refcount = 1; + + open_flags = (flags & IO_FLAG_RW) ? EXT2_FILE_WRITE : 0; + retval = ext2fs_file_open2(data->fs, data->ino, + (data->flags & CHANNEL_HAS_INODE) ? + &data->inode : 0, open_flags, + &data->file); + if (retval) + goto cleanup; + + *channel = io; + return 0; + +cleanup: + if (data) { + ext2fs_free_mem(&data); + } + if (io) + ext2fs_free_mem(&io); + return retval; +} + +static errcode_t inode_close(io_channel channel) +{ + struct inode_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + if (--channel->refcount > 0) + return 0; + + retval = ext2fs_file_close(data->file); + + ext2fs_free_mem(&channel->private_data); + if (channel->name) + ext2fs_free_mem(&channel->name); + ext2fs_free_mem(&channel); + return retval; +} + +static errcode_t inode_set_blksize(io_channel channel, int blksize) +{ + struct inode_private_data *data; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + channel->block_size = blksize; + return 0; +} + + +static errcode_t inode_read_blk(io_channel channel, unsigned long block, + int count, void *buf) +{ + struct inode_private_data *data; + errcode_t retval; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + if ((retval = ext2fs_file_lseek(data->file, + block * channel->block_size, + EXT2_SEEK_SET, 0))) + return retval; + + count = (count < 0) ? -count : (count * channel->block_size); + + return ext2fs_file_read(data->file, buf, count, 0); +} + +static errcode_t inode_write_blk(io_channel channel, unsigned long block, + int count, const void *buf) +{ + struct inode_private_data *data; + errcode_t retval; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + if ((retval = ext2fs_file_lseek(data->file, + block * channel->block_size, + EXT2_SEEK_SET, 0))) + return retval; + + count = (count < 0) ? -count : (count * channel->block_size); + + return ext2fs_file_write(data->file, buf, count, 0); +} + +static errcode_t inode_write_byte(io_channel channel, unsigned long offset, + int size, const void *buf) +{ + struct inode_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + if ((retval = ext2fs_file_lseek(data->file, offset, + EXT2_SEEK_SET, 0))) + return retval; + + return ext2fs_file_write(data->file, buf, size, 0); +} + +/* + * Flush data buffers to disk. + */ +static errcode_t inode_flush(io_channel channel) +{ + struct inode_private_data *data; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + return ext2fs_file_flush(data->file); +} + diff --git a/e2fsprogs/ext2fs/io_manager.c b/e2fsprogs/ext2fs/io_manager.c new file mode 100644 index 000000000..b47038602 --- /dev/null +++ b/e2fsprogs/ext2fs/io_manager.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: */ +/* + * io_manager.c --- the I/O manager abstraction + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t io_channel_set_options(io_channel channel, const char *opts) +{ + errcode_t retval = 0; + char *next, *ptr, *options, *arg; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + + if (!opts) + return 0; + + if (!channel->manager->set_option) + return EXT2_ET_INVALID_ARGUMENT; + + options = malloc(strlen(opts)+1); + if (!options) + return EXT2_ET_NO_MEMORY; + strcpy(options, opts); + ptr = options; + + while (ptr && *ptr) { + next = strchr(ptr, '&'); + if (next) + *next++ = 0; + + arg = strchr(ptr, '='); + if (arg) + *arg++ = 0; + + retval = (channel->manager->set_option)(channel, ptr, arg); + if (retval) + break; + ptr = next; + } + free(options); + return retval; +} + +errcode_t io_channel_write_byte(io_channel channel, unsigned long offset, + int count, const void *data) +{ + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + + if (channel->manager->write_byte) + return channel->manager->write_byte(channel, offset, + count, data); + + return EXT2_ET_UNIMPLEMENTED; +} diff --git a/e2fsprogs/ext2fs/irel.h b/e2fsprogs/ext2fs/irel.h new file mode 100644 index 000000000..91d1d89d5 --- /dev/null +++ b/e2fsprogs/ext2fs/irel.h @@ -0,0 +1,115 @@ +/* vi: set sw=4 ts=4: */ +/* + * irel.h + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +struct ext2_inode_reference { + blk_t block; + __u16 offset; +}; + +struct ext2_inode_relocate_entry { + ext2_ino_t new; + ext2_ino_t orig; + __u16 flags; + __u16 max_refs; +}; + +typedef struct ext2_inode_relocation_table *ext2_irel; + +struct ext2_inode_relocation_table { + __u32 magic; + char *name; + ext2_ino_t current; + void *priv_data; + + /* + * Add an inode relocation entry. + */ + errcode_t (*put)(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent); + /* + * Get an inode relocation entry. + */ + errcode_t (*get)(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent); + + /* + * Get an inode relocation entry by its original inode number + */ + errcode_t (*get_by_orig)(ext2_irel irel, ext2_ino_t orig, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent); + + /* + * Initialize for iterating over the inode relocation entries. + */ + errcode_t (*start_iter)(ext2_irel irel); + + /* + * The iterator function for the inode relocation entries. + * Returns an inode number of 0 when out of entries. + */ + errcode_t (*next)(ext2_irel irel, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent); + + /* + * Add an inode reference (i.e., note the fact that a + * particular block/offset contains a reference to an inode) + */ + errcode_t (*add_ref)(ext2_irel irel, ext2_ino_t ino, + struct ext2_inode_reference *ref); + + /* + * Initialize for iterating over the inode references for a + * particular inode. + */ + errcode_t (*start_iter_ref)(ext2_irel irel, ext2_ino_t ino); + + /* + * The iterator function for the inode references for an + * inode. The references for only one inode can be interator + * over at a time, as the iterator state is stored in ext2_irel. + */ + errcode_t (*next_ref)(ext2_irel irel, + struct ext2_inode_reference *ref); + + /* + * Move the inode relocation table from one inode number to + * another. Note that the inode references also must move. + */ + errcode_t (*move)(ext2_irel irel, ext2_ino_t old, ext2_ino_t new); + + /* + * Remove an inode relocation entry, along with all of the + * inode references. + */ + errcode_t (*delete)(ext2_irel irel, ext2_ino_t old); + + /* + * Free the inode relocation table. + */ + errcode_t (*free)(ext2_irel irel); +}; + +errcode_t ext2fs_irel_memarray_create(char *name, ext2_ino_t max_inode, + ext2_irel *irel); + +#define ext2fs_irel_put(irel, old, ent) ((irel)->put((irel), old, ent)) +#define ext2fs_irel_get(irel, old, ent) ((irel)->get((irel), old, ent)) +#define ext2fs_irel_get_by_orig(irel, orig, old, ent) \ + ((irel)->get_by_orig((irel), orig, old, ent)) +#define ext2fs_irel_start_iter(irel) ((irel)->start_iter((irel))) +#define ext2fs_irel_next(irel, old, ent) ((irel)->next((irel), old, ent)) +#define ext2fs_irel_add_ref(irel, ino, ref) ((irel)->add_ref((irel), ino, ref)) +#define ext2fs_irel_start_iter_ref(irel, ino) ((irel)->start_iter_ref((irel), ino)) +#define ext2fs_irel_next_ref(irel, ref) ((irel)->next_ref((irel), ref)) +#define ext2fs_irel_move(irel, old, new) ((irel)->move((irel), old, new)) +#define ext2fs_irel_delete(irel, old) ((irel)->delete((irel), old)) +#define ext2fs_irel_free(irel) ((irel)->free((irel))) diff --git a/e2fsprogs/ext2fs/irel_ma.c b/e2fsprogs/ext2fs/irel_ma.c new file mode 100644 index 000000000..c871b1891 --- /dev/null +++ b/e2fsprogs/ext2fs/irel_ma.c @@ -0,0 +1,367 @@ +/* vi: set sw=4 ts=4: */ +/* + * irel_ma.c + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" +#include "irel.h" + +static errcode_t ima_put(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent); +static errcode_t ima_get(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent); +static errcode_t ima_get_by_orig(ext2_irel irel, ext2_ino_t orig, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent); +static errcode_t ima_start_iter(ext2_irel irel); +static errcode_t ima_next(ext2_irel irel, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent); +static errcode_t ima_add_ref(ext2_irel irel, ext2_ino_t ino, + struct ext2_inode_reference *ref); +static errcode_t ima_start_iter_ref(ext2_irel irel, ext2_ino_t ino); +static errcode_t ima_next_ref(ext2_irel irel, struct ext2_inode_reference *ref); +static errcode_t ima_move(ext2_irel irel, ext2_ino_t old, ext2_ino_t new); +static errcode_t ima_delete(ext2_irel irel, ext2_ino_t old); +static errcode_t ima_free(ext2_irel irel); + +/* + * This data structure stores the array of inode references; there is + * a structure for each inode. + */ +struct inode_reference_entry { + __u16 num; + struct ext2_inode_reference *refs; +}; + +struct irel_ma { + __u32 magic; + ext2_ino_t max_inode; + ext2_ino_t ref_current; + int ref_iter; + ext2_ino_t *orig_map; + struct ext2_inode_relocate_entry *entries; + struct inode_reference_entry *ref_entries; +}; + +errcode_t ext2fs_irel_memarray_create(char *name, ext2_ino_t max_inode, + ext2_irel *new_irel) +{ + ext2_irel irel = 0; + errcode_t retval; + struct irel_ma *ma = 0; + size_t size; + + *new_irel = 0; + + /* + * Allocate memory structures + */ + retval = ext2fs_get_mem(sizeof(struct ext2_inode_relocation_table), + &irel); + if (retval) + goto errout; + memset(irel, 0, sizeof(struct ext2_inode_relocation_table)); + + retval = ext2fs_get_mem(strlen(name)+1, &irel->name); + if (retval) + goto errout; + strcpy(irel->name, name); + + retval = ext2fs_get_mem(sizeof(struct irel_ma), &ma); + if (retval) + goto errout; + memset(ma, 0, sizeof(struct irel_ma)); + irel->priv_data = ma; + + size = (size_t) (sizeof(ext2_ino_t) * (max_inode+1)); + retval = ext2fs_get_mem(size, &ma->orig_map); + if (retval) + goto errout; + memset(ma->orig_map, 0, size); + + size = (size_t) (sizeof(struct ext2_inode_relocate_entry) * + (max_inode+1)); + retval = ext2fs_get_mem(size, &ma->entries); + if (retval) + goto errout; + memset(ma->entries, 0, size); + + size = (size_t) (sizeof(struct inode_reference_entry) * + (max_inode+1)); + retval = ext2fs_get_mem(size, &ma->ref_entries); + if (retval) + goto errout; + memset(ma->ref_entries, 0, size); + ma->max_inode = max_inode; + + /* + * Fill in the irel data structure + */ + irel->put = ima_put; + irel->get = ima_get; + irel->get_by_orig = ima_get_by_orig; + irel->start_iter = ima_start_iter; + irel->next = ima_next; + irel->add_ref = ima_add_ref; + irel->start_iter_ref = ima_start_iter_ref; + irel->next_ref = ima_next_ref; + irel->move = ima_move; + irel->delete = ima_delete; + irel->free = ima_free; + + *new_irel = irel; + return 0; + +errout: + ima_free(irel); + return retval; +} + +static errcode_t ima_put(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent) +{ + struct inode_reference_entry *ref_ent; + struct irel_ma *ma; + errcode_t retval; + size_t size, old_size; + + ma = irel->priv_data; + if (old > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + + /* + * Force the orig field to the correct value; the application + * program shouldn't be messing with this field. + */ + if (ma->entries[(unsigned) old].new == 0) + ent->orig = old; + else + ent->orig = ma->entries[(unsigned) old].orig; + + /* + * If max_refs has changed, reallocate the refs array + */ + ref_ent = ma->ref_entries + (unsigned) old; + if (ref_ent->refs && ent->max_refs != + ma->entries[(unsigned) old].max_refs) { + size = (sizeof(struct ext2_inode_reference) * ent->max_refs); + old_size = (sizeof(struct ext2_inode_reference) * + ma->entries[(unsigned) old].max_refs); + retval = ext2fs_resize_mem(old_size, size, &ref_ent->refs); + if (retval) + return retval; + } + + ma->entries[(unsigned) old] = *ent; + ma->orig_map[(unsigned) ent->orig] = old; + return 0; +} + +static errcode_t ima_get(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent) +{ + struct irel_ma *ma; + + ma = irel->priv_data; + if (old > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned) old].new == 0) + return ENOENT; + *ent = ma->entries[(unsigned) old]; + return 0; +} + +static errcode_t ima_get_by_orig(ext2_irel irel, ext2_ino_t orig, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent) +{ + struct irel_ma *ma; + ext2_ino_t ino; + + ma = irel->priv_data; + if (orig > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + ino = ma->orig_map[(unsigned) orig]; + if (ino == 0) + return ENOENT; + *old = ino; + *ent = ma->entries[(unsigned) ino]; + return 0; +} + +static errcode_t ima_start_iter(ext2_irel irel) +{ + irel->current = 0; + return 0; +} + +static errcode_t ima_next(ext2_irel irel, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent) +{ + struct irel_ma *ma; + + ma = irel->priv_data; + while (++irel->current < ma->max_inode) { + if (ma->entries[(unsigned) irel->current].new == 0) + continue; + *old = irel->current; + *ent = ma->entries[(unsigned) irel->current]; + return 0; + } + *old = 0; + return 0; +} + +static errcode_t ima_add_ref(ext2_irel irel, ext2_ino_t ino, + struct ext2_inode_reference *ref) +{ + struct irel_ma *ma; + size_t size; + struct inode_reference_entry *ref_ent; + struct ext2_inode_relocate_entry *ent; + errcode_t retval; + + ma = irel->priv_data; + if (ino > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + + ref_ent = ma->ref_entries + (unsigned) ino; + ent = ma->entries + (unsigned) ino; + + /* + * If the inode reference array doesn't exist, create it. + */ + if (ref_ent->refs == 0) { + size = (size_t) ((sizeof(struct ext2_inode_reference) * + ent->max_refs)); + retval = ext2fs_get_mem(size, &ref_ent->refs); + if (retval) + return retval; + memset(ref_ent->refs, 0, size); + ref_ent->num = 0; + } + + if (ref_ent->num >= ent->max_refs) + return EXT2_ET_TOO_MANY_REFS; + + ref_ent->refs[(unsigned) ref_ent->num++] = *ref; + return 0; +} + +static errcode_t ima_start_iter_ref(ext2_irel irel, ext2_ino_t ino) +{ + struct irel_ma *ma; + + ma = irel->priv_data; + if (ino > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned) ino].new == 0) + return ENOENT; + ma->ref_current = ino; + ma->ref_iter = 0; + return 0; +} + +static errcode_t ima_next_ref(ext2_irel irel, + struct ext2_inode_reference *ref) +{ + struct irel_ma *ma; + struct inode_reference_entry *ref_ent; + + ma = irel->priv_data; + + ref_ent = ma->ref_entries + ma->ref_current; + + if ((ref_ent->refs == NULL) || + (ma->ref_iter >= ref_ent->num)) { + ref->block = 0; + ref->offset = 0; + return 0; + } + *ref = ref_ent->refs[ma->ref_iter++]; + return 0; +} + + +static errcode_t ima_move(ext2_irel irel, ext2_ino_t old, ext2_ino_t new) +{ + struct irel_ma *ma; + + ma = irel->priv_data; + if ((old > ma->max_inode) || (new > ma->max_inode)) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned) old].new == 0) + return ENOENT; + + ma->entries[(unsigned) new] = ma->entries[(unsigned) old]; + ext2fs_free_mem(&ma->ref_entries[(unsigned) new].refs); + ma->ref_entries[(unsigned) new] = ma->ref_entries[(unsigned) old]; + + ma->entries[(unsigned) old].new = 0; + ma->ref_entries[(unsigned) old].num = 0; + ma->ref_entries[(unsigned) old].refs = 0; + + ma->orig_map[ma->entries[new].orig] = new; + return 0; +} + +static errcode_t ima_delete(ext2_irel irel, ext2_ino_t old) +{ + struct irel_ma *ma; + + ma = irel->priv_data; + if (old > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned) old].new == 0) + return ENOENT; + + ma->entries[old].new = 0; + ext2fs_free_mem(&ma->ref_entries[(unsigned) old].refs); + ma->orig_map[ma->entries[(unsigned) old].orig] = 0; + + ma->ref_entries[(unsigned) old].num = 0; + ma->ref_entries[(unsigned) old].refs = 0; + return 0; +} + +static errcode_t ima_free(ext2_irel irel) +{ + struct irel_ma *ma; + ext2_ino_t ino; + + if (!irel) + return 0; + + ma = irel->priv_data; + + if (ma) { + ext2fs_free_mem(&ma->orig_map); + ext2fs_free_mem(&ma->entries); + if (ma->ref_entries) { + for (ino = 0; ino <= ma->max_inode; ino++) { + ext2fs_free_mem(&ma->ref_entries[(unsigned) ino].refs); + } + ext2fs_free_mem(&ma->ref_entries); + } + ext2fs_free_mem(&ma); + } + ext2fs_free_mem(&irel->name); + ext2fs_free_mem(&irel); + return 0; +} diff --git a/e2fsprogs/ext2fs/ismounted.c b/e2fsprogs/ext2fs/ismounted.c new file mode 100644 index 000000000..cace7715c --- /dev/null +++ b/e2fsprogs/ext2fs/ismounted.c @@ -0,0 +1,357 @@ +/* vi: set sw=4 ts=4: */ +/* + * ismounted.c --- Check to see if the filesystem was mounted + * + * Copyright (C) 1995,1996,1997,1998,1999,2000 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_LINUX_FD_H +#include +#endif +#ifdef HAVE_MNTENT_H +#include +#endif +#ifdef HAVE_GETMNTINFO +#include +#include +#include +#endif /* HAVE_GETMNTINFO */ +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +#ifdef HAVE_MNTENT_H +/* + * Helper function which checks a file in /etc/mtab format to see if a + * filesystem is mounted. Returns an error if the file doesn't exist + * or can't be opened. + */ +static errcode_t check_mntent_file(const char *mtab_file, const char *file, + int *mount_flags, char *mtpt, int mtlen) +{ + struct mntent *mnt; + struct stat st_buf; + errcode_t retval = 0; + dev_t file_dev=0, file_rdev=0; + ino_t file_ino=0; + FILE *f; + int fd; + + *mount_flags = 0; + if ((f = setmntent (mtab_file, "r")) == NULL) + return errno; + if (stat(file, &st_buf) == 0) { + if (S_ISBLK(st_buf.st_mode)) { +#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */ + file_rdev = st_buf.st_rdev; +#endif /* __GNU__ */ + } else { + file_dev = st_buf.st_dev; + file_ino = st_buf.st_ino; + } + } + while ((mnt = getmntent (f)) != NULL) { + if (strcmp(file, mnt->mnt_fsname) == 0) + break; + if (stat(mnt->mnt_fsname, &st_buf) == 0) { + if (S_ISBLK(st_buf.st_mode)) { +#ifndef __GNU__ + if (file_rdev && (file_rdev == st_buf.st_rdev)) + break; +#endif /* __GNU__ */ + } else { + if (file_dev && ((file_dev == st_buf.st_dev) && + (file_ino == st_buf.st_ino))) + break; + } + } + } + + if (mnt == 0) { +#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */ + /* + * Do an extra check to see if this is the root device. We + * can't trust /etc/mtab, and /proc/mounts will only list + * /dev/root for the root filesystem. Argh. Instead we + * check if the given device has the same major/minor number + * as the device that the root directory is on. + */ + if (file_rdev && stat("/", &st_buf) == 0) { + if (st_buf.st_dev == file_rdev) { + *mount_flags = EXT2_MF_MOUNTED; + if (mtpt) + strncpy(mtpt, "/", mtlen); + goto is_root; + } + } +#endif /* __GNU__ */ + goto errout; + } +#ifndef __GNU__ /* The GNU hurd is deficient; what else is new? */ + /* Validate the entry in case /etc/mtab is out of date */ + /* + * We need to be paranoid, because some broken distributions + * (read: Slackware) don't initialize /etc/mtab before checking + * all of the non-root filesystems on the disk. + */ + if (stat(mnt->mnt_dir, &st_buf) < 0) { + retval = errno; + if (retval == ENOENT) { +#ifdef DEBUG + printf("Bogus entry in %s! (%s does not exist)\n", + mtab_file, mnt->mnt_dir); +#endif /* DEBUG */ + retval = 0; + } + goto errout; + } + if (file_rdev && (st_buf.st_dev != file_rdev)) { +#ifdef DEBUG + printf("Bogus entry in %s! (%s not mounted on %s)\n", + mtab_file, file, mnt->mnt_dir); +#endif /* DEBUG */ + goto errout; + } +#endif /* __GNU__ */ + *mount_flags = EXT2_MF_MOUNTED; + +#ifdef MNTOPT_RO + /* Check to see if the ro option is set */ + if (hasmntopt(mnt, MNTOPT_RO)) + *mount_flags |= EXT2_MF_READONLY; +#endif + + if (mtpt) + strncpy(mtpt, mnt->mnt_dir, mtlen); + /* + * Check to see if we're referring to the root filesystem. + * If so, do a manual check to see if we can open /etc/mtab + * read/write, since if the root is mounted read/only, the + * contents of /etc/mtab may not be accurate. + */ + if (!strcmp(mnt->mnt_dir, "/")) { +is_root: +#define TEST_FILE "/.ismount-test-file" + *mount_flags |= EXT2_MF_ISROOT; + fd = open(TEST_FILE, O_RDWR|O_CREAT); + if (fd < 0) { + if (errno == EROFS) + *mount_flags |= EXT2_MF_READONLY; + } else + close(fd); + (void) unlink(TEST_FILE); + } + retval = 0; +errout: + endmntent (f); + return retval; +} + +static errcode_t check_mntent(const char *file, int *mount_flags, + char *mtpt, int mtlen) +{ + errcode_t retval; + +#ifdef DEBUG + retval = check_mntent_file("/tmp/mtab", file, mount_flags, + mtpt, mtlen); + if (retval == 0) + return 0; +#endif /* DEBUG */ +#ifdef __linux__ + retval = check_mntent_file("/proc/mounts", file, mount_flags, + mtpt, mtlen); + if (retval == 0 && (*mount_flags != 0)) + return 0; +#endif /* __linux__ */ +#if defined(MOUNTED) || defined(_PATH_MOUNTED) +#ifndef MOUNTED +#define MOUNTED _PATH_MOUNTED +#endif /* MOUNTED */ + retval = check_mntent_file(MOUNTED, file, mount_flags, mtpt, mtlen); + return retval; +#else + *mount_flags = 0; + return 0; +#endif /* defined(MOUNTED) || defined(_PATH_MOUNTED) */ +} + +#else +#if defined(HAVE_GETMNTINFO) + +static errcode_t check_getmntinfo(const char *file, int *mount_flags, + char *mtpt, int mtlen) +{ + struct statfs *mp; + int len, n; + const char *s1; + char *s2; + + n = getmntinfo(&mp, MNT_NOWAIT); + if (n == 0) + return errno; + + len = sizeof(_PATH_DEV) - 1; + s1 = file; + if (strncmp(_PATH_DEV, s1, len) == 0) + s1 += len; + + *mount_flags = 0; + while (--n >= 0) { + s2 = mp->f_mntfromname; + if (strncmp(_PATH_DEV, s2, len) == 0) { + s2 += len - 1; + *s2 = 'r'; + } + if (strcmp(s1, s2) == 0 || strcmp(s1, &s2[1]) == 0) { + *mount_flags = EXT2_MF_MOUNTED; + break; + } + ++mp; + } + if (mtpt) + strncpy(mtpt, mp->f_mntonname, mtlen); + return 0; +} +#endif /* HAVE_GETMNTINFO */ +#endif /* HAVE_MNTENT_H */ + +/* + * Check to see if we're dealing with the swap device. + */ +static int is_swap_device(const char *file) +{ + FILE *f; + char buf[1024], *cp; + dev_t file_dev; + struct stat st_buf; + int ret = 0; + + file_dev = 0; +#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */ + if ((stat(file, &st_buf) == 0) && + S_ISBLK(st_buf.st_mode)) + file_dev = st_buf.st_rdev; +#endif /* __GNU__ */ + + if (!(f = fopen("/proc/swaps", "r"))) + return 0; + /* Skip the first line */ + fgets(buf, sizeof(buf), f); + while (!feof(f)) { + if (!fgets(buf, sizeof(buf), f)) + break; + if ((cp = strchr(buf, ' ')) != NULL) + *cp = 0; + if ((cp = strchr(buf, '\t')) != NULL) + *cp = 0; + if (strcmp(buf, file) == 0) { + ret++; + break; + } +#ifndef __GNU__ + if (file_dev && (stat(buf, &st_buf) == 0) && + S_ISBLK(st_buf.st_mode) && + file_dev == st_buf.st_rdev) { + ret++; + break; + } +#endif /* __GNU__ */ + } + fclose(f); + return ret; +} + + +/* + * ext2fs_check_mount_point() returns 1 if the device is mounted, 0 + * otherwise. If mtpt is non-NULL, the directory where the device is + * mounted is copied to where mtpt is pointing, up to mtlen + * characters. + */ +#ifdef __TURBOC__ +# pragma argsused +#endif +errcode_t ext2fs_check_mount_point(const char *device, int *mount_flags, + char *mtpt, int mtlen) +{ + if (is_swap_device(device)) { + *mount_flags = EXT2_MF_MOUNTED | EXT2_MF_SWAP; + strncpy(mtpt, "", mtlen); + return 0; + } +#ifdef HAVE_MNTENT_H + return check_mntent(device, mount_flags, mtpt, mtlen); +#else +#ifdef HAVE_GETMNTINFO + return check_getmntinfo(device, mount_flags, mtpt, mtlen); +#else +#ifdef __GNUC__ + #warning "Can't use getmntent or getmntinfo to check for mounted filesystems!" +#endif + *mount_flags = 0; + return 0; +#endif /* HAVE_GETMNTINFO */ +#endif /* HAVE_MNTENT_H */ +} + +/* + * ext2fs_check_if_mounted() sets the mount_flags EXT2_MF_MOUNTED, + * EXT2_MF_READONLY, and EXT2_MF_ROOT + * + */ +errcode_t ext2fs_check_if_mounted(const char *file, int *mount_flags) +{ + return ext2fs_check_mount_point(file, mount_flags, NULL, 0); +} + +#ifdef DEBUG +int main(int argc, char **argv) +{ + int retval, mount_flags; + char mntpt[80]; + + if (argc < 2) { + fprintf(stderr, "Usage: %s device\n", argv[0]); + exit(1); + } + + mntpt[0] = 0; + retval = ext2fs_check_mount_point(argv[1], &mount_flags, + mntpt, sizeof(mntpt)); + if (retval) { + com_err(argv[0], retval, + "while calling ext2fs_check_if_mounted"); + exit(1); + } + printf("Device %s reports flags %02x\n", argv[1], mount_flags); + if (mount_flags & EXT2_MF_BUSY) + printf("\t%s is apparently in use.\n", argv[1]); + if (mount_flags & EXT2_MF_MOUNTED) + printf("\t%s is mounted.\n", argv[1]); + if (mount_flags & EXT2_MF_SWAP) + printf("\t%s is a swap device.\n", argv[1]); + if (mount_flags & EXT2_MF_READONLY) + printf("\t%s is read-only.\n", argv[1]); + if (mount_flags & EXT2_MF_ISROOT) + printf("\t%s is the root filesystem.\n", argv[1]); + if (mntpt[0]) + printf("\t%s is mounted on %s.\n", argv[1], mntpt); + exit(0); +} +#endif /* DEBUG */ diff --git a/e2fsprogs/ext2fs/jfs_dat.h b/e2fsprogs/ext2fs/jfs_dat.h new file mode 100644 index 000000000..136635de0 --- /dev/null +++ b/e2fsprogs/ext2fs/jfs_dat.h @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * jfs_dat.h --- stripped down header file which only contains the JFS + * on-disk data structures + */ + +#define JFS_MAGIC_NUMBER 0xc03b3998U /* The first 4 bytes of /dev/random! */ + +/* + * On-disk structures + */ + +/* + * Descriptor block types: + */ + +#define JFS_DESCRIPTOR_BLOCK 1 +#define JFS_COMMIT_BLOCK 2 +#define JFS_SUPERBLOCK 3 + +/* + * Standard header for all descriptor blocks: + */ +typedef struct journal_header_s +{ + __u32 h_magic; + __u32 h_blocktype; + __u32 h_sequence; +} journal_header_t; + + +/* + * The block tag: used to describe a single buffer in the journal + */ +typedef struct journal_block_tag_s +{ + __u32 t_blocknr; /* The on-disk block number */ + __u32 t_flags; /* See below */ +} journal_block_tag_t; + +/* Definitions for the journal tag flags word: */ +#define JFS_FLAG_ESCAPE 1 /* on-disk block is escaped */ +#define JFS_FLAG_SAME_UUID 2 /* block has same uuid as previous */ +#define JFS_FLAG_DELETED 4 /* block deleted by this transaction */ +#define JFS_FLAG_LAST_TAG 8 /* last tag in this descriptor block */ + + +/* + * The journal superblock + */ +typedef struct journal_superblock_s +{ + journal_header_t s_header; + + /* Static information describing the journal */ + __u32 s_blocksize; /* journal device blocksize */ + __u32 s_maxlen; /* total blocks in journal file */ + __u32 s_first; /* first block of log information */ + + /* Dynamic information describing the current state of the log */ + __u32 s_sequence; /* first commit ID expected in log */ + __u32 s_start; /* blocknr of start of log */ + +} journal_superblock_t; + diff --git a/e2fsprogs/ext2fs/kernel-jbd.h b/e2fsprogs/ext2fs/kernel-jbd.h new file mode 100644 index 000000000..4c6c7dedd --- /dev/null +++ b/e2fsprogs/ext2fs/kernel-jbd.h @@ -0,0 +1,236 @@ +/* vi: set sw=4 ts=4: */ +/* + * linux/include/linux/jbd.h + * + * Written by Stephen C. Tweedie + * + * Copyright 1998-2000 Red Hat, Inc --- All Rights Reserved + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + * + * Definitions for transaction data structures for the buffer cache + * filesystem journaling support. + */ + +#ifndef _LINUX_JBD_H +#define _LINUX_JBD_H + +#include +#include +#include "ext2fs.h" + +/* + * Standard header for all descriptor blocks: + */ + +typedef struct journal_header_s +{ + __u32 h_magic; + __u32 h_blocktype; + __u32 h_sequence; +} journal_header_t; + +/* + * This is the global e2fsck structure. + */ +typedef struct e2fsck_struct *e2fsck_t; + + +struct inode { + e2fsck_t i_ctx; + ext2_ino_t i_ino; + struct ext2_inode i_ext2; +}; + + +/* + * The journal superblock. All fields are in big-endian byte order. + */ +typedef struct journal_superblock_s +{ +/* 0x0000 */ + journal_header_t s_header; + +/* 0x000C */ + /* Static information describing the journal */ + __u32 s_blocksize; /* journal device blocksize */ + __u32 s_maxlen; /* total blocks in journal file */ + __u32 s_first; /* first block of log information */ + +/* 0x0018 */ + /* Dynamic information describing the current state of the log */ + __u32 s_sequence; /* first commit ID expected in log */ + __u32 s_start; /* blocknr of start of log */ + +/* 0x0020 */ + /* Error value, as set by journal_abort(). */ + __s32 s_errno; + +/* 0x0024 */ + /* Remaining fields are only valid in a version-2 superblock */ + __u32 s_feature_compat; /* compatible feature set */ + __u32 s_feature_incompat; /* incompatible feature set */ + __u32 s_feature_ro_compat; /* readonly-compatible feature set */ +/* 0x0030 */ + __u8 s_uuid[16]; /* 128-bit uuid for journal */ + +/* 0x0040 */ + __u32 s_nr_users; /* Nr of filesystems sharing log */ + + __u32 s_dynsuper; /* Blocknr of dynamic superblock copy*/ + +/* 0x0048 */ + __u32 s_max_transaction; /* Limit of journal blocks per trans.*/ + __u32 s_max_trans_data; /* Limit of data blocks per trans. */ + +/* 0x0050 */ + __u32 s_padding[44]; + +/* 0x0100 */ + __u8 s_users[16*48]; /* ids of all fs'es sharing the log */ +/* 0x0400 */ +} journal_superblock_t; + + +extern int journal_blocks_per_page(struct inode *inode); +extern int jbd_blocks_per_page(struct inode *inode); + +#define JFS_MIN_JOURNAL_BLOCKS 1024 + + +/* + * Internal structures used by the logging mechanism: + */ + +#define JFS_MAGIC_NUMBER 0xc03b3998U /* The first 4 bytes of /dev/random! */ + +/* + * Descriptor block types: + */ + +#define JFS_DESCRIPTOR_BLOCK 1 +#define JFS_COMMIT_BLOCK 2 +#define JFS_SUPERBLOCK_V1 3 +#define JFS_SUPERBLOCK_V2 4 +#define JFS_REVOKE_BLOCK 5 + +/* + * The block tag: used to describe a single buffer in the journal + */ +typedef struct journal_block_tag_s +{ + __u32 t_blocknr; /* The on-disk block number */ + __u32 t_flags; /* See below */ +} journal_block_tag_t; + +/* + * The revoke descriptor: used on disk to describe a series of blocks to + * be revoked from the log + */ +typedef struct journal_revoke_header_s +{ + journal_header_t r_header; + int r_count; /* Count of bytes used in the block */ +} journal_revoke_header_t; + + +/* Definitions for the journal tag flags word: */ +#define JFS_FLAG_ESCAPE 1 /* on-disk block is escaped */ +#define JFS_FLAG_SAME_UUID 2 /* block has same uuid as previous */ +#define JFS_FLAG_DELETED 4 /* block deleted by this transaction */ +#define JFS_FLAG_LAST_TAG 8 /* last tag in this descriptor block */ + + + + +#define JFS_HAS_COMPAT_FEATURE(j,mask) \ + ((j)->j_format_version >= 2 && \ + ((j)->j_superblock->s_feature_compat & cpu_to_be32((mask)))) +#define JFS_HAS_RO_COMPAT_FEATURE(j,mask) \ + ((j)->j_format_version >= 2 && \ + ((j)->j_superblock->s_feature_ro_compat & cpu_to_be32((mask)))) +#define JFS_HAS_INCOMPAT_FEATURE(j,mask) \ + ((j)->j_format_version >= 2 && \ + ((j)->j_superblock->s_feature_incompat & cpu_to_be32((mask)))) + +#define JFS_FEATURE_INCOMPAT_REVOKE 0x00000001 + +/* Features known to this kernel version: */ +#define JFS_KNOWN_COMPAT_FEATURES 0 +#define JFS_KNOWN_ROCOMPAT_FEATURES 0 +#define JFS_KNOWN_INCOMPAT_FEATURES JFS_FEATURE_INCOMPAT_REVOKE + +/* Comparison functions for transaction IDs: perform comparisons using + * modulo arithmetic so that they work over sequence number wraps. */ + + +/* + * Definitions which augment the buffer_head layer + */ + +/* journaling buffer types */ +#define BJ_None 0 /* Not journaled */ +#define BJ_SyncData 1 /* Normal data: flush before commit */ +#define BJ_AsyncData 2 /* writepage data: wait on it before commit */ +#define BJ_Metadata 3 /* Normal journaled metadata */ +#define BJ_Forget 4 /* Buffer superceded by this transaction */ +#define BJ_IO 5 /* Buffer is for temporary IO use */ +#define BJ_Shadow 6 /* Buffer contents being shadowed to the log */ +#define BJ_LogCtl 7 /* Buffer contains log descriptors */ +#define BJ_Reserved 8 /* Buffer is reserved for access by journal */ +#define BJ_Types 9 + + +struct kdev_s { + e2fsck_t k_ctx; + int k_dev; +}; + +typedef struct kdev_s *kdev_t; +typedef unsigned int tid_t; + +struct journal_s +{ + unsigned long j_flags; + int j_errno; + struct buffer_head * j_sb_buffer; + struct journal_superblock_s *j_superblock; + int j_format_version; + unsigned long j_head; + unsigned long j_tail; + unsigned long j_free; + unsigned long j_first, j_last; + kdev_t j_dev; + kdev_t j_fs_dev; + int j_blocksize; + unsigned int j_blk_offset; + unsigned int j_maxlen; + struct inode * j_inode; + tid_t j_tail_sequence; + tid_t j_transaction_sequence; + __u8 j_uuid[16]; + struct jbd_revoke_table_s *j_revoke; +}; + +typedef struct journal_s journal_t; + +extern int journal_recover (journal_t *journal); +extern int journal_skip_recovery (journal_t *); + +/* Primary revoke support */ +extern int journal_init_revoke(journal_t *, int); +extern void journal_destroy_revoke_caches(void); +extern int journal_init_revoke_caches(void); + +/* Recovery revoke support */ +extern int journal_set_revoke(journal_t *, unsigned long, tid_t); +extern int journal_test_revoke(journal_t *, unsigned long, tid_t); +extern void journal_clear_revoke(journal_t *); +extern void journal_brelse_array(struct buffer_head *b[], int n); + +extern void journal_destroy_revoke(journal_t *); + + +#endif /* _LINUX_JBD_H */ diff --git a/e2fsprogs/ext2fs/kernel-list.h b/e2fsprogs/ext2fs/kernel-list.h new file mode 100644 index 000000000..3392596ca --- /dev/null +++ b/e2fsprogs/ext2fs/kernel-list.h @@ -0,0 +1,113 @@ +/* vi: set sw=4 ts=4: */ +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = { &name, &name } + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +#if (!defined(__GNUC__) && !defined(__WATCOMC__)) +#define __inline__ +#endif + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_add(struct list_head * new, + struct list_head * prev, + struct list_head * next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/* + * Insert a new entry after the specified head.. + */ +static __inline__ void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/* + * Insert a new entry at the tail + */ +static __inline__ void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +static __inline__ void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +static __inline__ int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/* + * Splice in "list" into "head" + */ +static __inline__ void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +#endif diff --git a/e2fsprogs/ext2fs/link.c b/e2fsprogs/ext2fs/link.c new file mode 100644 index 000000000..08b2e9673 --- /dev/null +++ b/e2fsprogs/ext2fs/link.c @@ -0,0 +1,135 @@ +/* vi: set sw=4 ts=4: */ +/* + * link.c --- create links in a ext2fs directory + * + * Copyright (C) 1993, 1994 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct link_struct { + const char *name; + int namelen; + ext2_ino_t inode; + int flags; + int done; + struct ext2_super_block *sb; +}; + +static int link_proc(struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data) +{ + struct link_struct *ls = (struct link_struct *) priv_data; + struct ext2_dir_entry *next; + int rec_len, min_rec_len; + int ret = 0; + + rec_len = EXT2_DIR_REC_LEN(ls->namelen); + + /* + * See if the following directory entry (if any) is unused; + * if so, absorb it into this one. + */ + next = (struct ext2_dir_entry *) (buf + offset + dirent->rec_len); + if ((offset + dirent->rec_len < blocksize - 8) && + (next->inode == 0) && + (offset + dirent->rec_len + next->rec_len <= blocksize)) { + dirent->rec_len += next->rec_len; + ret = DIRENT_CHANGED; + } + + /* + * If the directory entry is used, see if we can split the + * directory entry to make room for the new name. If so, + * truncate it and return. + */ + if (dirent->inode) { + min_rec_len = EXT2_DIR_REC_LEN(dirent->name_len & 0xFF); + if (dirent->rec_len < (min_rec_len + rec_len)) + return ret; + rec_len = dirent->rec_len - min_rec_len; + dirent->rec_len = min_rec_len; + next = (struct ext2_dir_entry *) (buf + offset + + dirent->rec_len); + next->inode = 0; + next->name_len = 0; + next->rec_len = rec_len; + return DIRENT_CHANGED; + } + + /* + * If we get this far, then the directory entry is not used. + * See if we can fit the request entry in. If so, do it. + */ + if (dirent->rec_len < rec_len) + return ret; + dirent->inode = ls->inode; + dirent->name_len = ls->namelen; + strncpy(dirent->name, ls->name, ls->namelen); + if (ls->sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) + dirent->name_len |= (ls->flags & 0x7) << 8; + + ls->done++; + return DIRENT_ABORT|DIRENT_CHANGED; +} + +/* + * Note: the low 3 bits of the flags field are used as the directory + * entry filetype. + */ +#ifdef __TURBOC__ +# pragma argsused +#endif +errcode_t ext2fs_link(ext2_filsys fs, ext2_ino_t dir, const char *name, + ext2_ino_t ino, int flags) +{ + errcode_t retval; + struct link_struct ls; + struct ext2_inode inode; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + ls.name = name; + ls.namelen = name ? strlen(name) : 0; + ls.inode = ino; + ls.flags = flags; + ls.done = 0; + ls.sb = fs->super; + + retval = ext2fs_dir_iterate(fs, dir, DIRENT_FLAG_INCLUDE_EMPTY, + 0, link_proc, &ls); + if (retval) + return retval; + + if (!ls.done) + return EXT2_ET_DIR_NO_SPACE; + + if ((retval = ext2fs_read_inode(fs, dir, &inode)) != 0) + return retval; + + if (inode.i_flags & EXT2_INDEX_FL) { + inode.i_flags &= ~EXT2_INDEX_FL; + if ((retval = ext2fs_write_inode(fs, dir, &inode)) != 0) + return retval; + } + + return 0; +} diff --git a/e2fsprogs/ext2fs/lookup.c b/e2fsprogs/ext2fs/lookup.c new file mode 100644 index 000000000..31b30a182 --- /dev/null +++ b/e2fsprogs/ext2fs/lookup.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: */ +/* + * lookup.c --- ext2fs directory lookup operations + * + * Copyright (C) 1993, 1994, 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct lookup_struct { + const char *name; + int len; + ext2_ino_t *inode; + int found; +}; + +#ifdef __TURBOC__ +# pragma argsused +#endif +static int lookup_proc(struct ext2_dir_entry *dirent, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct lookup_struct *ls = (struct lookup_struct *) priv_data; + + if (ls->len != (dirent->name_len & 0xFF)) + return 0; + if (strncmp(ls->name, dirent->name, (dirent->name_len & 0xFF))) + return 0; + *ls->inode = dirent->inode; + ls->found++; + return DIRENT_ABORT; +} + + +errcode_t ext2fs_lookup(ext2_filsys fs, ext2_ino_t dir, const char *name, + int namelen, char *buf, ext2_ino_t *inode) +{ + errcode_t retval; + struct lookup_struct ls; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + ls.name = name; + ls.len = namelen; + ls.inode = inode; + ls.found = 0; + + retval = ext2fs_dir_iterate(fs, dir, 0, buf, lookup_proc, &ls); + if (retval) + return retval; + + return (ls.found) ? 0 : EXT2_ET_FILE_NOT_FOUND; +} + + diff --git a/e2fsprogs/ext2fs/mkdir.c b/e2fsprogs/ext2fs/mkdir.c new file mode 100644 index 000000000..b63c5d7a6 --- /dev/null +++ b/e2fsprogs/ext2fs/mkdir.c @@ -0,0 +1,142 @@ +/* vi: set sw=4 ts=4: */ +/* + * mkdir.c --- make a directory in the filesystem + * + * Copyright (C) 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +#ifndef EXT2_FT_DIR +#define EXT2_FT_DIR 2 +#endif + +errcode_t ext2fs_mkdir(ext2_filsys fs, ext2_ino_t parent, ext2_ino_t inum, + const char *name) +{ + errcode_t retval; + struct ext2_inode parent_inode, inode; + ext2_ino_t ino = inum; + ext2_ino_t scratch_ino; + blk_t blk; + char *block = 0; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + /* + * Allocate an inode, if necessary + */ + if (!ino) { + retval = ext2fs_new_inode(fs, parent, LINUX_S_IFDIR | 0755, + 0, &ino); + if (retval) + goto cleanup; + } + + /* + * Allocate a data block for the directory + */ + retval = ext2fs_new_block(fs, 0, 0, &blk); + if (retval) + goto cleanup; + + /* + * Create a scratch template for the directory + */ + retval = ext2fs_new_dir_block(fs, ino, parent, &block); + if (retval) + goto cleanup; + + /* + * Get the parent's inode, if necessary + */ + if (parent != ino) { + retval = ext2fs_read_inode(fs, parent, &parent_inode); + if (retval) + goto cleanup; + } else + memset(&parent_inode, 0, sizeof(parent_inode)); + + /* + * Create the inode structure.... + */ + memset(&inode, 0, sizeof(struct ext2_inode)); + inode.i_mode = LINUX_S_IFDIR | (0777 & ~fs->umask); + inode.i_uid = inode.i_gid = 0; + inode.i_blocks = fs->blocksize / 512; + inode.i_block[0] = blk; + inode.i_links_count = 2; + inode.i_ctime = inode.i_atime = inode.i_mtime = time(NULL); + inode.i_size = fs->blocksize; + + /* + * Write out the inode and inode data block + */ + retval = ext2fs_write_dir_block(fs, blk, block); + if (retval) + goto cleanup; + retval = ext2fs_write_new_inode(fs, ino, &inode); + if (retval) + goto cleanup; + + /* + * Link the directory into the filesystem hierarchy + */ + if (name) { + retval = ext2fs_lookup(fs, parent, name, strlen(name), 0, + &scratch_ino); + if (!retval) { + retval = EXT2_ET_DIR_EXISTS; + name = 0; + goto cleanup; + } + if (retval != EXT2_ET_FILE_NOT_FOUND) + goto cleanup; + retval = ext2fs_link(fs, parent, name, ino, EXT2_FT_DIR); + if (retval) + goto cleanup; + } + + /* + * Update parent inode's counts + */ + if (parent != ino) { + parent_inode.i_links_count++; + retval = ext2fs_write_inode(fs, parent, &parent_inode); + if (retval) + goto cleanup; + } + + /* + * Update accounting.... + */ + ext2fs_block_alloc_stats(fs, blk, +1); + ext2fs_inode_alloc_stats2(fs, ino, +1, 1); + +cleanup: + ext2fs_free_mem(&block); + return retval; + +} + + diff --git a/e2fsprogs/ext2fs/mkjournal.c b/e2fsprogs/ext2fs/mkjournal.c new file mode 100644 index 000000000..5bdd34682 --- /dev/null +++ b/e2fsprogs/ext2fs/mkjournal.c @@ -0,0 +1,428 @@ +/* vi: set sw=4 ts=4: */ +/* + * mkjournal.c --- make a journal for a filesystem + * + * Copyright (C) 2000 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif +#if HAVE_SYS_IOCTL_H +#include +#endif +#if HAVE_NETINET_IN_H +#include +#endif + +#include "ext2_fs.h" +#include "../e2p/e2p.h" +#include "../e2fsck.h" +#include "ext2fs.h" +#include "kernel-jbd.h" + +/* + * This function automatically sets up the journal superblock and + * returns it as an allocated block. + */ +errcode_t ext2fs_create_journal_superblock(ext2_filsys fs, + __u32 size, int flags, + char **ret_jsb) +{ + errcode_t retval; + journal_superblock_t *jsb; + + if (size < 1024) + return EXT2_ET_JOURNAL_TOO_SMALL; + + if ((retval = ext2fs_get_mem(fs->blocksize, &jsb))) + return retval; + + memset (jsb, 0, fs->blocksize); + + jsb->s_header.h_magic = htonl(JFS_MAGIC_NUMBER); + if (flags & EXT2_MKJOURNAL_V1_SUPER) + jsb->s_header.h_blocktype = htonl(JFS_SUPERBLOCK_V1); + else + jsb->s_header.h_blocktype = htonl(JFS_SUPERBLOCK_V2); + jsb->s_blocksize = htonl(fs->blocksize); + jsb->s_maxlen = htonl(size); + jsb->s_nr_users = htonl(1); + jsb->s_first = htonl(1); + jsb->s_sequence = htonl(1); + memcpy(jsb->s_uuid, fs->super->s_uuid, sizeof(fs->super->s_uuid)); + /* + * If we're creating an external journal device, we need to + * adjust these fields. + */ + if (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { + jsb->s_nr_users = 0; + if (fs->blocksize == 1024) + jsb->s_first = htonl(3); + else + jsb->s_first = htonl(2); + } + + *ret_jsb = (char *) jsb; + return 0; +} + +/* + * This function writes a journal using POSIX routines. It is used + * for creating external journals and creating journals on live + * filesystems. + */ +static errcode_t write_journal_file(ext2_filsys fs, char *filename, + blk_t size, int flags) +{ + errcode_t retval; + char *buf = 0; + int fd, ret_size; + blk_t i; + + if ((retval = ext2fs_create_journal_superblock(fs, size, flags, &buf))) + return retval; + + /* Open the device or journal file */ + if ((fd = open(filename, O_WRONLY)) < 0) { + retval = errno; + goto errout; + } + + /* Write the superblock out */ + retval = EXT2_ET_SHORT_WRITE; + ret_size = write(fd, buf, fs->blocksize); + if (ret_size < 0) { + retval = errno; + goto errout; + } + if (ret_size != (int) fs->blocksize) + goto errout; + memset(buf, 0, fs->blocksize); + + for (i = 1; i < size; i++) { + ret_size = write(fd, buf, fs->blocksize); + if (ret_size < 0) { + retval = errno; + goto errout; + } + if (ret_size != (int) fs->blocksize) + goto errout; + } + close(fd); + + retval = 0; +errout: + ext2fs_free_mem(&buf); + return retval; +} + +/* + * Helper function for creating the journal using direct I/O routines + */ +struct mkjournal_struct { + int num_blocks; + int newblocks; + char *buf; + errcode_t err; +}; + +static int mkjournal_proc(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct mkjournal_struct *es = (struct mkjournal_struct *) priv_data; + blk_t new_blk; + static blk_t last_blk = 0; + errcode_t retval; + + if (*blocknr) { + last_blk = *blocknr; + return 0; + } + retval = ext2fs_new_block(fs, last_blk, 0, &new_blk); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + if (blockcnt > 0) + es->num_blocks--; + + es->newblocks++; + retval = io_channel_write_blk(fs->io, new_blk, 1, es->buf); + + if (blockcnt == 0) + memset(es->buf, 0, fs->blocksize); + + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + *blocknr = new_blk; + last_blk = new_blk; + ext2fs_block_alloc_stats(fs, new_blk, +1); + + if (es->num_blocks == 0) + return (BLOCK_CHANGED | BLOCK_ABORT); + else + return BLOCK_CHANGED; + +} + +/* + * This function creates a journal using direct I/O routines. + */ +static errcode_t write_journal_inode(ext2_filsys fs, ext2_ino_t journal_ino, + blk_t size, int flags) +{ + char *buf; + errcode_t retval; + struct ext2_inode inode; + struct mkjournal_struct es; + + if ((retval = ext2fs_create_journal_superblock(fs, size, flags, &buf))) + return retval; + + if ((retval = ext2fs_read_bitmaps(fs))) + return retval; + + if ((retval = ext2fs_read_inode(fs, journal_ino, &inode))) + return retval; + + if (inode.i_blocks > 0) + return EEXIST; + + es.num_blocks = size; + es.newblocks = 0; + es.buf = buf; + es.err = 0; + + retval = ext2fs_block_iterate2(fs, journal_ino, BLOCK_FLAG_APPEND, + 0, mkjournal_proc, &es); + if (es.err) { + retval = es.err; + goto errout; + } + + if ((retval = ext2fs_read_inode(fs, journal_ino, &inode))) + goto errout; + + inode.i_size += fs->blocksize * size; + inode.i_blocks += (fs->blocksize / 512) * es.newblocks; + inode.i_mtime = inode.i_ctime = time(0); + inode.i_links_count = 1; + inode.i_mode = LINUX_S_IFREG | 0600; + + if ((retval = ext2fs_write_inode(fs, journal_ino, &inode))) + goto errout; + retval = 0; + + memcpy(fs->super->s_jnl_blocks, inode.i_block, EXT2_N_BLOCKS*4); + fs->super->s_jnl_blocks[16] = inode.i_size; + fs->super->s_jnl_backup_type = EXT3_JNL_BACKUP_BLOCKS; + ext2fs_mark_super_dirty(fs); + +errout: + ext2fs_free_mem(&buf); + return retval; +} + +/* + * This function adds a journal device to a filesystem + */ +errcode_t ext2fs_add_journal_device(ext2_filsys fs, ext2_filsys journal_dev) +{ + struct stat st; + errcode_t retval; + char buf[1024]; + journal_superblock_t *jsb; + int start; + __u32 i, nr_users; + + /* Make sure the device exists and is a block device */ + if (stat(journal_dev->device_name, &st) < 0) + return errno; + + if (!S_ISBLK(st.st_mode)) + return EXT2_ET_JOURNAL_NOT_BLOCK; /* Must be a block device */ + + /* Get the journal superblock */ + start = 1; + if (journal_dev->blocksize == 1024) + start++; + if ((retval = io_channel_read_blk(journal_dev->io, start, -1024, buf))) + return retval; + + jsb = (journal_superblock_t *) buf; + if ((jsb->s_header.h_magic != (unsigned) ntohl(JFS_MAGIC_NUMBER)) || + (jsb->s_header.h_blocktype != (unsigned) ntohl(JFS_SUPERBLOCK_V2))) + return EXT2_ET_NO_JOURNAL_SB; + + if (ntohl(jsb->s_blocksize) != (unsigned long) fs->blocksize) + return EXT2_ET_UNEXPECTED_BLOCK_SIZE; + + /* Check and see if this filesystem has already been added */ + nr_users = ntohl(jsb->s_nr_users); + for (i=0; i < nr_users; i++) { + if (memcmp(fs->super->s_uuid, + &jsb->s_users[i*16], 16) == 0) + break; + } + if (i >= nr_users) { + memcpy(&jsb->s_users[nr_users*16], + fs->super->s_uuid, 16); + jsb->s_nr_users = htonl(nr_users+1); + } + + /* Writeback the journal superblock */ + if ((retval = io_channel_write_blk(journal_dev->io, start, -1024, buf))) + return retval; + + fs->super->s_journal_inum = 0; + fs->super->s_journal_dev = st.st_rdev; + memcpy(fs->super->s_journal_uuid, jsb->s_uuid, + sizeof(fs->super->s_journal_uuid)); + fs->super->s_feature_compat |= EXT3_FEATURE_COMPAT_HAS_JOURNAL; + ext2fs_mark_super_dirty(fs); + return 0; +} + +/* + * This function adds a journal inode to a filesystem, using either + * POSIX routines if the filesystem is mounted, or using direct I/O + * functions if it is not. + */ +errcode_t ext2fs_add_journal_inode(ext2_filsys fs, blk_t size, int flags) +{ + errcode_t retval; + ext2_ino_t journal_ino; + struct stat st; + char jfile[1024]; + int fd, mount_flags, f; + + retval = ext2fs_check_mount_point(fs->device_name, &mount_flags, + jfile, sizeof(jfile)-10); + if (retval) + return retval; + + if (mount_flags & EXT2_MF_MOUNTED) { + strcat(jfile, "/.journal"); + + /* + * If .../.journal already exists, make sure any + * immutable or append-only flags are cleared. + */ +#if defined(HAVE_CHFLAGS) && defined(UF_NODUMP) + (void) chflags (jfile, 0); +#else +#if HAVE_EXT2_IOCTLS + fd = open(jfile, O_RDONLY); + if (fd >= 0) { + f = 0; + ioctl(fd, EXT2_IOC_SETFLAGS, &f); + close(fd); + } +#endif +#endif + + /* Create the journal file */ + if ((fd = open(jfile, O_CREAT|O_WRONLY, 0600)) < 0) + return errno; + + if ((retval = write_journal_file(fs, jfile, size, flags))) + goto errout; + + /* Get inode number of the journal file */ + if (fstat(fd, &st) < 0) + goto errout; + +#if defined(HAVE_CHFLAGS) && defined(UF_NODUMP) + retval = fchflags (fd, UF_NODUMP|UF_IMMUTABLE); +#else +#if HAVE_EXT2_IOCTLS + f = EXT2_NODUMP_FL | EXT2_IMMUTABLE_FL; + retval = ioctl(fd, EXT2_IOC_SETFLAGS, &f); +#endif +#endif + if (retval) + goto errout; + + close(fd); + journal_ino = st.st_ino; + } else { + journal_ino = EXT2_JOURNAL_INO; + if ((retval = write_journal_inode(fs, journal_ino, + size, flags))) + return retval; + } + + fs->super->s_journal_inum = journal_ino; + fs->super->s_journal_dev = 0; + memset(fs->super->s_journal_uuid, 0, + sizeof(fs->super->s_journal_uuid)); + fs->super->s_feature_compat |= EXT3_FEATURE_COMPAT_HAS_JOURNAL; + + ext2fs_mark_super_dirty(fs); + return 0; +errout: + close(fd); + return retval; +} + +#ifdef DEBUG +main(int argc, char **argv) +{ + errcode_t retval; + char *device_name; + ext2_filsys fs; + + if (argc < 2) { + fprintf(stderr, "Usage: %s filesystem\n", argv[0]); + exit(1); + } + device_name = argv[1]; + + retval = ext2fs_open (device_name, EXT2_FLAG_RW, 0, 0, + unix_io_manager, &fs); + if (retval) { + com_err(argv[0], retval, "while opening %s", device_name); + exit(1); + } + + retval = ext2fs_add_journal_inode(fs, 1024); + if (retval) { + com_err(argv[0], retval, "while adding journal to %s", + device_name); + exit(1); + } + retval = ext2fs_flush(fs); + if (retval) { + printf("Warning, had trouble writing out superblocks.\n"); + } + ext2fs_close(fs); + exit(0); + +} +#endif diff --git a/e2fsprogs/ext2fs/namei.c b/e2fsprogs/ext2fs/namei.c new file mode 100644 index 000000000..988967009 --- /dev/null +++ b/e2fsprogs/ext2fs/namei.c @@ -0,0 +1,205 @@ +/* vi: set sw=4 ts=4: */ +/* + * namei.c --- ext2fs directory lookup operations + * + * Copyright (C) 1993, 1994, 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +/* #define NAMEI_DEBUG */ + +#include "ext2_fs.h" +#include "ext2fs.h" + +static errcode_t open_namei(ext2_filsys fs, ext2_ino_t root, ext2_ino_t base, + const char *pathname, size_t pathlen, int follow, + int link_count, char *buf, ext2_ino_t *res_inode); + +static errcode_t follow_link(ext2_filsys fs, ext2_ino_t root, ext2_ino_t dir, + ext2_ino_t inode, int link_count, + char *buf, ext2_ino_t *res_inode) +{ + char *pathname; + char *buffer = 0; + errcode_t retval; + struct ext2_inode ei; + +#ifdef NAMEI_DEBUG + printf("follow_link: root=%lu, dir=%lu, inode=%lu, lc=%d\n", + root, dir, inode, link_count); + +#endif + retval = ext2fs_read_inode (fs, inode, &ei); + if (retval) return retval; + if (!LINUX_S_ISLNK (ei.i_mode)) { + *res_inode = inode; + return 0; + } + if (link_count++ > 5) { + return EXT2_ET_SYMLINK_LOOP; + } + if (ext2fs_inode_data_blocks(fs,&ei)) { + retval = ext2fs_get_mem(fs->blocksize, &buffer); + if (retval) + return retval; + retval = io_channel_read_blk(fs->io, ei.i_block[0], 1, buffer); + if (retval) { + ext2fs_free_mem(&buffer); + return retval; + } + pathname = buffer; + } else + pathname = (char *)&(ei.i_block[0]); + retval = open_namei(fs, root, dir, pathname, ei.i_size, 1, + link_count, buf, res_inode); + ext2fs_free_mem(&buffer); + return retval; +} + +/* + * This routine interprets a pathname in the context of the current + * directory and the root directory, and returns the inode of the + * containing directory, and a pointer to the filename of the file + * (pointing into the pathname) and the length of the filename. + */ +static errcode_t dir_namei(ext2_filsys fs, ext2_ino_t root, ext2_ino_t dir, + const char *pathname, int pathlen, + int link_count, char *buf, + const char **name, int *namelen, + ext2_ino_t *res_inode) +{ + char c; + const char *thisname; + int len; + ext2_ino_t inode; + errcode_t retval; + + if ((c = *pathname) == '/') { + dir = root; + pathname++; + pathlen--; + } + while (1) { + thisname = pathname; + for (len=0; --pathlen >= 0;len++) { + c = *(pathname++); + if (c == '/') + break; + } + if (pathlen < 0) + break; + retval = ext2fs_lookup (fs, dir, thisname, len, buf, &inode); + if (retval) return retval; + retval = follow_link (fs, root, dir, inode, + link_count, buf, &dir); + if (retval) return retval; + } + *name = thisname; + *namelen = len; + *res_inode = dir; + return 0; +} + +static errcode_t open_namei(ext2_filsys fs, ext2_ino_t root, ext2_ino_t base, + const char *pathname, size_t pathlen, int follow, + int link_count, char *buf, ext2_ino_t *res_inode) +{ + const char *basename; + int namelen; + ext2_ino_t dir, inode; + errcode_t retval; + +#ifdef NAMEI_DEBUG + printf("open_namei: root=%lu, dir=%lu, path=%*s, lc=%d\n", + root, base, pathlen, pathname, link_count); +#endif + retval = dir_namei(fs, root, base, pathname, pathlen, + link_count, buf, &basename, &namelen, &dir); + if (retval) return retval; + if (!namelen) { /* special case: '/usr/' etc */ + *res_inode=dir; + return 0; + } + retval = ext2fs_lookup (fs, dir, basename, namelen, buf, &inode); + if (retval) + return retval; + if (follow) { + retval = follow_link(fs, root, dir, inode, link_count, + buf, &inode); + if (retval) + return retval; + } +#ifdef NAMEI_DEBUG + printf("open_namei: (link_count=%d) returns %lu\n", + link_count, inode); +#endif + *res_inode = inode; + return 0; +} + +errcode_t ext2fs_namei(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + const char *name, ext2_ino_t *inode) +{ + char *buf; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + + retval = open_namei(fs, root, cwd, name, strlen(name), 0, 0, + buf, inode); + + ext2fs_free_mem(&buf); + return retval; +} + +errcode_t ext2fs_namei_follow(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + const char *name, ext2_ino_t *inode) +{ + char *buf; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + + retval = open_namei(fs, root, cwd, name, strlen(name), 1, 0, + buf, inode); + + ext2fs_free_mem(&buf); + return retval; +} + +errcode_t ext2fs_follow_link(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + ext2_ino_t inode, ext2_ino_t *res_inode) +{ + char *buf; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + + retval = follow_link(fs, root, cwd, inode, 0, buf, res_inode); + + ext2fs_free_mem(&buf); + return retval; +} + diff --git a/e2fsprogs/ext2fs/newdir.c b/e2fsprogs/ext2fs/newdir.c new file mode 100644 index 000000000..9470e7f56 --- /dev/null +++ b/e2fsprogs/ext2fs/newdir.c @@ -0,0 +1,73 @@ +/* vi: set sw=4 ts=4: */ +/* + * newdir.c --- create a new directory block + * + * Copyright (C) 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +#ifndef EXT2_FT_DIR +#define EXT2_FT_DIR 2 +#endif + +/* + * Create new directory block + */ +errcode_t ext2fs_new_dir_block(ext2_filsys fs, ext2_ino_t dir_ino, + ext2_ino_t parent_ino, char **block) +{ + struct ext2_dir_entry *dir = NULL; + errcode_t retval; + char *buf; + int rec_len; + int filetype = 0; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + memset(buf, 0, fs->blocksize); + dir = (struct ext2_dir_entry *) buf; + dir->rec_len = fs->blocksize; + + if (dir_ino) { + if (fs->super->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_FILETYPE) + filetype = EXT2_FT_DIR << 8; + /* + * Set up entry for '.' + */ + dir->inode = dir_ino; + dir->name_len = 1 | filetype; + dir->name[0] = '.'; + rec_len = dir->rec_len - EXT2_DIR_REC_LEN(1); + dir->rec_len = EXT2_DIR_REC_LEN(1); + + /* + * Set up entry for '..' + */ + dir = (struct ext2_dir_entry *) (buf + dir->rec_len); + dir->rec_len = rec_len; + dir->inode = parent_ino; + dir->name_len = 2 | filetype; + dir->name[0] = '.'; + dir->name[1] = '.'; + + } + *block = buf; + return 0; +} diff --git a/e2fsprogs/ext2fs/openfs.c b/e2fsprogs/ext2fs/openfs.c new file mode 100644 index 000000000..716be060f --- /dev/null +++ b/e2fsprogs/ext2fs/openfs.c @@ -0,0 +1,330 @@ +/* vi: set sw=4 ts=4: */ +/* + * openfs.c --- open an ext2 filesystem + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" + + +#include "ext2fs.h" +#include "e2image.h" + +blk_t ext2fs_descriptor_block_loc(ext2_filsys fs, blk_t group_block, dgrp_t i) +{ + int bg; + int has_super = 0; + int ret_blk; + + if (!(fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) || + (i < fs->super->s_first_meta_bg)) + return (group_block + i + 1); + + bg = (fs->blocksize / sizeof (struct ext2_group_desc)) * i; + if (ext2fs_bg_has_super(fs, bg)) + has_super = 1; + ret_blk = (fs->super->s_first_data_block + has_super + + (bg * fs->super->s_blocks_per_group)); + /* + * If group_block is not the normal value, we're trying to use + * the backup group descriptors and superblock --- so use the + * alternate location of the second block group in the + * metablock group. Ideally we should be testing each bg + * descriptor block individually for correctness, but we don't + * have the infrastructure in place to do that. + */ + if (group_block != fs->super->s_first_data_block && + ((ret_blk + fs->super->s_blocks_per_group) < + fs->super->s_blocks_count)) + ret_blk += fs->super->s_blocks_per_group; + return ret_blk; +} + +errcode_t ext2fs_open(const char *name, int flags, int superblock, + unsigned int block_size, io_manager manager, + ext2_filsys *ret_fs) +{ + return ext2fs_open2(name, 0, flags, superblock, block_size, + manager, ret_fs); +} + +/* + * Note: if superblock is non-zero, block-size must also be non-zero. + * Superblock and block_size can be zero to use the default size. + * + * Valid flags for ext2fs_open() + * + * EXT2_FLAG_RW - Open the filesystem for read/write. + * EXT2_FLAG_FORCE - Open the filesystem even if some of the + * features aren't supported. + * EXT2_FLAG_JOURNAL_DEV_OK - Open an ext3 journal device + */ +errcode_t ext2fs_open2(const char *name, const char *io_options, + int flags, int superblock, + unsigned int block_size, io_manager manager, + ext2_filsys *ret_fs) +{ + ext2_filsys fs; + errcode_t retval; + unsigned long i; + int groups_per_block, blocks_per_group; + blk_t group_block, blk; + char *dest, *cp; +#if BB_BIG_ENDIAN + int j; + struct ext2_group_desc *gdp; +#endif + + EXT2_CHECK_MAGIC(manager, EXT2_ET_MAGIC_IO_MANAGER); + + retval = ext2fs_get_mem(sizeof(struct struct_ext2_filsys), &fs); + if (retval) + return retval; + + memset(fs, 0, sizeof(struct struct_ext2_filsys)); + fs->magic = EXT2_ET_MAGIC_EXT2FS_FILSYS; + fs->flags = flags; + fs->umask = 022; + retval = ext2fs_get_mem(strlen(name)+1, &fs->device_name); + if (retval) + goto cleanup; + strcpy(fs->device_name, name); + cp = strchr(fs->device_name, '?'); + if (!io_options && cp) { + *cp++ = 0; + io_options = cp; + } + + retval = manager->open(fs->device_name, + (flags & EXT2_FLAG_RW) ? IO_FLAG_RW : 0, + &fs->io); + if (retval) + goto cleanup; + if (io_options && + (retval = io_channel_set_options(fs->io, io_options))) + goto cleanup; + fs->image_io = fs->io; + fs->io->app_data = fs; + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &fs->super); + if (retval) + goto cleanup; + if (flags & EXT2_FLAG_IMAGE_FILE) { + retval = ext2fs_get_mem(sizeof(struct ext2_image_hdr), + &fs->image_header); + if (retval) + goto cleanup; + retval = io_channel_read_blk(fs->io, 0, + -(int)sizeof(struct ext2_image_hdr), + fs->image_header); + if (retval) + goto cleanup; + if (fs->image_header->magic_number != EXT2_ET_MAGIC_E2IMAGE) + return EXT2_ET_MAGIC_E2IMAGE; + superblock = 1; + block_size = fs->image_header->fs_blocksize; + } + + /* + * If the user specifies a specific block # for the + * superblock, then he/she must also specify the block size! + * Otherwise, read the master superblock located at offset + * SUPERBLOCK_OFFSET from the start of the partition. + * + * Note: we only save a backup copy of the superblock if we + * are reading the superblock from the primary superblock location. + */ + if (superblock) { + if (!block_size) { + retval = EXT2_ET_INVALID_ARGUMENT; + goto cleanup; + } + io_channel_set_blksize(fs->io, block_size); + group_block = superblock; + fs->orig_super = 0; + } else { + io_channel_set_blksize(fs->io, SUPERBLOCK_OFFSET); + superblock = 1; + group_block = 0; + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &fs->orig_super); + if (retval) + goto cleanup; + } + retval = io_channel_read_blk(fs->io, superblock, -SUPERBLOCK_SIZE, + fs->super); + if (retval) + goto cleanup; + if (fs->orig_super) + memcpy(fs->orig_super, fs->super, SUPERBLOCK_SIZE); + +#if BB_BIG_ENDIAN + if ((fs->super->s_magic == ext2fs_swab16(EXT2_SUPER_MAGIC)) || + (fs->flags & EXT2_FLAG_SWAP_BYTES)) { + fs->flags |= EXT2_FLAG_SWAP_BYTES; + + ext2fs_swap_super(fs->super); + } +#endif + + if (fs->super->s_magic != EXT2_SUPER_MAGIC) { + retval = EXT2_ET_BAD_MAGIC; + goto cleanup; + } + if (fs->super->s_rev_level > EXT2_LIB_CURRENT_REV) { + retval = EXT2_ET_REV_TOO_HIGH; + goto cleanup; + } + + /* + * Check for feature set incompatibility + */ + if (!(flags & EXT2_FLAG_FORCE)) { + if (fs->super->s_feature_incompat & + ~EXT2_LIB_FEATURE_INCOMPAT_SUPP) { + retval = EXT2_ET_UNSUPP_FEATURE; + goto cleanup; + } + if ((flags & EXT2_FLAG_RW) && + (fs->super->s_feature_ro_compat & + ~EXT2_LIB_FEATURE_RO_COMPAT_SUPP)) { + retval = EXT2_ET_RO_UNSUPP_FEATURE; + goto cleanup; + } + if (!(flags & EXT2_FLAG_JOURNAL_DEV_OK) && + (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)) { + retval = EXT2_ET_UNSUPP_FEATURE; + goto cleanup; + } + } + + fs->blocksize = EXT2_BLOCK_SIZE(fs->super); + if (fs->blocksize == 0) { + retval = EXT2_ET_CORRUPT_SUPERBLOCK; + goto cleanup; + } + fs->fragsize = EXT2_FRAG_SIZE(fs->super); + fs->inode_blocks_per_group = ((fs->super->s_inodes_per_group * + EXT2_INODE_SIZE(fs->super) + + EXT2_BLOCK_SIZE(fs->super) - 1) / + EXT2_BLOCK_SIZE(fs->super)); + if (block_size) { + if (block_size != fs->blocksize) { + retval = EXT2_ET_UNEXPECTED_BLOCK_SIZE; + goto cleanup; + } + } + /* + * Set the blocksize to the filesystem's blocksize. + */ + io_channel_set_blksize(fs->io, fs->blocksize); + + /* + * If this is an external journal device, don't try to read + * the group descriptors, because they're not there. + */ + if (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { + fs->group_desc_count = 0; + *ret_fs = fs; + return 0; + } + + /* + * Read group descriptors + */ + blocks_per_group = EXT2_BLOCKS_PER_GROUP(fs->super); + if (blocks_per_group == 0 || + blocks_per_group > EXT2_MAX_BLOCKS_PER_GROUP(fs->super) || + fs->inode_blocks_per_group > EXT2_MAX_INODES_PER_GROUP(fs->super)) { + retval = EXT2_ET_CORRUPT_SUPERBLOCK; + goto cleanup; + } + fs->group_desc_count = (fs->super->s_blocks_count - + fs->super->s_first_data_block + + blocks_per_group - 1) / blocks_per_group; + fs->desc_blocks = (fs->group_desc_count + + EXT2_DESC_PER_BLOCK(fs->super) - 1) + / EXT2_DESC_PER_BLOCK(fs->super); + retval = ext2fs_get_mem(fs->desc_blocks * fs->blocksize, + &fs->group_desc); + if (retval) + goto cleanup; + if (!group_block) + group_block = fs->super->s_first_data_block; + dest = (char *) fs->group_desc; + groups_per_block = fs->blocksize / sizeof(struct ext2_group_desc); + for (i=0 ; i < fs->desc_blocks; i++) { + blk = ext2fs_descriptor_block_loc(fs, group_block, i); + retval = io_channel_read_blk(fs->io, blk, 1, dest); + if (retval) + goto cleanup; +#if BB_BIG_ENDIAN + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + gdp = (struct ext2_group_desc *) dest; + for (j=0; j < groups_per_block; j++) + ext2fs_swap_group_desc(gdp++); + } +#endif + dest += fs->blocksize; + } + + *ret_fs = fs; + return 0; +cleanup: + ext2fs_free(fs); + return retval; +} + +/* + * Set/get the filesystem data I/O channel. + * + * These functions are only valid if EXT2_FLAG_IMAGE_FILE is true. + */ +errcode_t ext2fs_get_data_io(ext2_filsys fs, io_channel *old_io) +{ + if ((fs->flags & EXT2_FLAG_IMAGE_FILE) == 0) + return EXT2_ET_NOT_IMAGE_FILE; + if (old_io) { + *old_io = (fs->image_io == fs->io) ? 0 : fs->io; + } + return 0; +} + +errcode_t ext2fs_set_data_io(ext2_filsys fs, io_channel new_io) +{ + if ((fs->flags & EXT2_FLAG_IMAGE_FILE) == 0) + return EXT2_ET_NOT_IMAGE_FILE; + fs->io = new_io ? new_io : fs->image_io; + return 0; +} + +errcode_t ext2fs_rewrite_to_io(ext2_filsys fs, io_channel new_io) +{ + if ((fs->flags & EXT2_FLAG_IMAGE_FILE) == 0) + return EXT2_ET_NOT_IMAGE_FILE; + fs->io = fs->image_io = new_io; + fs->flags |= EXT2_FLAG_DIRTY | EXT2_FLAG_RW | + EXT2_FLAG_BB_DIRTY | EXT2_FLAG_IB_DIRTY; + fs->flags &= ~EXT2_FLAG_IMAGE_FILE; + return 0; +} diff --git a/e2fsprogs/ext2fs/read_bb.c b/e2fsprogs/ext2fs/read_bb.c new file mode 100644 index 000000000..4766157c2 --- /dev/null +++ b/e2fsprogs/ext2fs/read_bb.c @@ -0,0 +1,98 @@ +/* vi: set sw=4 ts=4: */ +/* + * read_bb --- read the bad blocks inode + * + * Copyright (C) 1994 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct read_bb_record { + ext2_badblocks_list bb_list; + errcode_t err; +}; + +/* + * Helper function for ext2fs_read_bb_inode() + */ +#ifdef __TURBOC__ +# pragma argsused +#endif +static int mark_bad_block(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)), + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct read_bb_record *rb = (struct read_bb_record *) priv_data; + + if (blockcnt < 0) + return 0; + + if ((*block_nr < fs->super->s_first_data_block) || + (*block_nr >= fs->super->s_blocks_count)) + return 0; /* Ignore illegal blocks */ + + rb->err = ext2fs_badblocks_list_add(rb->bb_list, *block_nr); + if (rb->err) + return BLOCK_ABORT; + return 0; +} + +/* + * Reads the current bad blocks from the bad blocks inode. + */ +errcode_t ext2fs_read_bb_inode(ext2_filsys fs, ext2_badblocks_list *bb_list) +{ + errcode_t retval; + struct read_bb_record rb; + struct ext2_inode inode; + blk_t numblocks; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!*bb_list) { + retval = ext2fs_read_inode(fs, EXT2_BAD_INO, &inode); + if (retval) + return retval; + if (inode.i_blocks < 500) + numblocks = (inode.i_blocks / + (fs->blocksize / 512)) + 20; + else + numblocks = 500; + retval = ext2fs_badblocks_list_create(bb_list, numblocks); + if (retval) + return retval; + } + + rb.bb_list = *bb_list; + rb.err = 0; + retval = ext2fs_block_iterate2(fs, EXT2_BAD_INO, 0, 0, + mark_bad_block, &rb); + if (retval) + return retval; + + return rb.err; +} + + diff --git a/e2fsprogs/ext2fs/read_bb_file.c b/e2fsprogs/ext2fs/read_bb_file.c new file mode 100644 index 000000000..831adcc3a --- /dev/null +++ b/e2fsprogs/ext2fs/read_bb_file.c @@ -0,0 +1,98 @@ +/* vi: set sw=4 ts=4: */ +/* + * read_bb_file.c --- read a list of bad blocks from a FILE * + * + * Copyright (C) 1994, 1995, 2000 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * Reads a list of bad blocks from a FILE * + */ +errcode_t ext2fs_read_bb_FILE2(ext2_filsys fs, FILE *f, + ext2_badblocks_list *bb_list, + void *priv_data, + void (*invalid)(ext2_filsys fs, + blk_t blk, + char *badstr, + void *priv_data)) +{ + errcode_t retval; + blk_t blockno; + int count; + char buf[128]; + + if (fs) + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!*bb_list) { + retval = ext2fs_badblocks_list_create(bb_list, 10); + if (retval) + return retval; + } + + while (!feof (f)) { + if (fgets(buf, sizeof(buf), f) == NULL) + break; + count = sscanf(buf, "%u", &blockno); + if (count <= 0) + continue; + if (fs && + ((blockno < fs->super->s_first_data_block) || + (blockno >= fs->super->s_blocks_count))) { + if (invalid) + (invalid)(fs, blockno, buf, priv_data); + continue; + } + retval = ext2fs_badblocks_list_add(*bb_list, blockno); + if (retval) + return retval; + } + return 0; +} + +static void call_compat_invalid(ext2_filsys fs, blk_t blk, + char *badstr EXT2FS_ATTR((unused)), + void *priv_data) +{ + void (*invalid)(ext2_filsys, blk_t); + + invalid = (void (*)(ext2_filsys, blk_t)) priv_data; + if (invalid) + invalid(fs, blk); +} + + +/* + * Reads a list of bad blocks from a FILE * + */ +errcode_t ext2fs_read_bb_FILE(ext2_filsys fs, FILE *f, + ext2_badblocks_list *bb_list, + void (*invalid)(ext2_filsys fs, blk_t blk)) +{ + return ext2fs_read_bb_FILE2(fs, f, bb_list, (void *) invalid, + call_compat_invalid); +} + + diff --git a/e2fsprogs/ext2fs/res_gdt.c b/e2fsprogs/ext2fs/res_gdt.c new file mode 100644 index 000000000..f7ee8b1fb --- /dev/null +++ b/e2fsprogs/ext2fs/res_gdt.c @@ -0,0 +1,221 @@ +/* vi: set sw=4 ts=4: */ +/* + * res_gdt.c --- reserve blocks for growing the group descriptor table + * during online resizing. + * + * Copyright (C) 2002 Andreas Dilger + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * Iterate through the groups which hold BACKUP superblock/GDT copies in an + * ext3 filesystem. The counters should be initialized to 1, 5, and 7 before + * calling this for the first time. In a sparse filesystem it will be the + * sequence of powers of 3, 5, and 7: 1, 3, 5, 7, 9, 25, 27, 49, 81, ... + * For a non-sparse filesystem it will be every group: 1, 2, 3, 4, ... + */ +static unsigned int list_backups(ext2_filsys fs, unsigned int *three, + unsigned int *five, unsigned int *seven) +{ + unsigned int *min = three; + int mult = 3; + unsigned int ret; + + if (!(fs->super->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) { + ret = *min; + *min += 1; + return ret; + } + + if (*five < *min) { + min = five; + mult = 5; + } + if (*seven < *min) { + min = seven; + mult = 7; + } + + ret = *min; + *min *= mult; + + return ret; +} + +/* + * This code assumes that the reserved blocks have already been marked in-use + * during ext2fs_initialize(), so that they are not allocated for other + * uses before we can add them to the resize inode (which has to come + * after the creation of the inode table). + */ +errcode_t ext2fs_create_resize_inode(ext2_filsys fs) +{ + errcode_t retval, retval2; + struct ext2_super_block *sb; + struct ext2_inode inode; + __u32 *dindir_buf, *gdt_buf; + int rsv_add; + unsigned long long apb, inode_size; + blk_t dindir_blk, rsv_off, gdt_off, gdt_blk; + int dindir_dirty = 0, inode_dirty = 0; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + sb = fs->super; + + retval = ext2fs_get_mem(2 * fs->blocksize, (void *)&dindir_buf); + if (retval) + goto out_free; + gdt_buf = (__u32 *)((char *)dindir_buf + fs->blocksize); + + retval = ext2fs_read_inode(fs, EXT2_RESIZE_INO, &inode); + if (retval) + goto out_free; + + /* Maximum possible file size (we donly use the dindirect blocks) */ + apb = EXT2_ADDR_PER_BLOCK(sb); + rsv_add = fs->blocksize / 512; + if ((dindir_blk = inode.i_block[EXT2_DIND_BLOCK])) { +#ifdef RES_GDT_DEBUG + printf("reading GDT dindir %u\n", dindir_blk); +#endif + retval = ext2fs_read_ind_block(fs, dindir_blk, dindir_buf); + if (retval) + goto out_inode; + } else { + blk_t goal = 3 + sb->s_reserved_gdt_blocks + + fs->desc_blocks + fs->inode_blocks_per_group; + + retval = ext2fs_alloc_block(fs, goal, 0, &dindir_blk); + if (retval) + goto out_free; + inode.i_mode = LINUX_S_IFREG | 0600; + inode.i_links_count = 1; + inode.i_block[EXT2_DIND_BLOCK] = dindir_blk; + inode.i_blocks = rsv_add; + memset(dindir_buf, 0, fs->blocksize); +#ifdef RES_GDT_DEBUG + printf("allocated GDT dindir %u\n", dindir_blk); +#endif + dindir_dirty = inode_dirty = 1; + inode_size = apb*apb + apb + EXT2_NDIR_BLOCKS; + inode_size *= fs->blocksize; + inode.i_size = inode_size & 0xFFFFFFFF; + inode.i_size_high = (inode_size >> 32) & 0xFFFFFFFF; + if(inode.i_size_high) { + sb->s_feature_ro_compat |= + EXT2_FEATURE_RO_COMPAT_LARGE_FILE; + } + inode.i_ctime = time(0); + } + + for (rsv_off = 0, gdt_off = fs->desc_blocks, + gdt_blk = sb->s_first_data_block + 1 + fs->desc_blocks; + rsv_off < sb->s_reserved_gdt_blocks; + rsv_off++, gdt_off++, gdt_blk++) { + unsigned int three = 1, five = 5, seven = 7; + unsigned int grp, last = 0; + int gdt_dirty = 0; + + gdt_off %= apb; + if (!dindir_buf[gdt_off]) { + /* FIXME XXX XXX + blk_t new_blk; + + retval = ext2fs_new_block(fs, gdt_blk, 0, &new_blk); + if (retval) + goto out_free; + if (new_blk != gdt_blk) { + // XXX free block + retval = -1; // XXX + } + */ + gdt_dirty = dindir_dirty = inode_dirty = 1; + memset(gdt_buf, 0, fs->blocksize); + dindir_buf[gdt_off] = gdt_blk; + inode.i_blocks += rsv_add; +#ifdef RES_GDT_DEBUG + printf("added primary GDT block %u at %u[%u]\n", + gdt_blk, dindir_blk, gdt_off); +#endif + } else if (dindir_buf[gdt_off] == gdt_blk) { +#ifdef RES_GDT_DEBUG + printf("reading primary GDT block %u\n", gdt_blk); +#endif + retval = ext2fs_read_ind_block(fs, gdt_blk, gdt_buf); + if (retval) + goto out_dindir; + } else { +#ifdef RES_GDT_DEBUG + printf("bad primary GDT %u != %u at %u[%u]\n", + dindir_buf[gdt_off], gdt_blk,dindir_blk,gdt_off); +#endif + retval = EXT2_ET_RESIZE_INODE_CORRUPT; + goto out_dindir; + } + + while ((grp = list_backups(fs, &three, &five, &seven)) < + fs->group_desc_count) { + blk_t expect = gdt_blk + grp * sb->s_blocks_per_group; + + if (!gdt_buf[last]) { +#ifdef RES_GDT_DEBUG + printf("added backup GDT %u grp %u@%u[%u]\n", + expect, grp, gdt_blk, last); +#endif + gdt_buf[last] = expect; + inode.i_blocks += rsv_add; + gdt_dirty = inode_dirty = 1; + } else if (gdt_buf[last] != expect) { +#ifdef RES_GDT_DEBUG + printf("bad backup GDT %u != %u at %u[%u]\n", + gdt_buf[last], expect, gdt_blk, last); +#endif + retval = EXT2_ET_RESIZE_INODE_CORRUPT; + goto out_dindir; + } + last++; + } + if (gdt_dirty) { +#ifdef RES_GDT_DEBUG + printf("writing primary GDT block %u\n", gdt_blk); +#endif + retval = ext2fs_write_ind_block(fs, gdt_blk, gdt_buf); + if (retval) + goto out_dindir; + } + } + +out_dindir: + if (dindir_dirty) { + retval2 = ext2fs_write_ind_block(fs, dindir_blk, dindir_buf); + if (!retval) + retval = retval2; + } +out_inode: +#ifdef RES_GDT_DEBUG + printf("inode.i_blocks = %u, i_size = %u\n", inode.i_blocks, + inode.i_size); +#endif + if (inode_dirty) { + inode.i_atime = inode.i_mtime = time(0); + retval2 = ext2fs_write_inode(fs, EXT2_RESIZE_INO, &inode); + if (!retval) + retval = retval2; + } +out_free: + ext2fs_free_mem((void *)&dindir_buf); + return retval; +} + diff --git a/e2fsprogs/ext2fs/rs_bitmap.c b/e2fsprogs/ext2fs/rs_bitmap.c new file mode 100644 index 000000000..e932b3c9d --- /dev/null +++ b/e2fsprogs/ext2fs/rs_bitmap.c @@ -0,0 +1,107 @@ +/* vi: set sw=4 ts=4: */ +/* + * rs_bitmap.c --- routine for changing the size of a bitmap + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_resize_generic_bitmap(__u32 new_end, __u32 new_real_end, + ext2fs_generic_bitmap bmap) +{ + errcode_t retval; + size_t size, new_size; + __u32 bitno; + + if (!bmap) + return EXT2_ET_INVALID_ARGUMENT; + + EXT2_CHECK_MAGIC(bmap, EXT2_ET_MAGIC_GENERIC_BITMAP); + + /* + * If we're expanding the bitmap, make sure all of the new + * parts of the bitmap are zero. + */ + if (new_end > bmap->end) { + bitno = bmap->real_end; + if (bitno > new_end) + bitno = new_end; + for (; bitno > bmap->end; bitno--) + ext2fs_clear_bit(bitno - bmap->start, bmap->bitmap); + } + if (new_real_end == bmap->real_end) { + bmap->end = new_end; + return 0; + } + + size = ((bmap->real_end - bmap->start) / 8) + 1; + new_size = ((new_real_end - bmap->start) / 8) + 1; + + if (size != new_size) { + retval = ext2fs_resize_mem(size, new_size, &bmap->bitmap); + if (retval) + return retval; + } + if (new_size > size) + memset(bmap->bitmap + size, 0, new_size - size); + + bmap->end = new_end; + bmap->real_end = new_real_end; + return 0; +} + +errcode_t ext2fs_resize_inode_bitmap(__u32 new_end, __u32 new_real_end, + ext2fs_inode_bitmap bmap) +{ + errcode_t retval; + + if (!bmap) + return EXT2_ET_INVALID_ARGUMENT; + + EXT2_CHECK_MAGIC(bmap, EXT2_ET_MAGIC_INODE_BITMAP); + + bmap->magic = EXT2_ET_MAGIC_GENERIC_BITMAP; + retval = ext2fs_resize_generic_bitmap(new_end, new_real_end, + bmap); + bmap->magic = EXT2_ET_MAGIC_INODE_BITMAP; + return retval; +} + +errcode_t ext2fs_resize_block_bitmap(__u32 new_end, __u32 new_real_end, + ext2fs_block_bitmap bmap) +{ + errcode_t retval; + + if (!bmap) + return EXT2_ET_INVALID_ARGUMENT; + + EXT2_CHECK_MAGIC(bmap, EXT2_ET_MAGIC_BLOCK_BITMAP); + + bmap->magic = EXT2_ET_MAGIC_GENERIC_BITMAP; + retval = ext2fs_resize_generic_bitmap(new_end, new_real_end, + bmap); + bmap->magic = EXT2_ET_MAGIC_BLOCK_BITMAP; + return retval; +} + diff --git a/e2fsprogs/ext2fs/rw_bitmaps.c b/e2fsprogs/ext2fs/rw_bitmaps.c new file mode 100644 index 000000000..a5782db95 --- /dev/null +++ b/e2fsprogs/ext2fs/rw_bitmaps.c @@ -0,0 +1,296 @@ +/* vi: set sw=4 ts=4: */ +/* + * rw_bitmaps.c --- routines to read and write the inode and block bitmaps. + * + * Copyright (C) 1993, 1994, 1994, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" +#include "e2image.h" + +#if defined(__powerpc__) && BB_BIG_ENDIAN +/* + * On the PowerPC, the big-endian variant of the ext2 filesystem + * has its bitmaps stored as 32-bit words with bit 0 as the LSB + * of each word. Thus a bitmap with only bit 0 set would be, as + * a string of bytes, 00 00 00 01 00 ... + * To cope with this, we byte-reverse each word of a bitmap if + * we have a big-endian filesystem, that is, if we are *not* + * byte-swapping other word-sized numbers. + */ +#define EXT2_BIG_ENDIAN_BITMAPS +#endif + +#ifdef EXT2_BIG_ENDIAN_BITMAPS +static void ext2fs_swap_bitmap(ext2_filsys fs, char *bitmap, int nbytes) +{ + __u32 *p = (__u32 *) bitmap; + int n; + + for (n = nbytes / sizeof(__u32); n > 0; --n, ++p) + *p = ext2fs_swab32(*p); +} +#endif + +errcode_t ext2fs_write_inode_bitmap(ext2_filsys fs) +{ + dgrp_t i; + size_t nbytes; + errcode_t retval; + char * inode_bitmap = fs->inode_map->bitmap; + char * bitmap_block = NULL; + blk_t blk; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + if (!inode_bitmap) + return 0; + nbytes = (size_t) ((EXT2_INODES_PER_GROUP(fs->super)+7) / 8); + + retval = ext2fs_get_mem(fs->blocksize, &bitmap_block); + if (retval) + return retval; + memset(bitmap_block, 0xff, fs->blocksize); + for (i = 0; i < fs->group_desc_count; i++) { + memcpy(bitmap_block, inode_bitmap, nbytes); + blk = fs->group_desc[i].bg_inode_bitmap; + if (blk) { +#ifdef EXT2_BIG_ENDIAN_BITMAPS + if (!((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE))) + ext2fs_swap_bitmap(fs, bitmap_block, nbytes); +#endif + retval = io_channel_write_blk(fs->io, blk, 1, + bitmap_block); + if (retval) + return EXT2_ET_INODE_BITMAP_WRITE; + } + inode_bitmap += nbytes; + } + fs->flags &= ~EXT2_FLAG_IB_DIRTY; + ext2fs_free_mem(&bitmap_block); + return 0; +} + +errcode_t ext2fs_write_block_bitmap (ext2_filsys fs) +{ + dgrp_t i; + unsigned int j; + int nbytes; + unsigned int nbits; + errcode_t retval; + char * block_bitmap = fs->block_map->bitmap; + char * bitmap_block = NULL; + blk_t blk; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + if (!block_bitmap) + return 0; + nbytes = EXT2_BLOCKS_PER_GROUP(fs->super) / 8; + retval = ext2fs_get_mem(fs->blocksize, &bitmap_block); + if (retval) + return retval; + memset(bitmap_block, 0xff, fs->blocksize); + for (i = 0; i < fs->group_desc_count; i++) { + memcpy(bitmap_block, block_bitmap, nbytes); + if (i == fs->group_desc_count - 1) { + /* Force bitmap padding for the last group */ + nbits = ((fs->super->s_blocks_count + - fs->super->s_first_data_block) + % EXT2_BLOCKS_PER_GROUP(fs->super)); + if (nbits) + for (j = nbits; j < fs->blocksize * 8; j++) + ext2fs_set_bit(j, bitmap_block); + } + blk = fs->group_desc[i].bg_block_bitmap; + if (blk) { +#ifdef EXT2_BIG_ENDIAN_BITMAPS + if (!((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE))) + ext2fs_swap_bitmap(fs, bitmap_block, nbytes); +#endif + retval = io_channel_write_blk(fs->io, blk, 1, + bitmap_block); + if (retval) + return EXT2_ET_BLOCK_BITMAP_WRITE; + } + block_bitmap += nbytes; + } + fs->flags &= ~EXT2_FLAG_BB_DIRTY; + ext2fs_free_mem(&bitmap_block); + return 0; +} + +static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block) +{ + dgrp_t i; + char *block_bitmap = 0, *inode_bitmap = 0; + char *buf; + errcode_t retval; + int block_nbytes = (int) EXT2_BLOCKS_PER_GROUP(fs->super) / 8; + int inode_nbytes = (int) EXT2_INODES_PER_GROUP(fs->super) / 8; + blk_t blk; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + fs->write_bitmaps = ext2fs_write_bitmaps; + + retval = ext2fs_get_mem(strlen(fs->device_name) + 80, &buf); + if (retval) + return retval; + if (do_block) { + ext2fs_free_block_bitmap(fs->block_map); + sprintf(buf, "block bitmap for %s", fs->device_name); + retval = ext2fs_allocate_block_bitmap(fs, buf, &fs->block_map); + if (retval) + goto cleanup; + block_bitmap = fs->block_map->bitmap; + } + if (do_inode) { + ext2fs_free_inode_bitmap(fs->inode_map); + sprintf(buf, "inode bitmap for %s", fs->device_name); + retval = ext2fs_allocate_inode_bitmap(fs, buf, &fs->inode_map); + if (retval) + goto cleanup; + inode_bitmap = fs->inode_map->bitmap; + } + ext2fs_free_mem(&buf); + + if (fs->flags & EXT2_FLAG_IMAGE_FILE) { + if (inode_bitmap) { + blk = (fs->image_header->offset_inodemap / + fs->blocksize); + retval = io_channel_read_blk(fs->image_io, blk, + -(inode_nbytes * fs->group_desc_count), + inode_bitmap); + if (retval) + goto cleanup; + } + if (block_bitmap) { + blk = (fs->image_header->offset_blockmap / + fs->blocksize); + retval = io_channel_read_blk(fs->image_io, blk, + -(block_nbytes * fs->group_desc_count), + block_bitmap); + if (retval) + goto cleanup; + } + return 0; + } + + for (i = 0; i < fs->group_desc_count; i++) { + if (block_bitmap) { + blk = fs->group_desc[i].bg_block_bitmap; + if (blk) { + retval = io_channel_read_blk(fs->io, blk, + -block_nbytes, block_bitmap); + if (retval) { + retval = EXT2_ET_BLOCK_BITMAP_READ; + goto cleanup; + } +#ifdef EXT2_BIG_ENDIAN_BITMAPS + if (!((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_READ))) + ext2fs_swap_bitmap(fs, block_bitmap, block_nbytes); +#endif + } else + memset(block_bitmap, 0, block_nbytes); + block_bitmap += block_nbytes; + } + if (inode_bitmap) { + blk = fs->group_desc[i].bg_inode_bitmap; + if (blk) { + retval = io_channel_read_blk(fs->io, blk, + -inode_nbytes, inode_bitmap); + if (retval) { + retval = EXT2_ET_INODE_BITMAP_READ; + goto cleanup; + } +#ifdef EXT2_BIG_ENDIAN_BITMAPS + if (!((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_READ))) + ext2fs_swap_bitmap(fs, inode_bitmap, inode_nbytes); +#endif + } else + memset(inode_bitmap, 0, inode_nbytes); + inode_bitmap += inode_nbytes; + } + } + return 0; + +cleanup: + if (do_block) { + ext2fs_free_mem(&fs->block_map); + } + if (do_inode) { + ext2fs_free_mem(&fs->inode_map); + } + ext2fs_free_mem(&buf); + return retval; +} + +errcode_t ext2fs_read_inode_bitmap (ext2_filsys fs) +{ + return read_bitmaps(fs, 1, 0); +} + +errcode_t ext2fs_read_block_bitmap(ext2_filsys fs) +{ + return read_bitmaps(fs, 0, 1); +} + +errcode_t ext2fs_read_bitmaps(ext2_filsys fs) +{ + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (fs->inode_map && fs->block_map) + return 0; + + return read_bitmaps(fs, !fs->inode_map, !fs->block_map); +} + +errcode_t ext2fs_write_bitmaps(ext2_filsys fs) +{ + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (fs->block_map && ext2fs_test_bb_dirty(fs)) { + retval = ext2fs_write_block_bitmap(fs); + if (retval) + return retval; + } + if (fs->inode_map && ext2fs_test_ib_dirty(fs)) { + retval = ext2fs_write_inode_bitmap(fs); + if (retval) + return retval; + } + return 0; +} + diff --git a/e2fsprogs/ext2fs/sparse.c b/e2fsprogs/ext2fs/sparse.c new file mode 100644 index 000000000..b3d3071e9 --- /dev/null +++ b/e2fsprogs/ext2fs/sparse.c @@ -0,0 +1,79 @@ +/* vi: set sw=4 ts=4: */ +/* + * sparse.c --- find the groups in an ext2 filesystem with metadata backups + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * Copyright (C) 2002 Andreas Dilger. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include + +#include "ext2_fs.h" +#include "ext2fsP.h" + +static int test_root(int a, int b) +{ + if (a == 0) + return 1; + while (1) { + if (a == 1) + return 1; + if (a % b) + return 0; + a = a / b; + } +} + +int ext2fs_bg_has_super(ext2_filsys fs, int group_block) +{ + if (!(fs->super->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) + return 1; + + if (test_root(group_block, 3) || (test_root(group_block, 5)) || + test_root(group_block, 7)) + return 1; + + return 0; +} + +/* + * Iterate through the groups which hold BACKUP superblock/GDT copies in an + * ext3 filesystem. The counters should be initialized to 1, 5, and 7 before + * calling this for the first time. In a sparse filesystem it will be the + * sequence of powers of 3, 5, and 7: 1, 3, 5, 7, 9, 25, 27, 49, 81, ... + * For a non-sparse filesystem it will be every group: 1, 2, 3, 4, ... + */ +unsigned int ext2fs_list_backups(ext2_filsys fs, unsigned int *three, + unsigned int *five, unsigned int *seven) +{ + unsigned int *min = three; + int mult = 3; + unsigned int ret; + + if (!(fs->super->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) { + ret = *min; + *min += 1; + return ret; + } + + if (*five < *min) { + min = five; + mult = 5; + } + if (*seven < *min) { + min = seven; + mult = 7; + } + + ret = *min; + *min *= mult; + + return ret; +} diff --git a/e2fsprogs/ext2fs/swapfs.c b/e2fsprogs/ext2fs/swapfs.c new file mode 100644 index 000000000..2fca3cfbb --- /dev/null +++ b/e2fsprogs/ext2fs/swapfs.c @@ -0,0 +1,236 @@ +/* vi: set sw=4 ts=4: */ +/* + * swapfs.c --- swap ext2 filesystem data structures + * + * Copyright (C) 1995, 1996, 2002 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" +#include "ext2_ext_attr.h" + +#if BB_BIG_ENDIAN +void ext2fs_swap_super(struct ext2_super_block * sb) +{ + int i; + sb->s_inodes_count = ext2fs_swab32(sb->s_inodes_count); + sb->s_blocks_count = ext2fs_swab32(sb->s_blocks_count); + sb->s_r_blocks_count = ext2fs_swab32(sb->s_r_blocks_count); + sb->s_free_blocks_count = ext2fs_swab32(sb->s_free_blocks_count); + sb->s_free_inodes_count = ext2fs_swab32(sb->s_free_inodes_count); + sb->s_first_data_block = ext2fs_swab32(sb->s_first_data_block); + sb->s_log_block_size = ext2fs_swab32(sb->s_log_block_size); + sb->s_log_frag_size = ext2fs_swab32(sb->s_log_frag_size); + sb->s_blocks_per_group = ext2fs_swab32(sb->s_blocks_per_group); + sb->s_frags_per_group = ext2fs_swab32(sb->s_frags_per_group); + sb->s_inodes_per_group = ext2fs_swab32(sb->s_inodes_per_group); + sb->s_mtime = ext2fs_swab32(sb->s_mtime); + sb->s_wtime = ext2fs_swab32(sb->s_wtime); + sb->s_mnt_count = ext2fs_swab16(sb->s_mnt_count); + sb->s_max_mnt_count = ext2fs_swab16(sb->s_max_mnt_count); + sb->s_magic = ext2fs_swab16(sb->s_magic); + sb->s_state = ext2fs_swab16(sb->s_state); + sb->s_errors = ext2fs_swab16(sb->s_errors); + sb->s_minor_rev_level = ext2fs_swab16(sb->s_minor_rev_level); + sb->s_lastcheck = ext2fs_swab32(sb->s_lastcheck); + sb->s_checkinterval = ext2fs_swab32(sb->s_checkinterval); + sb->s_creator_os = ext2fs_swab32(sb->s_creator_os); + sb->s_rev_level = ext2fs_swab32(sb->s_rev_level); + sb->s_def_resuid = ext2fs_swab16(sb->s_def_resuid); + sb->s_def_resgid = ext2fs_swab16(sb->s_def_resgid); + sb->s_first_ino = ext2fs_swab32(sb->s_first_ino); + sb->s_inode_size = ext2fs_swab16(sb->s_inode_size); + sb->s_block_group_nr = ext2fs_swab16(sb->s_block_group_nr); + sb->s_feature_compat = ext2fs_swab32(sb->s_feature_compat); + sb->s_feature_incompat = ext2fs_swab32(sb->s_feature_incompat); + sb->s_feature_ro_compat = ext2fs_swab32(sb->s_feature_ro_compat); + sb->s_algorithm_usage_bitmap = ext2fs_swab32(sb->s_algorithm_usage_bitmap); + sb->s_reserved_gdt_blocks = ext2fs_swab16(sb->s_reserved_gdt_blocks); + sb->s_journal_inum = ext2fs_swab32(sb->s_journal_inum); + sb->s_journal_dev = ext2fs_swab32(sb->s_journal_dev); + sb->s_last_orphan = ext2fs_swab32(sb->s_last_orphan); + sb->s_default_mount_opts = ext2fs_swab32(sb->s_default_mount_opts); + sb->s_first_meta_bg = ext2fs_swab32(sb->s_first_meta_bg); + sb->s_mkfs_time = ext2fs_swab32(sb->s_mkfs_time); + for (i=0; i < 4; i++) + sb->s_hash_seed[i] = ext2fs_swab32(sb->s_hash_seed[i]); + for (i=0; i < 17; i++) + sb->s_jnl_blocks[i] = ext2fs_swab32(sb->s_jnl_blocks[i]); + +} + +void ext2fs_swap_group_desc(struct ext2_group_desc *gdp) +{ + gdp->bg_block_bitmap = ext2fs_swab32(gdp->bg_block_bitmap); + gdp->bg_inode_bitmap = ext2fs_swab32(gdp->bg_inode_bitmap); + gdp->bg_inode_table = ext2fs_swab32(gdp->bg_inode_table); + gdp->bg_free_blocks_count = ext2fs_swab16(gdp->bg_free_blocks_count); + gdp->bg_free_inodes_count = ext2fs_swab16(gdp->bg_free_inodes_count); + gdp->bg_used_dirs_count = ext2fs_swab16(gdp->bg_used_dirs_count); +} + +void ext2fs_swap_ext_attr(char *to, char *from, int bufsize, int has_header) +{ + struct ext2_ext_attr_header *from_header = + (struct ext2_ext_attr_header *)from; + struct ext2_ext_attr_header *to_header = + (struct ext2_ext_attr_header *)to; + struct ext2_ext_attr_entry *from_entry, *to_entry; + char *from_end = (char *)from_header + bufsize; + int n; + + if (to_header != from_header) + memcpy(to_header, from_header, bufsize); + + from_entry = (struct ext2_ext_attr_entry *)from_header; + to_entry = (struct ext2_ext_attr_entry *)to_header; + + if (has_header) { + to_header->h_magic = ext2fs_swab32(from_header->h_magic); + to_header->h_blocks = ext2fs_swab32(from_header->h_blocks); + to_header->h_refcount = ext2fs_swab32(from_header->h_refcount); + for (n=0; n<4; n++) + to_header->h_reserved[n] = + ext2fs_swab32(from_header->h_reserved[n]); + from_entry = (struct ext2_ext_attr_entry *)(from_header+1); + to_entry = (struct ext2_ext_attr_entry *)(to_header+1); + } + + while ((char *)from_entry < from_end && *(__u32 *)from_entry) { + to_entry->e_value_offs = + ext2fs_swab16(from_entry->e_value_offs); + to_entry->e_value_block = + ext2fs_swab32(from_entry->e_value_block); + to_entry->e_value_size = + ext2fs_swab32(from_entry->e_value_size); + from_entry = EXT2_EXT_ATTR_NEXT(from_entry); + to_entry = EXT2_EXT_ATTR_NEXT(to_entry); + } +} + +void ext2fs_swap_inode_full(ext2_filsys fs, struct ext2_inode_large *t, + struct ext2_inode_large *f, int hostorder, + int bufsize) +{ + unsigned i; + int islnk = 0; + __u32 *eaf, *eat; + + if (hostorder && LINUX_S_ISLNK(f->i_mode)) + islnk = 1; + t->i_mode = ext2fs_swab16(f->i_mode); + if (!hostorder && LINUX_S_ISLNK(t->i_mode)) + islnk = 1; + t->i_uid = ext2fs_swab16(f->i_uid); + t->i_size = ext2fs_swab32(f->i_size); + t->i_atime = ext2fs_swab32(f->i_atime); + t->i_ctime = ext2fs_swab32(f->i_ctime); + t->i_mtime = ext2fs_swab32(f->i_mtime); + t->i_dtime = ext2fs_swab32(f->i_dtime); + t->i_gid = ext2fs_swab16(f->i_gid); + t->i_links_count = ext2fs_swab16(f->i_links_count); + t->i_blocks = ext2fs_swab32(f->i_blocks); + t->i_flags = ext2fs_swab32(f->i_flags); + t->i_file_acl = ext2fs_swab32(f->i_file_acl); + t->i_dir_acl = ext2fs_swab32(f->i_dir_acl); + if (!islnk || ext2fs_inode_data_blocks(fs, (struct ext2_inode *)t)) { + for (i = 0; i < EXT2_N_BLOCKS; i++) + t->i_block[i] = ext2fs_swab32(f->i_block[i]); + } else if (t != f) { + for (i = 0; i < EXT2_N_BLOCKS; i++) + t->i_block[i] = f->i_block[i]; + } + t->i_generation = ext2fs_swab32(f->i_generation); + t->i_faddr = ext2fs_swab32(f->i_faddr); + + switch (fs->super->s_creator_os) { + case EXT2_OS_LINUX: + t->osd1.linux1.l_i_reserved1 = + ext2fs_swab32(f->osd1.linux1.l_i_reserved1); + t->osd2.linux2.l_i_frag = f->osd2.linux2.l_i_frag; + t->osd2.linux2.l_i_fsize = f->osd2.linux2.l_i_fsize; + t->osd2.linux2.i_pad1 = ext2fs_swab16(f->osd2.linux2.i_pad1); + t->osd2.linux2.l_i_uid_high = + ext2fs_swab16 (f->osd2.linux2.l_i_uid_high); + t->osd2.linux2.l_i_gid_high = + ext2fs_swab16 (f->osd2.linux2.l_i_gid_high); + t->osd2.linux2.l_i_reserved2 = + ext2fs_swab32(f->osd2.linux2.l_i_reserved2); + break; + case EXT2_OS_HURD: + t->osd1.hurd1.h_i_translator = + ext2fs_swab32 (f->osd1.hurd1.h_i_translator); + t->osd2.hurd2.h_i_frag = f->osd2.hurd2.h_i_frag; + t->osd2.hurd2.h_i_fsize = f->osd2.hurd2.h_i_fsize; + t->osd2.hurd2.h_i_mode_high = + ext2fs_swab16 (f->osd2.hurd2.h_i_mode_high); + t->osd2.hurd2.h_i_uid_high = + ext2fs_swab16 (f->osd2.hurd2.h_i_uid_high); + t->osd2.hurd2.h_i_gid_high = + ext2fs_swab16 (f->osd2.hurd2.h_i_gid_high); + t->osd2.hurd2.h_i_author = + ext2fs_swab32 (f->osd2.hurd2.h_i_author); + break; + case EXT2_OS_MASIX: + t->osd1.masix1.m_i_reserved1 = + ext2fs_swab32(f->osd1.masix1.m_i_reserved1); + t->osd2.masix2.m_i_frag = f->osd2.masix2.m_i_frag; + t->osd2.masix2.m_i_fsize = f->osd2.masix2.m_i_fsize; + t->osd2.masix2.m_pad1 = ext2fs_swab16(f->osd2.masix2.m_pad1); + t->osd2.masix2.m_i_reserved2[0] = + ext2fs_swab32(f->osd2.masix2.m_i_reserved2[0]); + t->osd2.masix2.m_i_reserved2[1] = + ext2fs_swab32(f->osd2.masix2.m_i_reserved2[1]); + break; + } + + if (bufsize < (int) (sizeof(struct ext2_inode) + sizeof(__u16))) + return; /* no i_extra_isize field */ + + t->i_extra_isize = ext2fs_swab16(f->i_extra_isize); + if (t->i_extra_isize > EXT2_INODE_SIZE(fs->super) - + sizeof(struct ext2_inode)) { + /* this is error case: i_extra_size is too large */ + return; + } + + i = sizeof(struct ext2_inode) + t->i_extra_isize + sizeof(__u32); + if (bufsize < (int) i) + return; /* no space for EA magic */ + + eaf = (__u32 *) (((char *) f) + sizeof(struct ext2_inode) + + f->i_extra_isize); + + if (ext2fs_swab32(*eaf) != EXT2_EXT_ATTR_MAGIC) + return; /* it seems no magic here */ + + eat = (__u32 *) (((char *) t) + sizeof(struct ext2_inode) + + f->i_extra_isize); + *eat = ext2fs_swab32(*eaf); + + /* convert EA(s) */ + ext2fs_swap_ext_attr((char *) (eat + 1), (char *) (eaf + 1), + bufsize - sizeof(struct ext2_inode) - + t->i_extra_isize - sizeof(__u32), 0); + +} + +void ext2fs_swap_inode(ext2_filsys fs, struct ext2_inode *t, + struct ext2_inode *f, int hostorder) +{ + ext2fs_swap_inode_full(fs, (struct ext2_inode_large *) t, + (struct ext2_inode_large *) f, hostorder, + sizeof(struct ext2_inode)); +} + +#endif diff --git a/e2fsprogs/ext2fs/test_io.c b/e2fsprogs/ext2fs/test_io.c new file mode 100644 index 000000000..bd74225c0 --- /dev/null +++ b/e2fsprogs/ext2fs/test_io.c @@ -0,0 +1,380 @@ +/* vi: set sw=4 ts=4: */ +/* + * test_io.c --- This is the Test I/O interface. + * + * Copyright (C) 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * For checking structure magic numbers... + */ + +#define EXT2_CHECK_MAGIC(struct, code) \ + if ((struct)->magic != (code)) return (code) + +struct test_private_data { + int magic; + io_channel real; + int flags; + FILE *outfile; + unsigned long block; + int read_abort_count, write_abort_count; + void (*read_blk)(unsigned long block, int count, errcode_t err); + void (*write_blk)(unsigned long block, int count, errcode_t err); + void (*set_blksize)(int blksize, errcode_t err); + void (*write_byte)(unsigned long block, int count, errcode_t err); +}; + +static errcode_t test_open(const char *name, int flags, io_channel *channel); +static errcode_t test_close(io_channel channel); +static errcode_t test_set_blksize(io_channel channel, int blksize); +static errcode_t test_read_blk(io_channel channel, unsigned long block, + int count, void *data); +static errcode_t test_write_blk(io_channel channel, unsigned long block, + int count, const void *data); +static errcode_t test_flush(io_channel channel); +static errcode_t test_write_byte(io_channel channel, unsigned long offset, + int count, const void *buf); +static errcode_t test_set_option(io_channel channel, const char *option, + const char *arg); + +static struct struct_io_manager struct_test_manager = { + EXT2_ET_MAGIC_IO_MANAGER, + "Test I/O Manager", + test_open, + test_close, + test_set_blksize, + test_read_blk, + test_write_blk, + test_flush, + test_write_byte, + test_set_option +}; + +io_manager test_io_manager = &struct_test_manager; + +/* + * These global variable can be set by the test program as + * necessary *before* calling test_open + */ +io_manager test_io_backing_manager = 0; +void (*test_io_cb_read_blk) + (unsigned long block, int count, errcode_t err) = 0; +void (*test_io_cb_write_blk) + (unsigned long block, int count, errcode_t err) = 0; +void (*test_io_cb_set_blksize) + (int blksize, errcode_t err) = 0; +void (*test_io_cb_write_byte) + (unsigned long block, int count, errcode_t err) = 0; + +/* + * Test flags + */ +#define TEST_FLAG_READ 0x01 +#define TEST_FLAG_WRITE 0x02 +#define TEST_FLAG_SET_BLKSIZE 0x04 +#define TEST_FLAG_FLUSH 0x08 +#define TEST_FLAG_DUMP 0x10 +#define TEST_FLAG_SET_OPTION 0x20 + +static void test_dump_block(io_channel channel, + struct test_private_data *data, + unsigned long block, const void *buf) +{ + const unsigned char *cp; + FILE *f = data->outfile; + int i; + unsigned long cksum = 0; + + for (i=0, cp = buf; i < channel->block_size; i++, cp++) { + cksum += *cp; + } + fprintf(f, "Contents of block %lu, checksum %08lu:\n", block, cksum); + for (i=0, cp = buf; i < channel->block_size; i++, cp++) { + if ((i % 16) == 0) + fprintf(f, "%04x: ", i); + fprintf(f, "%02x%c", *cp, ((i % 16) == 15) ? '\n' : ' '); + } +} + +static void test_abort(io_channel channel, unsigned long block) +{ + struct test_private_data *data; + FILE *f; + + data = (struct test_private_data *) channel->private_data; + f = data->outfile; + test_flush(channel); + + fprintf(f, "Aborting due to I/O to block %lu\n", block); + fflush(f); + abort(); +} + +static errcode_t test_open(const char *name, int flags, io_channel *channel) +{ + io_channel io = NULL; + struct test_private_data *data = NULL; + errcode_t retval; + char *value; + + if (name == 0) + return EXT2_ET_BAD_DEVICE_NAME; + retval = ext2fs_get_mem(sizeof(struct struct_io_channel), &io); + if (retval) + return retval; + memset(io, 0, sizeof(struct struct_io_channel)); + io->magic = EXT2_ET_MAGIC_IO_CHANNEL; + retval = ext2fs_get_mem(sizeof(struct test_private_data), &data); + if (retval) { + retval = EXT2_ET_NO_MEMORY; + goto cleanup; + } + io->manager = test_io_manager; + retval = ext2fs_get_mem(strlen(name)+1, &io->name); + if (retval) + goto cleanup; + + strcpy(io->name, name); + io->private_data = data; + io->block_size = 1024; + io->read_error = 0; + io->write_error = 0; + io->refcount = 1; + + memset(data, 0, sizeof(struct test_private_data)); + data->magic = EXT2_ET_MAGIC_TEST_IO_CHANNEL; + if (test_io_backing_manager) { + retval = test_io_backing_manager->open(name, flags, + &data->real); + if (retval) + goto cleanup; + } else + data->real = 0; + data->read_blk = test_io_cb_read_blk; + data->write_blk = test_io_cb_write_blk; + data->set_blksize = test_io_cb_set_blksize; + data->write_byte = test_io_cb_write_byte; + + data->outfile = NULL; + if ((value = getenv("TEST_IO_LOGFILE")) != NULL) + data->outfile = fopen(value, "w"); + if (!data->outfile) + data->outfile = stderr; + + data->flags = 0; + if ((value = getenv("TEST_IO_FLAGS")) != NULL) + data->flags = strtoul(value, NULL, 0); + + data->block = 0; + if ((value = getenv("TEST_IO_BLOCK")) != NULL) + data->block = strtoul(value, NULL, 0); + + data->read_abort_count = 0; + if ((value = getenv("TEST_IO_READ_ABORT")) != NULL) + data->read_abort_count = strtoul(value, NULL, 0); + + data->write_abort_count = 0; + if ((value = getenv("TEST_IO_WRITE_ABORT")) != NULL) + data->write_abort_count = strtoul(value, NULL, 0); + + *channel = io; + return 0; + +cleanup: + ext2fs_free_mem(&io); + ext2fs_free_mem(&data); + return retval; +} + +static errcode_t test_close(io_channel channel) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (--channel->refcount > 0) + return 0; + + if (data->real) + retval = io_channel_close(data->real); + + if (data->outfile && data->outfile != stderr) + fclose(data->outfile); + + ext2fs_free_mem(&channel->private_data); + ext2fs_free_mem(&channel->name); + ext2fs_free_mem(&channel); + return retval; +} + +static errcode_t test_set_blksize(io_channel channel, int blksize) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (data->real) + retval = io_channel_set_blksize(data->real, blksize); + if (data->set_blksize) + data->set_blksize(blksize, retval); + if (data->flags & TEST_FLAG_SET_BLKSIZE) + fprintf(data->outfile, + "Test_io: set_blksize(%d) returned %s\n", + blksize, retval ? error_message(retval) : "OK"); + channel->block_size = blksize; + return retval; +} + + +static errcode_t test_read_blk(io_channel channel, unsigned long block, + int count, void *buf) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (data->real) + retval = io_channel_read_blk(data->real, block, count, buf); + if (data->read_blk) + data->read_blk(block, count, retval); + if (data->flags & TEST_FLAG_READ) + fprintf(data->outfile, + "Test_io: read_blk(%lu, %d) returned %s\n", + block, count, retval ? error_message(retval) : "OK"); + if (data->block && data->block == block) { + if (data->flags & TEST_FLAG_DUMP) + test_dump_block(channel, data, block, buf); + if (--data->read_abort_count == 0) + test_abort(channel, block); + } + return retval; +} + +static errcode_t test_write_blk(io_channel channel, unsigned long block, + int count, const void *buf) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (data->real) + retval = io_channel_write_blk(data->real, block, count, buf); + if (data->write_blk) + data->write_blk(block, count, retval); + if (data->flags & TEST_FLAG_WRITE) + fprintf(data->outfile, + "Test_io: write_blk(%lu, %d) returned %s\n", + block, count, retval ? error_message(retval) : "OK"); + if (data->block && data->block == block) { + if (data->flags & TEST_FLAG_DUMP) + test_dump_block(channel, data, block, buf); + if (--data->write_abort_count == 0) + test_abort(channel, block); + } + return retval; +} + +static errcode_t test_write_byte(io_channel channel, unsigned long offset, + int count, const void *buf) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (data->real && data->real->manager->write_byte) + retval = io_channel_write_byte(data->real, offset, count, buf); + if (data->write_byte) + data->write_byte(offset, count, retval); + if (data->flags & TEST_FLAG_WRITE) + fprintf(data->outfile, + "Test_io: write_byte(%lu, %d) returned %s\n", + offset, count, retval ? error_message(retval) : "OK"); + return retval; +} + +/* + * Flush data buffers to disk. + */ +static errcode_t test_flush(io_channel channel) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (data->real) + retval = io_channel_flush(data->real); + + if (data->flags & TEST_FLAG_FLUSH) + fprintf(data->outfile, "Test_io: flush() returned %s\n", + retval ? error_message(retval) : "OK"); + + return retval; +} + +static errcode_t test_set_option(io_channel channel, const char *option, + const char *arg) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + + if (data->flags & TEST_FLAG_SET_OPTION) + fprintf(data->outfile, "Test_io: set_option(%s, %s) ", + option, arg); + if (data->real && data->real->manager->set_option) { + retval = (data->real->manager->set_option)(data->real, + option, arg); + if (data->flags & TEST_FLAG_SET_OPTION) + fprintf(data->outfile, "returned %s\n", + retval ? error_message(retval) : "OK"); + } else { + if (data->flags & TEST_FLAG_SET_OPTION) + fprintf(data->outfile, "not implemented\n"); + } + return retval; +} diff --git a/e2fsprogs/ext2fs/unix_io.c b/e2fsprogs/ext2fs/unix_io.c new file mode 100644 index 000000000..474f07340 --- /dev/null +++ b/e2fsprogs/ext2fs/unix_io.c @@ -0,0 +1,703 @@ +/* vi: set sw=4 ts=4: */ +/* + * unix_io.c --- This is the Unix (well, really POSIX) implementation + * of the I/O manager. + * + * Implements a one-block write-through cache. + * + * Includes support for Windows NT support under Cygwin. + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, + * 2002 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#include +#ifdef __linux__ +#include +#endif +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * For checking structure magic numbers... + */ + +#define EXT2_CHECK_MAGIC(struct, code) \ + if ((struct)->magic != (code)) return (code) + +struct unix_cache { + char *buf; + unsigned long block; + int access_time; + unsigned dirty:1; + unsigned in_use:1; +}; + +#define CACHE_SIZE 8 +#define WRITE_DIRECT_SIZE 4 /* Must be smaller than CACHE_SIZE */ +#define READ_DIRECT_SIZE 4 /* Should be smaller than CACHE_SIZE */ + +struct unix_private_data { + int magic; + int dev; + int flags; + int access_time; + ext2_loff_t offset; + struct unix_cache cache[CACHE_SIZE]; +}; + +static errcode_t unix_open(const char *name, int flags, io_channel *channel); +static errcode_t unix_close(io_channel channel); +static errcode_t unix_set_blksize(io_channel channel, int blksize); +static errcode_t unix_read_blk(io_channel channel, unsigned long block, + int count, void *data); +static errcode_t unix_write_blk(io_channel channel, unsigned long block, + int count, const void *data); +static errcode_t unix_flush(io_channel channel); +static errcode_t unix_write_byte(io_channel channel, unsigned long offset, + int size, const void *data); +static errcode_t unix_set_option(io_channel channel, const char *option, + const char *arg); + +static void reuse_cache(io_channel channel, struct unix_private_data *data, + struct unix_cache *cache, unsigned long block); + +/* __FreeBSD_kernel__ is defined by GNU/kFreeBSD - the FreeBSD kernel + * does not know buffered block devices - everything is raw. */ +#if defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#define NEED_BOUNCE_BUFFER +#else +#undef NEED_BOUNCE_BUFFER +#endif + +static struct struct_io_manager struct_unix_manager = { + EXT2_ET_MAGIC_IO_MANAGER, + "Unix I/O Manager", + unix_open, + unix_close, + unix_set_blksize, + unix_read_blk, + unix_write_blk, + unix_flush, +#ifdef NEED_BOUNCE_BUFFER + 0, +#else + unix_write_byte, +#endif + unix_set_option +}; + +io_manager unix_io_manager = &struct_unix_manager; + +/* + * Here are the raw I/O functions + */ +#ifndef NEED_BOUNCE_BUFFER +static errcode_t raw_read_blk(io_channel channel, + struct unix_private_data *data, + unsigned long block, + int count, void *buf) +{ + errcode_t retval; + ssize_t size; + ext2_loff_t location; + int actual = 0; + + size = (count < 0) ? -count : count * channel->block_size; + location = ((ext2_loff_t) block * channel->block_size) + data->offset; + if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) { + retval = errno ? errno : EXT2_ET_LLSEEK_FAILED; + goto error_out; + } + actual = read(data->dev, buf, size); + if (actual != size) { + if (actual < 0) + actual = 0; + retval = EXT2_ET_SHORT_READ; + goto error_out; + } + return 0; + +error_out: + memset((char *) buf+actual, 0, size-actual); + if (channel->read_error) + retval = (channel->read_error)(channel, block, count, buf, + size, actual, retval); + return retval; +} +#else /* NEED_BOUNCE_BUFFER */ +/* + * Windows and FreeBSD block devices only allow sector alignment IO in offset and size + */ +static errcode_t raw_read_blk(io_channel channel, + struct unix_private_data *data, + unsigned long block, + int count, void *buf) +{ + errcode_t retval; + size_t size, alignsize, fragment; + ext2_loff_t location; + int total = 0, actual; +#define BLOCKALIGN 512 + char sector[BLOCKALIGN]; + + size = (count < 0) ? -count : count * channel->block_size; + location = ((ext2_loff_t) block * channel->block_size) + data->offset; +#ifdef DEBUG + printf("count=%d, size=%d, block=%d, blk_size=%d, location=%lx\n", + count, size, block, channel->block_size, location); +#endif + if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) { + retval = errno ? errno : EXT2_ET_LLSEEK_FAILED; + goto error_out; + } + fragment = size % BLOCKALIGN; + alignsize = size - fragment; + if (alignsize) { + actual = read(data->dev, buf, alignsize); + if (actual != alignsize) + goto short_read; + } + if (fragment) { + actual = read(data->dev, sector, BLOCKALIGN); + if (actual != BLOCKALIGN) + goto short_read; + memcpy(buf+alignsize, sector, fragment); + } + return 0; + +short_read: + if (actual>0) + total += actual; + retval = EXT2_ET_SHORT_READ; + +error_out: + memset((char *) buf+total, 0, size-actual); + if (channel->read_error) + retval = (channel->read_error)(channel, block, count, buf, + size, actual, retval); + return retval; +} +#endif + +static errcode_t raw_write_blk(io_channel channel, + struct unix_private_data *data, + unsigned long block, + int count, const void *buf) +{ + ssize_t size; + ext2_loff_t location; + int actual = 0; + errcode_t retval; + + if (count == 1) + size = channel->block_size; + else { + if (count < 0) + size = -count; + else + size = count * channel->block_size; + } + + location = ((ext2_loff_t) block * channel->block_size) + data->offset; + if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) { + retval = errno ? errno : EXT2_ET_LLSEEK_FAILED; + goto error_out; + } + + actual = write(data->dev, buf, size); + if (actual != size) { + retval = EXT2_ET_SHORT_WRITE; + goto error_out; + } + return 0; + +error_out: + if (channel->write_error) + retval = (channel->write_error)(channel, block, count, buf, + size, actual, retval); + return retval; +} + + +/* + * Here we implement the cache functions + */ + +/* Allocate the cache buffers */ +static errcode_t alloc_cache(io_channel channel, + struct unix_private_data *data) +{ + errcode_t retval; + struct unix_cache *cache; + int i; + + data->access_time = 0; + for (i=0, cache = data->cache; i < CACHE_SIZE; i++, cache++) { + cache->block = 0; + cache->access_time = 0; + cache->dirty = 0; + cache->in_use = 0; + if ((retval = ext2fs_get_mem(channel->block_size, + &cache->buf))) + return retval; + } + return 0; +} + +/* Free the cache buffers */ +static void free_cache(struct unix_private_data *data) +{ + struct unix_cache *cache; + int i; + + data->access_time = 0; + for (i=0, cache = data->cache; i < CACHE_SIZE; i++, cache++) { + cache->block = 0; + cache->access_time = 0; + cache->dirty = 0; + cache->in_use = 0; + ext2fs_free_mem(&cache->buf); + cache->buf = 0; + } +} + +#ifndef NO_IO_CACHE +/* + * Try to find a block in the cache. If the block is not found, and + * eldest is a non-zero pointer, then fill in eldest with the cache + * entry to that should be reused. + */ +static struct unix_cache *find_cached_block(struct unix_private_data *data, + unsigned long block, + struct unix_cache **eldest) +{ + struct unix_cache *cache, *unused_cache, *oldest_cache; + int i; + + unused_cache = oldest_cache = 0; + for (i=0, cache = data->cache; i < CACHE_SIZE; i++, cache++) { + if (!cache->in_use) { + if (!unused_cache) + unused_cache = cache; + continue; + } + if (cache->block == block) { + cache->access_time = ++data->access_time; + return cache; + } + if (!oldest_cache || + (cache->access_time < oldest_cache->access_time)) + oldest_cache = cache; + } + if (eldest) + *eldest = (unused_cache) ? unused_cache : oldest_cache; + return 0; +} + +/* + * Reuse a particular cache entry for another block. + */ +static void reuse_cache(io_channel channel, struct unix_private_data *data, + struct unix_cache *cache, unsigned long block) +{ + if (cache->dirty && cache->in_use) + raw_write_blk(channel, data, cache->block, 1, cache->buf); + + cache->in_use = 1; + cache->dirty = 0; + cache->block = block; + cache->access_time = ++data->access_time; +} + +/* + * Flush all of the blocks in the cache + */ +static errcode_t flush_cached_blocks(io_channel channel, + struct unix_private_data *data, + int invalidate) + +{ + struct unix_cache *cache; + errcode_t retval, retval2; + int i; + + retval2 = 0; + for (i=0, cache = data->cache; i < CACHE_SIZE; i++, cache++) { + if (!cache->in_use) + continue; + + if (invalidate) + cache->in_use = 0; + + if (!cache->dirty) + continue; + + retval = raw_write_blk(channel, data, + cache->block, 1, cache->buf); + if (retval) + retval2 = retval; + else + cache->dirty = 0; + } + return retval2; +} +#endif /* NO_IO_CACHE */ + +static errcode_t unix_open(const char *name, int flags, io_channel *channel) +{ + io_channel io = NULL; + struct unix_private_data *data = NULL; + errcode_t retval; + int open_flags; + struct stat st; +#ifdef __linux__ + struct utsname ut; +#endif + + if (name == 0) + return EXT2_ET_BAD_DEVICE_NAME; + retval = ext2fs_get_mem(sizeof(struct struct_io_channel), &io); + if (retval) + return retval; + memset(io, 0, sizeof(struct struct_io_channel)); + io->magic = EXT2_ET_MAGIC_IO_CHANNEL; + retval = ext2fs_get_mem(sizeof(struct unix_private_data), &data); + if (retval) + goto cleanup; + + io->manager = unix_io_manager; + retval = ext2fs_get_mem(strlen(name)+1, &io->name); + if (retval) + goto cleanup; + + strcpy(io->name, name); + io->private_data = data; + io->block_size = 1024; + io->read_error = 0; + io->write_error = 0; + io->refcount = 1; + + memset(data, 0, sizeof(struct unix_private_data)); + data->magic = EXT2_ET_MAGIC_UNIX_IO_CHANNEL; + + if ((retval = alloc_cache(io, data))) + goto cleanup; + + open_flags = (flags & IO_FLAG_RW) ? O_RDWR : O_RDONLY; +#ifdef CONFIG_LFS + data->dev = open64(io->name, open_flags); +#else + data->dev = open(io->name, open_flags); +#endif + if (data->dev < 0) { + retval = errno; + goto cleanup; + } + +#ifdef __linux__ +#undef RLIM_INFINITY +#if (defined(__alpha__) || ((defined(__sparc__) || defined(__mips__)) && (SIZEOF_LONG == 4))) +#define RLIM_INFINITY ((unsigned long)(~0UL>>1)) +#else +#define RLIM_INFINITY (~0UL) +#endif + /* + * Work around a bug in 2.4.10-2.4.18 kernels where writes to + * block devices are wrongly getting hit by the filesize + * limit. This workaround isn't perfect, since it won't work + * if glibc wasn't built against 2.2 header files. (Sigh.) + * + */ + if ((flags & IO_FLAG_RW) && + (uname(&ut) == 0) && + ((ut.release[0] == '2') && (ut.release[1] == '.') && + (ut.release[2] == '4') && (ut.release[3] == '.') && + (ut.release[4] == '1') && (ut.release[5] >= '0') && + (ut.release[5] < '8')) && + (fstat(data->dev, &st) == 0) && + (S_ISBLK(st.st_mode))) { + struct rlimit rlim; + + rlim.rlim_cur = rlim.rlim_max = (unsigned long) RLIM_INFINITY; + setrlimit(RLIMIT_FSIZE, &rlim); + getrlimit(RLIMIT_FSIZE, &rlim); + if (((unsigned long) rlim.rlim_cur) < + ((unsigned long) rlim.rlim_max)) { + rlim.rlim_cur = rlim.rlim_max; + setrlimit(RLIMIT_FSIZE, &rlim); + } + } +#endif + *channel = io; + return 0; + +cleanup: + if (data) { + free_cache(data); + ext2fs_free_mem(&data); + } + ext2fs_free_mem(&io); + return retval; +} + +static errcode_t unix_close(io_channel channel) +{ + struct unix_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + + if (--channel->refcount > 0) + return 0; + +#ifndef NO_IO_CACHE + retval = flush_cached_blocks(channel, data, 0); +#endif + + if (close(data->dev) < 0) + retval = errno; + free_cache(data); + + ext2fs_free_mem(&channel->private_data); + ext2fs_free_mem(&channel->name); + ext2fs_free_mem(&channel); + return retval; +} + +static errcode_t unix_set_blksize(io_channel channel, int blksize) +{ + struct unix_private_data *data; + errcode_t retval; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + + if (channel->block_size != blksize) { +#ifndef NO_IO_CACHE + if ((retval = flush_cached_blocks(channel, data, 0))) + return retval; +#endif + + channel->block_size = blksize; + free_cache(data); + if ((retval = alloc_cache(channel, data))) + return retval; + } + return 0; +} + + +static errcode_t unix_read_blk(io_channel channel, unsigned long block, + int count, void *buf) +{ + struct unix_private_data *data; + struct unix_cache *cache, *reuse[READ_DIRECT_SIZE]; + errcode_t retval; + char *cp; + int i, j; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + +#ifdef NO_IO_CACHE + return raw_read_blk(channel, data, block, count, buf); +#else + /* + * If we're doing an odd-sized read or a very large read, + * flush out the cache and then do a direct read. + */ + if (count < 0 || count > WRITE_DIRECT_SIZE) { + if ((retval = flush_cached_blocks(channel, data, 0))) + return retval; + return raw_read_blk(channel, data, block, count, buf); + } + + cp = buf; + while (count > 0) { + /* If it's in the cache, use it! */ + if ((cache = find_cached_block(data, block, &reuse[0]))) { +#ifdef DEBUG + printf("Using cached block %d\n", block); +#endif + memcpy(cp, cache->buf, channel->block_size); + count--; + block++; + cp += channel->block_size; + continue; + } + /* + * Find the number of uncached blocks so we can do a + * single read request + */ + for (i=1; i < count; i++) + if (find_cached_block(data, block+i, &reuse[i])) + break; +#ifdef DEBUG + printf("Reading %d blocks starting at %d\n", i, block); +#endif + if ((retval = raw_read_blk(channel, data, block, i, cp))) + return retval; + + /* Save the results in the cache */ + for (j=0; j < i; j++) { + count--; + cache = reuse[j]; + reuse_cache(channel, data, cache, block++); + memcpy(cache->buf, cp, channel->block_size); + cp += channel->block_size; + } + } + return 0; +#endif /* NO_IO_CACHE */ +} + +static errcode_t unix_write_blk(io_channel channel, unsigned long block, + int count, const void *buf) +{ + struct unix_private_data *data; + struct unix_cache *cache, *reuse; + errcode_t retval = 0; + const char *cp; + int writethrough; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + +#ifdef NO_IO_CACHE + return raw_write_blk(channel, data, block, count, buf); +#else + /* + * If we're doing an odd-sized write or a very large write, + * flush out the cache completely and then do a direct write. + */ + if (count < 0 || count > WRITE_DIRECT_SIZE) { + if ((retval = flush_cached_blocks(channel, data, 1))) + return retval; + return raw_write_blk(channel, data, block, count, buf); + } + + /* + * For a moderate-sized multi-block write, first force a write + * if we're in write-through cache mode, and then fill the + * cache with the blocks. + */ + writethrough = channel->flags & CHANNEL_FLAGS_WRITETHROUGH; + if (writethrough) + retval = raw_write_blk(channel, data, block, count, buf); + + cp = buf; + while (count > 0) { + cache = find_cached_block(data, block, &reuse); + if (!cache) { + cache = reuse; + reuse_cache(channel, data, cache, block); + } + memcpy(cache->buf, cp, channel->block_size); + cache->dirty = !writethrough; + count--; + block++; + cp += channel->block_size; + } + return retval; +#endif /* NO_IO_CACHE */ +} + +static errcode_t unix_write_byte(io_channel channel, unsigned long offset, + int size, const void *buf) +{ + struct unix_private_data *data; + errcode_t retval = 0; + ssize_t actual; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + +#ifndef NO_IO_CACHE + /* + * Flush out the cache completely + */ + if ((retval = flush_cached_blocks(channel, data, 1))) + return retval; +#endif + + if (lseek(data->dev, offset + data->offset, SEEK_SET) < 0) + return errno; + + actual = write(data->dev, buf, size); + if (actual != size) + return EXT2_ET_SHORT_WRITE; + + return 0; +} + +/* + * Flush data buffers to disk. + */ +static errcode_t unix_flush(io_channel channel) +{ + struct unix_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + +#ifndef NO_IO_CACHE + retval = flush_cached_blocks(channel, data, 0); +#endif + fsync(data->dev); + return retval; +} + +static errcode_t unix_set_option(io_channel channel, const char *option, + const char *arg) +{ + struct unix_private_data *data; + unsigned long tmp; + char *end; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + + if (!strcmp(option, "offset")) { + if (!arg) + return EXT2_ET_INVALID_ARGUMENT; + + tmp = strtoul(arg, &end, 0); + if (*end) + return EXT2_ET_INVALID_ARGUMENT; + data->offset = tmp; + return 0; + } + return EXT2_ET_INVALID_ARGUMENT; +} diff --git a/e2fsprogs/ext2fs/unlink.c b/e2fsprogs/ext2fs/unlink.c new file mode 100644 index 000000000..83ac2713e --- /dev/null +++ b/e2fsprogs/ext2fs/unlink.c @@ -0,0 +1,100 @@ +/* vi: set sw=4 ts=4: */ +/* + * unlink.c --- delete links in a ext2fs directory + * + * Copyright (C) 1993, 1994, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct link_struct { + const char *name; + int namelen; + ext2_ino_t inode; + int flags; + struct ext2_dir_entry *prev; + int done; +}; + +#ifdef __TURBOC__ +# pragma argsused +#endif +static int unlink_proc(struct ext2_dir_entry *dirent, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct link_struct *ls = (struct link_struct *) priv_data; + struct ext2_dir_entry *prev; + + prev = ls->prev; + ls->prev = dirent; + + if (ls->name) { + if ((dirent->name_len & 0xFF) != ls->namelen) + return 0; + if (strncmp(ls->name, dirent->name, dirent->name_len & 0xFF)) + return 0; + } + if (ls->inode) { + if (dirent->inode != ls->inode) + return 0; + } else { + if (!dirent->inode) + return 0; + } + + if (prev) + prev->rec_len += dirent->rec_len; + else + dirent->inode = 0; + ls->done++; + return DIRENT_ABORT|DIRENT_CHANGED; +} + +#ifdef __TURBOC__ + #pragma argsused +#endif +errcode_t ext2fs_unlink(ext2_filsys fs, ext2_ino_t dir, + const char *name, ext2_ino_t ino, + int flags EXT2FS_ATTR((unused))) +{ + errcode_t retval; + struct link_struct ls; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!name && !ino) + return EXT2_ET_INVALID_ARGUMENT; + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + ls.name = name; + ls.namelen = name ? strlen(name) : 0; + ls.inode = ino; + ls.flags = 0; + ls.done = 0; + ls.prev = 0; + + retval = ext2fs_dir_iterate(fs, dir, DIRENT_FLAG_INCLUDE_EMPTY, + 0, unlink_proc, &ls); + if (retval) + return retval; + + return (ls.done) ? 0 : EXT2_ET_DIR_NO_SPACE; +} + diff --git a/e2fsprogs/ext2fs/valid_blk.c b/e2fsprogs/ext2fs/valid_blk.c new file mode 100644 index 000000000..8ed77ae2a --- /dev/null +++ b/e2fsprogs/ext2fs/valid_blk.c @@ -0,0 +1,57 @@ +/* vi: set sw=4 ts=4: */ +/* + * valid_blk.c --- does the inode have valid blocks? + * + * Copyright 1997 by Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * This function returns 1 if the inode's block entries actually + * contain block entries. + */ +int ext2fs_inode_has_valid_blocks(struct ext2_inode *inode) +{ + /* + * Only directories, regular files, and some symbolic links + * have valid block entries. + */ + if (!LINUX_S_ISDIR(inode->i_mode) && !LINUX_S_ISREG(inode->i_mode) && + !LINUX_S_ISLNK(inode->i_mode)) + return 0; + + /* + * If the symbolic link is a "fast symlink", then the symlink + * target is stored in the block entries. + */ + if (LINUX_S_ISLNK (inode->i_mode)) { + if (inode->i_file_acl == 0) { + /* With no EA block, we can rely on i_blocks */ + if (inode->i_blocks == 0) + return 0; + } else { + /* With an EA block, life gets more tricky */ + if (inode->i_size >= EXT2_N_BLOCKS*4) + return 1; /* definitely using i_block[] */ + if (inode->i_size > 4 && inode->i_block[1] == 0) + return 1; /* definitely using i_block[] */ + return 0; /* Probably a fast symlink */ + } + } + return 1; +} diff --git a/e2fsprogs/ext2fs/version.c b/e2fsprogs/ext2fs/version.c new file mode 100644 index 000000000..d2981e867 --- /dev/null +++ b/e2fsprogs/ext2fs/version.c @@ -0,0 +1,51 @@ +/* vi: set sw=4 ts=4: */ +/* + * version.c --- Return the version of the ext2 library + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +static const char *lib_version = E2FSPROGS_VERSION; +static const char *lib_date = E2FSPROGS_DATE; + +int ext2fs_parse_version_string(const char *ver_string) +{ + const char *cp; + int version = 0; + + for (cp = ver_string; *cp; cp++) { + if (*cp == '.') + continue; + if (!isdigit(*cp)) + break; + version = (version * 10) + (*cp - '0'); + } + return version; +} + + +int ext2fs_get_library_version(const char **ver_string, + const char **date_string) +{ + if (ver_string) + *ver_string = lib_version; + if (date_string) + *date_string = lib_date; + + return ext2fs_parse_version_string(lib_version); +} diff --git a/e2fsprogs/ext2fs/write_bb_file.c b/e2fsprogs/ext2fs/write_bb_file.c new file mode 100644 index 000000000..5b19eefa0 --- /dev/null +++ b/e2fsprogs/ext2fs/write_bb_file.c @@ -0,0 +1,35 @@ +/* vi: set sw=4 ts=4: */ +/* + * write_bb_file.c --- write a list of bad blocks to a FILE * + * + * Copyright (C) 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_write_bb_FILE(ext2_badblocks_list bb_list, + unsigned int flags EXT2FS_ATTR((unused)), + FILE *f) +{ + badblocks_iterate bb_iter; + blk_t blk; + errcode_t retval; + + retval = ext2fs_badblocks_list_iterate_begin(bb_list, &bb_iter); + if (retval) + return retval; + + while (ext2fs_badblocks_list_iterate(bb_iter, &blk)) { + fprintf(f, "%d\n", blk); + } + ext2fs_badblocks_list_iterate_end(bb_iter); + return 0; +} diff --git a/e2fsprogs/fsck.c b/e2fsprogs/fsck.c new file mode 100644 index 000000000..f0c1316a9 --- /dev/null +++ b/e2fsprogs/fsck.c @@ -0,0 +1,1392 @@ +/* vi: set sw=4 ts=4: */ +/* + * pfsck --- A generic, parallelizing front-end for the fsck program. + * It will automatically try to run fsck programs in parallel if the + * devices are on separate spindles. It is based on the same ideas as + * the generic front end for fsck by David Engel and Fred van Kempen, + * but it has been completely rewritten from scratch to support + * parallel execution. + * + * Written by Theodore Ts'o, + * + * Miquel van Smoorenburg (miquels@drinkel.ow.org) 20-Oct-1994: + * o Changed -t fstype to behave like with mount when -A (all file + * systems) or -M (like mount) is specified. + * o fsck looks if it can find the fsck.type program to decide + * if it should ignore the fs type. This way more fsck programs + * can be added without changing this front-end. + * o -R flag skip root file system. + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, + * 2001, 2002, 2003, 2004, 2005 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fsck.h" +#include "blkid/blkid.h" + +#include "e2fsbb.h" + +#include "busybox.h" + +#ifndef _PATH_MNTTAB +#define _PATH_MNTTAB "/etc/fstab" +#endif + +/* + * fsck.h + */ + +#ifndef DEFAULT_FSTYPE +#define DEFAULT_FSTYPE "ext2" +#endif + +#define MAX_DEVICES 32 +#define MAX_ARGS 32 + +/* + * Internal structure for mount tabel entries. + */ + +struct fs_info { + char *device; + char *mountpt; + char *type; + char *opts; + int freq; + int passno; + int flags; + struct fs_info *next; +}; + +#define FLAG_DONE 1 +#define FLAG_PROGRESS 2 + +/* + * Structure to allow exit codes to be stored + */ +struct fsck_instance { + int pid; + int flags; + int exit_status; + time_t start_time; + char * prog; + char * type; + char * device; + char * base_device; + struct fsck_instance *next; +}; + +/* + * base_device.c + * + * Return the "base device" given a particular device; this is used to + * assure that we only fsck one partition on a particular drive at any + * one time. Otherwise, the disk heads will be seeking all over the + * place. If the base device cannot be determined, return NULL. + * + * The base_device() function returns an allocated string which must + * be freed. + * + */ + + +#ifdef CONFIG_FEATURE_DEVFS +/* + * Required for the uber-silly devfs /dev/ide/host1/bus2/target3/lun3 + * pathames. + */ +static const char * const devfs_hier[] = { + "host", "bus", "target", "lun", 0 +}; +#endif + +static char *base_device(const char *device) +{ + char *str, *cp; +#ifdef CONFIG_FEATURE_DEVFS + const char * const *hier; + const char *disk; + int len; +#endif + + cp = str = xstrdup(device); + + /* Skip over /dev/; if it's not present, give up. */ + if (strncmp(cp, "/dev/", 5) != 0) + goto errout; + cp += 5; + + /* + * For md devices, we treat them all as if they were all + * on one disk, since we don't know how to parallelize them. + */ + if (cp[0] == 'm' && cp[1] == 'd') { + *(cp+2) = 0; + return str; + } + + /* Handle DAC 960 devices */ + if (strncmp(cp, "rd/", 3) == 0) { + cp += 3; + if (cp[0] != 'c' || cp[2] != 'd' || + !isdigit(cp[1]) || !isdigit(cp[3])) + goto errout; + *(cp+4) = 0; + return str; + } + + /* Now let's handle /dev/hd* and /dev/sd* devices.... */ + if ((cp[0] == 'h' || cp[0] == 's') && (cp[1] == 'd')) { + cp += 2; + /* If there's a single number after /dev/hd, skip it */ + if (isdigit(*cp)) + cp++; + /* What follows must be an alpha char, or give up */ + if (!isalpha(*cp)) + goto errout; + *(cp + 1) = 0; + return str; + } + +#ifdef CONFIG_FEATURE_DEVFS + /* Now let's handle devfs (ugh) names */ + len = 0; + if (strncmp(cp, "ide/", 4) == 0) + len = 4; + if (strncmp(cp, "scsi/", 5) == 0) + len = 5; + if (len) { + cp += len; + /* + * Now we proceed down the expected devfs hierarchy. + * i.e., .../host1/bus2/target3/lun4/... + * If we don't find the expected token, followed by + * some number of digits at each level, abort. + */ + for (hier = devfs_hier; *hier; hier++) { + len = strlen(*hier); + if (strncmp(cp, *hier, len) != 0) + goto errout; + cp += len; + while (*cp != '/' && *cp != 0) { + if (!isdigit(*cp)) + goto errout; + cp++; + } + cp++; + } + *(cp - 1) = 0; + return str; + } + + /* Now handle devfs /dev/disc or /dev/disk names */ + disk = 0; + if (strncmp(cp, "discs/", 6) == 0) + disk = "disc"; + else if (strncmp(cp, "disks/", 6) == 0) + disk = "disk"; + if (disk) { + cp += 6; + if (strncmp(cp, disk, 4) != 0) + goto errout; + cp += 4; + while (*cp != '/' && *cp != 0) { + if (!isdigit(*cp)) + goto errout; + cp++; + } + *cp = 0; + return str; + } +#endif + +errout: + free(str); + return NULL; +} + + +static const char * const ignored_types[] = { + "ignore", + "iso9660", + "nfs", + "proc", + "sw", + "swap", + "tmpfs", + "devpts", + NULL +}; + +static const char * const really_wanted[] = { + "minix", + "ext2", + "ext3", + "jfs", + "reiserfs", + "xiafs", + "xfs", + NULL +}; + +#define BASE_MD "/dev/md" + +/* + * Global variables for options + */ +static char *devices[MAX_DEVICES]; +static char *args[MAX_ARGS]; +static int num_devices, num_args; + +static int verbose; +static int doall; +static int noexecute; +static int serialize; +static int skip_root; +static int like_mount; +static int notitle; +static int parallel_root; +static int progress; +static int progress_fd; +static int force_all_parallel; +static int num_running; +static int max_running; +static volatile int cancel_requested; +static int kill_sent; +static char *fstype; +static struct fs_info *filesys_info, *filesys_last; +static struct fsck_instance *instance_list; +static char *fsck_path; +static blkid_cache cache; + +static char *string_copy(const char *s) +{ + char *ret; + + if (!s) + return 0; + ret = strdup(s); + return ret; +} + +static int string_to_int(const char *s) +{ + long l; + char *p; + + l = strtol(s, &p, 0); + if (*p || l == LONG_MIN || l == LONG_MAX || l < 0 || l > INT_MAX) + return -1; + else + return (int) l; +} + +static char *skip_over_blank(char *cp) +{ + while (*cp && isspace(*cp)) + cp++; + return cp; +} + +static char *skip_over_word(char *cp) +{ + while (*cp && !isspace(*cp)) + cp++; + return cp; +} + +static void strip_line(char *line) +{ + char *p; + + while (*line) { + p = line + strlen(line) - 1; + if ((*p == '\n') || (*p == '\r')) + *p = 0; + else + break; + } +} + +static char *parse_word(char **buf) +{ + char *word, *next; + + word = *buf; + if (*word == 0) + return 0; + + word = skip_over_blank(word); + next = skip_over_word(word); + if (*next) + *next++ = 0; + *buf = next; + return word; +} + +static void parse_escape(char *word) +{ + char *q, c; + const char *p; + + if (!word) + return; + + for (p = q = word; *p; q++) { + c = *p++; + if (c != '\\') { + *q = c; + } else { + *q = bb_process_escape_sequence(&p); + } + } + *q = 0; +} + +static void free_instance(struct fsck_instance *i) +{ + if (i->prog) + free(i->prog); + if (i->device) + free(i->device); + if (i->base_device) + free(i->base_device); + free(i); + return; +} + +static struct fs_info *create_fs_device(const char *device, const char *mntpnt, + const char *type, const char *opts, + int freq, int passno) +{ + struct fs_info *fs; + + if (!(fs = malloc(sizeof(struct fs_info)))) + return NULL; + + fs->device = string_copy(device); + fs->mountpt = string_copy(mntpnt); + fs->type = string_copy(type); + fs->opts = string_copy(opts ? opts : ""); + fs->freq = freq; + fs->passno = passno; + fs->flags = 0; + fs->next = NULL; + + if (!filesys_info) + filesys_info = fs; + else + filesys_last->next = fs; + filesys_last = fs; + + return fs; +} + + + +static int parse_fstab_line(char *line, struct fs_info **ret_fs) +{ + char *dev, *device, *mntpnt, *type, *opts, *freq, *passno, *cp; + struct fs_info *fs; + + *ret_fs = 0; + strip_line(line); + if ((cp = strchr(line, '#'))) + *cp = 0; /* Ignore everything after the comment char */ + cp = line; + + device = parse_word(&cp); + mntpnt = parse_word(&cp); + type = parse_word(&cp); + opts = parse_word(&cp); + freq = parse_word(&cp); + passno = parse_word(&cp); + + if (!device) + return 0; /* Allow blank lines */ + + if (!mntpnt || !type) + return -1; + + parse_escape(device); + parse_escape(mntpnt); + parse_escape(type); + parse_escape(opts); + parse_escape(freq); + parse_escape(passno); + + dev = blkid_get_devname(cache, device, NULL); + if (dev) + device = dev; + + if (strchr(type, ',')) + type = 0; + + fs = create_fs_device(device, mntpnt, type ? type : "auto", opts, + freq ? atoi(freq) : -1, + passno ? atoi(passno) : -1); + if (dev) + free(dev); + + if (!fs) + return -1; + *ret_fs = fs; + return 0; +} + +static void interpret_type(struct fs_info *fs) +{ + char *t; + + if (strcmp(fs->type, "auto") != 0) + return; + t = blkid_get_tag_value(cache, "TYPE", fs->device); + if (t) { + free(fs->type); + fs->type = t; + } +} + +/* + * Load the filesystem database from /etc/fstab + */ +static void load_fs_info(const char *filename) +{ + FILE *f; + char buf[1024]; + int lineno = 0; + int old_fstab = 1; + struct fs_info *fs; + + if ((f = fopen(filename, "r")) == NULL) { + bb_perror_msg("WARNING: cannot open %s", filename); + return; + } + while (!feof(f)) { + lineno++; + if (!fgets(buf, sizeof(buf), f)) + break; + buf[sizeof(buf)-1] = 0; + if (parse_fstab_line(buf, &fs) < 0) { + bb_error_msg("WARNING: bad format " + "on line %d of %s\n", lineno, filename); + continue; + } + if (!fs) + continue; + if (fs->passno < 0) + fs->passno = 0; + else + old_fstab = 0; + } + + fclose(f); + + if (old_fstab) { + fputs("\007\007\007" + "WARNING: Your /etc/fstab does not contain the fsck passno\n" + " field. I will kludge around things for you, but you\n" + " should fix your /etc/fstab file as soon as you can.\n\n", stderr); + + for (fs = filesys_info; fs; fs = fs->next) { + fs->passno = 1; + } + } +} + +/* Lookup filesys in /etc/fstab and return the corresponding entry. */ +static struct fs_info *lookup(char *filesys) +{ + struct fs_info *fs; + + /* No filesys name given. */ + if (filesys == NULL) + return NULL; + + for (fs = filesys_info; fs; fs = fs->next) { + if (!strcmp(filesys, fs->device) || + (fs->mountpt && !strcmp(filesys, fs->mountpt))) + break; + } + + return fs; +} + +/* Find fsck program for a given fs type. */ +static char *find_fsck(char *type) +{ + char *s; + const char *tpl; + char *p = string_copy(fsck_path); + struct stat st; + + /* Are we looking for a program or just a type? */ + tpl = (strncmp(type, "fsck.", 5) ? "%s/fsck.%s" : "%s/%s"); + + for(s = strtok(p, ":"); s; s = strtok(NULL, ":")) { + s = xasprintf(tpl, s, type); + if (stat(s, &st) == 0) break; + free(s); + } + free(p); + return s; +} + +static int progress_active(void) +{ + struct fsck_instance *inst; + + for (inst = instance_list; inst; inst = inst->next) { + if (inst->flags & FLAG_DONE) + continue; + if (inst->flags & FLAG_PROGRESS) + return 1; + } + return 0; +} + +/* + * Execute a particular fsck program, and link it into the list of + * child processes we are waiting for. + */ +static int execute(const char *type, const char *device, const char *mntpt, + int interactive) +{ + char *s, *argv[80]; + char *prog; + int argc, i; + struct fsck_instance *inst, *p; + pid_t pid; + + inst = malloc(sizeof(struct fsck_instance)); + if (!inst) + return ENOMEM; + memset(inst, 0, sizeof(struct fsck_instance)); + + prog = xasprintf("fsck.%s", type); + argv[0] = prog; + argc = 1; + + for (i=0; i flags |= FLAG_PROGRESS; + } + } + + argv[argc++] = string_copy(device); + argv[argc] = 0; + + s = find_fsck(prog); + if (s == NULL) { + bb_error_msg("%s: not found", prog); + return ENOENT; + } + + if (verbose || noexecute) { + printf("[%s (%d) -- %s] ", s, num_running, + mntpt ? mntpt : device); + for (i=0; i < argc; i++) + printf("%s ", argv[i]); + puts(""); + } + + /* Fork and execute the correct program. */ + if (noexecute) + pid = -1; + else if ((pid = fork()) < 0) { + perror("fork"); + return errno; + } else if (pid == 0) { + if (!interactive) + close(0); + (void) execv(s, argv); + bb_perror_msg_and_die("%s", argv[0]); + } + + for (i = 1; i < argc; i++) + free(argv[i]); + + free(s); + inst->pid = pid; + inst->prog = prog; + inst->type = string_copy(type); + inst->device = string_copy(device); + inst->base_device = base_device(device); + inst->start_time = time(0); + inst->next = NULL; + + /* + * Find the end of the list, so we add the instance on at the end. + */ + for (p = instance_list; p && p->next; p = p->next); + + if (p) + p->next = inst; + else + instance_list = inst; + + return 0; +} + +/* + * Send a signal to all outstanding fsck child processes + */ +static int kill_all(int signum) +{ + struct fsck_instance *inst; + int n = 0; + + for (inst = instance_list; inst; inst = inst->next) { + if (inst->flags & FLAG_DONE) + continue; + kill(inst->pid, signum); + n++; + } + return n; +} + +/* + * Wait for one child process to exit; when it does, unlink it from + * the list of executing child processes, and return it. + */ +static struct fsck_instance *wait_one(int flags) +{ + int status; + int sig; + struct fsck_instance *inst, *inst2, *prev; + pid_t pid; + + if (!instance_list) + return NULL; + + if (noexecute) { + inst = instance_list; + prev = 0; +#ifdef RANDOM_DEBUG + while (inst->next && (random() & 1)) { + prev = inst; + inst = inst->next; + } +#endif + inst->exit_status = 0; + goto ret_inst; + } + + /* + * gcc -Wall fails saving throw against stupidity + * (inst and prev are thought to be uninitialized variables) + */ + inst = prev = NULL; + + do { + pid = waitpid(-1, &status, flags); + if (cancel_requested && !kill_sent) { + kill_all(SIGTERM); + kill_sent++; + } + if ((pid == 0) && (flags & WNOHANG)) + return NULL; + if (pid < 0) { + if ((errno == EINTR) || (errno == EAGAIN)) + continue; + if (errno == ECHILD) { + bb_error_msg("wait: no more child process?!?"); + return NULL; + } + perror("wait"); + continue; + } + for (prev = 0, inst = instance_list; + inst; + prev = inst, inst = inst->next) { + if (inst->pid == pid) + break; + } + } while (!inst); + + if (WIFEXITED(status)) + status = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) { + sig = WTERMSIG(status); + if (sig == SIGINT) { + status = EXIT_UNCORRECTED; + } else { + printf("Warning... %s for device %s exited " + "with signal %d.\n", + inst->prog, inst->device, sig); + status = EXIT_ERROR; + } + } else { + printf("%s %s: status is %x, should never happen.\n", + inst->prog, inst->device, status); + status = EXIT_ERROR; + } + inst->exit_status = status; + if (progress && (inst->flags & FLAG_PROGRESS) && + !progress_active()) { + for (inst2 = instance_list; inst2; inst2 = inst2->next) { + if (inst2->flags & FLAG_DONE) + continue; + if (strcmp(inst2->type, "ext2") && + strcmp(inst2->type, "ext3")) + continue; + /* + * If we've just started the fsck, wait a tiny + * bit before sending the kill, to give it + * time to set up the signal handler + */ + if (inst2->start_time < time(0)+2) { + if (fork() == 0) { + sleep(1); + kill(inst2->pid, SIGUSR1); + exit(0); + } + } else + kill(inst2->pid, SIGUSR1); + inst2->flags |= FLAG_PROGRESS; + break; + } + } +ret_inst: + if (prev) + prev->next = inst->next; + else + instance_list = inst->next; + if (verbose > 1) + printf("Finished with %s (exit status %d)\n", + inst->device, inst->exit_status); + num_running--; + return inst; +} + +#define FLAG_WAIT_ALL 0 +#define FLAG_WAIT_ATLEAST_ONE 1 +/* + * Wait until all executing child processes have exited; return the + * logical OR of all of their exit code values. + */ +static int wait_many(int flags) +{ + struct fsck_instance *inst; + int global_status = 0; + int wait_flags = 0; + + while ((inst = wait_one(wait_flags))) { + global_status |= inst->exit_status; + free_instance(inst); +#ifdef RANDOM_DEBUG + if (noexecute && (flags & WNOHANG) && !(random() % 3)) + break; +#endif + if (flags & FLAG_WAIT_ATLEAST_ONE) + wait_flags = WNOHANG; + } + return global_status; +} + +/* + * Run the fsck program on a particular device + * + * If the type is specified using -t, and it isn't prefixed with "no" + * (as in "noext2") and only one filesystem type is specified, then + * use that type regardless of what is specified in /etc/fstab. + * + * If the type isn't specified by the user, then use either the type + * specified in /etc/fstab, or DEFAULT_FSTYPE. + */ +static void fsck_device(struct fs_info *fs, int interactive) +{ + const char *type; + int retval; + + interpret_type(fs); + + if (strcmp(fs->type, "auto") != 0) + type = fs->type; + else if (fstype && strncmp(fstype, "no", 2) && + strncmp(fstype, "opts=", 5) && strncmp(fstype, "loop", 4) && + !strchr(fstype, ',')) + type = fstype; + else + type = DEFAULT_FSTYPE; + + num_running++; + retval = execute(type, fs->device, fs->mountpt, interactive); + if (retval) { + bb_error_msg("error %d while executing fsck.%s for %s", + retval, type, fs->device); + num_running--; + } +} + + +/* + * Deal with the fsck -t argument. + */ +struct fs_type_compile { + char **list; + int *type; + int negate; +} fs_type_compiled; + +#define FS_TYPE_NORMAL 0 +#define FS_TYPE_OPT 1 +#define FS_TYPE_NEGOPT 2 + +static const char fs_type_syntax_error[] = +"Either all or none of the filesystem types passed to -t must be prefixed\n" + "with 'no' or '!'."; + +static void compile_fs_type(char *fs_type, struct fs_type_compile *cmp) +{ + char *cp, *list, *s; + int num = 2; + int negate, first_negate = 1; + + if (fs_type) { + for (cp=fs_type; *cp; cp++) { + if (*cp == ',') + num++; + } + } + + cmp->list = xzalloc(num * sizeof(char *)); + cmp->type = xzalloc(num * sizeof(int)); + cmp->negate = 0; + + if (!fs_type) + return; + + list = string_copy(fs_type); + num = 0; + s = strtok(list, ","); + while(s) { + negate = 0; + if (strncmp(s, "no", 2) == 0) { + s += 2; + negate = 1; + } else if (*s == '!') { + s++; + negate = 1; + } + if (strcmp(s, "loop") == 0) + /* loop is really short-hand for opts=loop */ + goto loop_special_case; + else if (strncmp(s, "opts=", 5) == 0) { + s += 5; + loop_special_case: + cmp->type[num] = negate ? FS_TYPE_NEGOPT : FS_TYPE_OPT; + } else { + if (first_negate) { + cmp->negate = negate; + first_negate = 0; + } + if ((negate && !cmp->negate) || + (!negate && cmp->negate)) { + bb_error_msg_and_die("%s", fs_type_syntax_error); + } + } + cmp->list[num++] = string_copy(s); + s = strtok(NULL, ","); + } + free(list); +} + +/* + * This function returns true if a particular option appears in a + * comma-delimited options list + */ +static int opt_in_list(char *opt, char *optlist) +{ + char *list, *s; + + if (!optlist) + return 0; + list = string_copy(optlist); + + s = strtok(list, ","); + while(s) { + if (strcmp(s, opt) == 0) { + free(list); + return 1; + } + s = strtok(NULL, ","); + } + free(list); + return 0; +} + +/* See if the filesystem matches the criteria given by the -t option */ +static int fs_match(struct fs_info *fs, struct fs_type_compile *cmp) +{ + int n, ret = 0, checked_type = 0; + char *cp; + + if (cmp->list == 0 || cmp->list[0] == 0) + return 1; + + for (n=0; (cp = cmp->list[n]); n++) { + switch (cmp->type[n]) { + case FS_TYPE_NORMAL: + checked_type++; + if (strcmp(cp, fs->type) == 0) { + ret = 1; + } + break; + case FS_TYPE_NEGOPT: + if (opt_in_list(cp, fs->opts)) + return 0; + break; + case FS_TYPE_OPT: + if (!opt_in_list(cp, fs->opts)) + return 0; + break; + } + } + if (checked_type == 0) + return 1; + return (cmp->negate ? !ret : ret); +} + +/* Check if we should ignore this filesystem. */ +static int ignore(struct fs_info *fs) +{ + int wanted; + char *s; + + /* + * If the pass number is 0, ignore it. + */ + if (fs->passno == 0) + return 1; + + interpret_type(fs); + + /* + * If a specific fstype is specified, and it doesn't match, + * ignore it. + */ + if (!fs_match(fs, &fs_type_compiled)) return 1; + + /* Are we ignoring this type? */ + if (index_in_str_array(ignored_types, fs->type) >= 0) + return 1; + + /* Do we really really want to check this fs? */ + wanted = index_in_str_array(really_wanted, fs->type) >= 0; + + /* See if the program is available. */ + s = find_fsck(fs->type); + if (s == NULL) { + if (wanted) + bb_error_msg("cannot check %s: fsck.%s not found", + fs->device, fs->type); + return 1; + } + free(s); + + /* We can and want to check this file system type. */ + return 0; +} + +/* + * Returns TRUE if a partition on the same disk is already being + * checked. + */ +static int device_already_active(char *device) +{ + struct fsck_instance *inst; + char *base; + + if (force_all_parallel) + return 0; + +#ifdef BASE_MD + /* Don't check a soft raid disk with any other disk */ + if (instance_list && + (!strncmp(instance_list->device, BASE_MD, sizeof(BASE_MD)-1) || + !strncmp(device, BASE_MD, sizeof(BASE_MD)-1))) + return 1; +#endif + + base = base_device(device); + /* + * If we don't know the base device, assume that the device is + * already active if there are any fsck instances running. + */ + if (!base) + return (instance_list != 0); + for (inst = instance_list; inst; inst = inst->next) { + if (!inst->base_device || !strcmp(base, inst->base_device)) { + free(base); + return 1; + } + } + free(base); + return 0; +} + +/* Check all file systems, using the /etc/fstab table. */ +static int check_all(void) +{ + struct fs_info *fs = NULL; + int status = EXIT_OK; + int not_done_yet = 1; + int passno = 1; + int pass_done; + + if (verbose) + fputs("Checking all file systems.\n", stdout); + + /* + * Do an initial scan over the filesystem; mark filesystems + * which should be ignored as done, and resolve any "auto" + * filesystem types (done as a side-effect of calling ignore()). + */ + for (fs = filesys_info; fs; fs = fs->next) { + if (ignore(fs)) + fs->flags |= FLAG_DONE; + } + + /* + * Find and check the root filesystem. + */ + if (!parallel_root) { + for (fs = filesys_info; fs; fs = fs->next) { + if (!strcmp(fs->mountpt, "/")) + break; + } + if (fs) { + if (!skip_root && !ignore(fs)) { + fsck_device(fs, 1); + status |= wait_many(FLAG_WAIT_ALL); + if (status > EXIT_NONDESTRUCT) + return status; + } + fs->flags |= FLAG_DONE; + } + } + /* + * This is for the bone-headed user who enters the root + * filesystem twice. Skip root will skep all root entries. + */ + if (skip_root) + for (fs = filesys_info; fs; fs = fs->next) + if (!strcmp(fs->mountpt, "/")) + fs->flags |= FLAG_DONE; + + while (not_done_yet) { + not_done_yet = 0; + pass_done = 1; + + for (fs = filesys_info; fs; fs = fs->next) { + if (cancel_requested) + break; + if (fs->flags & FLAG_DONE) + continue; + /* + * If the filesystem's pass number is higher + * than the current pass number, then we don't + * do it yet. + */ + if (fs->passno > passno) { + not_done_yet++; + continue; + } + /* + * If a filesystem on a particular device has + * already been spawned, then we need to defer + * this to another pass. + */ + if (device_already_active(fs->device)) { + pass_done = 0; + continue; + } + /* + * Spawn off the fsck process + */ + fsck_device(fs, serialize); + fs->flags |= FLAG_DONE; + + /* + * Only do one filesystem at a time, or if we + * have a limit on the number of fsck's extant + * at one time, apply that limit. + */ + if (serialize || + (max_running && (num_running >= max_running))) { + pass_done = 0; + break; + } + } + if (cancel_requested) + break; + if (verbose > 1) + printf("--waiting-- (pass %d)\n", passno); + status |= wait_many(pass_done ? FLAG_WAIT_ALL : + FLAG_WAIT_ATLEAST_ONE); + if (pass_done) { + if (verbose > 1) + printf("----------------------------------\n"); + passno++; + } else + not_done_yet++; + } + if (cancel_requested && !kill_sent) { + kill_all(SIGTERM); + kill_sent++; + } + status |= wait_many(FLAG_WAIT_ATLEAST_ONE); + return status; +} + +static void signal_cancel(int sig FSCK_ATTR((unused))) +{ + cancel_requested++; +} + +static void PRS(int argc, char *argv[]) +{ + int i, j; + char *arg, *dev, *tmp = 0; + char options[128]; + int opt = 0; + int opts_for_fsck = 0; + struct sigaction sa; + + /* + * Set up signal action + */ + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = signal_cancel; + sigaction(SIGINT, &sa, 0); + sigaction(SIGTERM, &sa, 0); + + num_devices = 0; + num_args = 0; + instance_list = 0; + + for (i=1; i < argc; i++) { + arg = argv[i]; + if (!arg) + continue; + if ((arg[0] == '/' && !opts_for_fsck) || strchr(arg, '=')) { + if (num_devices >= MAX_DEVICES) { + bb_error_msg_and_die("too many devices"); + } + dev = blkid_get_devname(cache, arg, NULL); + if (!dev && strchr(arg, '=')) { + /* + * Check to see if we failed because + * /proc/partitions isn't found. + */ + if (access("/proc/partitions", R_OK) < 0) { + bb_perror_msg_and_die("cannot open /proc/partitions " + "(is /proc mounted?)"); + } + /* + * Check to see if this is because + * we're not running as root + */ + if (geteuid()) + bb_error_msg_and_die( + "must be root to scan for matching filesystems: %s\n", arg); + else + bb_error_msg_and_die( + "cannot find matching filesystem: %s", arg); + } + devices[num_devices++] = dev ? dev : string_copy(arg); + continue; + } + if (arg[0] != '-' || opts_for_fsck) { + if (num_args >= MAX_ARGS) { + bb_error_msg_and_die("too many arguments"); + } + args[num_args++] = string_copy(arg); + continue; + } + for (j=1; arg[j]; j++) { + if (opts_for_fsck) { + options[++opt] = arg[j]; + continue; + } + switch (arg[j]) { + case 'A': + doall++; + break; + case 'C': + progress++; + if (arg[j+1]) { + progress_fd = string_to_int(arg+j+1); + if (progress_fd < 0) + progress_fd = 0; + else + goto next_arg; + } else if ((i+1) < argc && + !strncmp(argv[i+1], "-", 1) == 0) { + progress_fd = string_to_int(argv[i]); + if (progress_fd < 0) + progress_fd = 0; + else { + goto next_arg; + i++; + } + } + break; + case 'V': + verbose++; + break; + case 'N': + noexecute++; + break; + case 'R': + skip_root++; + break; + case 'T': + notitle++; + break; + case 'M': + like_mount++; + break; + case 'P': + parallel_root++; + break; + case 's': + serialize++; + break; + case 't': + tmp = 0; + if (fstype) + bb_show_usage(); + if (arg[j+1]) + tmp = arg+j+1; + else if ((i+1) < argc) + tmp = argv[++i]; + else + bb_show_usage(); + fstype = string_copy(tmp); + compile_fs_type(fstype, &fs_type_compiled); + goto next_arg; + case '-': + opts_for_fsck++; + break; + case '?': + bb_show_usage(); + break; + default: + options[++opt] = arg[j]; + break; + } + } + next_arg: + if (opt) { + options[0] = '-'; + options[++opt] = '\0'; + if (num_args >= MAX_ARGS) { + bb_error_msg("too many arguments"); + } + args[num_args++] = string_copy(options); + opt = 0; + } + } + if (getenv("FSCK_FORCE_ALL_PARALLEL")) + force_all_parallel++; + if ((tmp = getenv("FSCK_MAX_INST"))) + max_running = atoi(tmp); +} + +int fsck_main(int argc, char *argv[]) +{ + int i, status = 0; + int interactive = 0; + const char *fstab; + struct fs_info *fs; + + setvbuf(stdout, NULL, _IONBF, BUFSIZ); + setvbuf(stderr, NULL, _IONBF, BUFSIZ); + + blkid_get_cache(&cache, NULL); + PRS(argc, argv); + + if (!notitle) + printf("fsck %s (%s)\n", E2FSPROGS_VERSION, E2FSPROGS_DATE); + + fstab = getenv("FSTAB_FILE"); + if (!fstab) + fstab = _PATH_MNTTAB; + load_fs_info(fstab); + + fsck_path = e2fs_set_sbin_path(); + + if ((num_devices == 1) || (serialize)) + interactive = 1; + + /* If -A was specified ("check all"), do that! */ + if (doall) + return check_all(); + + if (num_devices == 0) { + serialize++; + interactive++; + return check_all(); + } + for (i = 0 ; i < num_devices; i++) { + if (cancel_requested) { + if (!kill_sent) { + kill_all(SIGTERM); + kill_sent++; + } + break; + } + fs = lookup(devices[i]); + if (!fs) { + fs = create_fs_device(devices[i], 0, "auto", + 0, -1, -1); + if (!fs) + continue; + } + fsck_device(fs, interactive); + if (serialize || + (max_running && (num_running >= max_running))) { + struct fsck_instance *inst; + + inst = wait_one(0); + if (inst) { + status |= inst->exit_status; + free_instance(inst); + } + if (verbose > 1) + printf("----------------------------------\n"); + } + } + status |= wait_many(FLAG_WAIT_ALL); + blkid_put_cache(cache); + return status; +} diff --git a/e2fsprogs/fsck.h b/e2fsprogs/fsck.h new file mode 100644 index 000000000..2ca2af7da --- /dev/null +++ b/e2fsprogs/fsck.h @@ -0,0 +1,16 @@ +/* vi: set sw=4 ts=4: */ +/* + * fsck.h + */ + +#define FSCK_ATTR(x) __attribute__(x) + +#define EXIT_OK 0 +#define EXIT_NONDESTRUCT 1 +#define EXIT_DESTRUCT 2 +#define EXIT_UNCORRECTED 4 +#define EXIT_ERROR 8 +#define EXIT_USAGE 16 +#define FSCK_CANCELED 32 /* Aborted with a signal or ^C */ + +extern char *e2fs_set_sbin_path(void); diff --git a/e2fsprogs/lsattr.c b/e2fsprogs/lsattr.c new file mode 100644 index 000000000..eb28fcbab --- /dev/null +++ b/e2fsprogs/lsattr.c @@ -0,0 +1,128 @@ +/* vi: set sw=4 ts=4: */ +/* + * lsattr.c - List file attributes on an ext2 file system + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + * 93/11/13 - Replace stat() calls by lstat() to avoid loops + * 94/02/27 - Integrated in Ted's distribution + * 98/12/29 - Display version info only when -V specified (G M Sipe) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ext2fs/ext2_fs.h" +#include "e2fsbb.h" +#include "e2p/e2p.h" + +#define OPT_RECUR 1 +#define OPT_ALL 2 +#define OPT_DIRS_OPT 4 +#define OPT_PF_LONG 8 +#define OPT_GENERATION 16 +static int flags; + +static void list_attributes(const char *name) +{ + unsigned long fsflags; + unsigned long generation; + + if (fgetflags(name, &fsflags) == -1) + goto read_err; + if (flags & OPT_GENERATION) { + if (fgetversion(name, &generation) == -1) + goto read_err; + printf("%5lu ", generation); + } + + if (flags & OPT_PF_LONG) { + printf("%-28s ", name); + print_flags(stdout, fsflags, PFOPT_LONG); + puts(""); + } else { + print_flags(stdout, fsflags, 0); + printf(" %s\n", name); + } + + return; +read_err: + bb_perror_msg("reading %s", name); +} + +static int lsattr_dir_proc(const char *, struct dirent *, void *); + +static void lsattr_args(const char *name) +{ + struct stat st; + + if (lstat(name, &st) == -1) { + bb_perror_msg("stating %s", name); + } else { + if (S_ISDIR(st.st_mode) && !(flags & OPT_DIRS_OPT)) + iterate_on_dir(name, lsattr_dir_proc, NULL); + else + list_attributes(name); + } +} + +static int lsattr_dir_proc(const char *dir_name, struct dirent *de, + void *private) +{ + struct stat st; + char *path; + + path = concat_path_file(dir_name, de->d_name); + + if (lstat(path, &st) == -1) + bb_perror_msg(path); + else { + if (de->d_name[0] != '.' || (flags & OPT_ALL)) { + list_attributes(path); + if (S_ISDIR(st.st_mode) && (flags & OPT_RECUR) && + (de->d_name[0] != '.' && (de->d_name[1] != '\0' || + (de->d_name[1] != '.' && de->d_name[2] != '\0')))) { + printf("\n%s:\n", path); + iterate_on_dir(path, lsattr_dir_proc, NULL); + puts(""); + } + } + } + + free(path); + + return 0; +} + +int lsattr_main(int argc, char **argv) +{ + int i; + + flags = getopt32(argc, argv, "Radlv"); + + if (optind > argc - 1) + lsattr_args("."); + else + for (i = optind; i < argc; i++) + lsattr_args(argv[i]); + + return EXIT_SUCCESS; +} diff --git a/e2fsprogs/mke2fs.c b/e2fsprogs/mke2fs.c new file mode 100644 index 000000000..f25ecfb6c --- /dev/null +++ b/e2fsprogs/mke2fs.c @@ -0,0 +1,1336 @@ +/* vi: set sw=4 ts=4: */ +/* + * mke2fs.c - Make a ext2fs filesystem. + * + * Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, + * 2003, 2004, 2005 by Theodore Ts'o. + * + * This file may be redistributed under the terms of the GNU Public + * License. + */ + +/* Usage: mke2fs [options] device + * + * The device may be a block device or a image of one, but this isn't + * enforced (but it's not much fun on a character device :-). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "e2fsbb.h" +#include "ext2fs/ext2_fs.h" +#include "uuid/uuid.h" +#include "e2p/e2p.h" +#include "ext2fs/ext2fs.h" +#include "util.h" + +#define STRIDE_LENGTH 8 + +#ifndef __sparc__ +#define ZAP_BOOTBLOCK +#endif + +static const char * device_name; + +/* Command line options */ +static int cflag; +static int quiet; +static int super_only; +static int force; +static int noaction; +static int journal_size; +static int journal_flags; +static const char *bad_blocks_filename; +static __u32 fs_stride; + +static struct ext2_super_block param; +static char *creator_os; +static char *volume_label; +static char *mount_dir; +static char *journal_device = NULL; +static int sync_kludge; /* Set using the MKE2FS_SYNC env. option */ + +static int sys_page_size = 4096; +static int linux_version_code = 0; + +static int int_log2(int arg) +{ + int l = 0; + + arg >>= 1; + while (arg) { + l++; + arg >>= 1; + } + return l; +} + +static int int_log10(unsigned int arg) +{ + int l; + + for (l=0; arg ; l++) + arg = arg / 10; + return l; +} + +/* + * This function sets the default parameters for a filesystem + * + * The type is specified by the user. The size is the maximum size + * (in megabytes) for which a set of parameters applies, with a size + * of zero meaning that it is the default parameter for the type. + * Note that order is important in the table below. + */ +#define DEF_MAX_BLOCKSIZE -1 +static const char default_str[] = "default"; +struct mke2fs_defaults { + const char *type; + int size; + int blocksize; + int inode_ratio; +}; + +static const struct mke2fs_defaults settings[] = { + { default_str, 0, 4096, 8192 }, + { default_str, 512, 1024, 4096 }, + { default_str, 3, 1024, 8192 }, + { "journal", 0, 4096, 8192 }, + { "news", 0, 4096, 4096 }, + { "largefile", 0, 4096, 1024 * 1024 }, + { "largefile4", 0, 4096, 4096 * 1024 }, + { 0, 0, 0, 0}, +}; + +static void set_fs_defaults(const char *fs_type, + struct ext2_super_block *super, + int blocksize, int sector_size, + int *inode_ratio) +{ + int megs; + int ratio = 0; + const struct mke2fs_defaults *p; + int use_bsize = 1024; + + megs = super->s_blocks_count * (EXT2_BLOCK_SIZE(super) / 1024) / 1024; + if (inode_ratio) + ratio = *inode_ratio; + if (!fs_type) + fs_type = default_str; + for (p = settings; p->type; p++) { + if ((strcmp(p->type, fs_type) != 0) && + (strcmp(p->type, default_str) != 0)) + continue; + if ((p->size != 0) && (megs > p->size)) + continue; + if (ratio == 0) + *inode_ratio = p->inode_ratio < blocksize ? + blocksize : p->inode_ratio; + use_bsize = p->blocksize; + } + if (blocksize <= 0) { + if (use_bsize == DEF_MAX_BLOCKSIZE) { + use_bsize = sys_page_size; + if ((linux_version_code < (2*65536 + 6*256)) && + (use_bsize > 4096)) + use_bsize = 4096; + } + if (sector_size && use_bsize < sector_size) + use_bsize = sector_size; + if ((blocksize < 0) && (use_bsize < (-blocksize))) + use_bsize = -blocksize; + blocksize = use_bsize; + super->s_blocks_count /= blocksize / 1024; + } + super->s_log_frag_size = super->s_log_block_size = + int_log2(blocksize >> EXT2_MIN_BLOCK_LOG_SIZE); +} + + +/* + * Helper function for read_bb_file and test_disk + */ +static void invalid_block(ext2_filsys fs EXT2FS_ATTR((unused)), blk_t blk) +{ + bb_error_msg("Bad block %u out of range; ignored", blk); + return; +} + +/* + * Busybox stuff + */ +static void mke2fs_error_msg_and_die(int retval, const char *fmt, ...)__attribute__ ((format (printf, 2, 3))); +static void mke2fs_error_msg_and_die(int retval, const char *fmt, ...) +{ + va_list ap; + + if (retval) { + va_start(ap, fmt); + fprintf(stderr,"\nCould not "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(EXIT_FAILURE); + } +} + +static void mke2fs_verbose(const char *fmt, ...)__attribute__ ((format (printf, 1, 2))); +static void mke2fs_verbose(const char *fmt, ...) +{ + va_list ap; + + if (!quiet) { + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + fflush(stdout); + va_end(ap); + } +} + +static void mke2fs_verbose_done(void) +{ + mke2fs_verbose("done\n"); +} + +static void mke2fs_warning_msg(int retval, char *fmt, ... )__attribute__ ((format (printf, 2, 3))); +static void mke2fs_warning_msg(int retval, char *fmt, ... ) +{ + va_list ap; + + if (retval) { + va_start(ap, fmt); + fprintf(stderr,"\nWarning: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + } +} + +/* + * Reads the bad blocks list from a file + */ +static void read_bb_file(ext2_filsys fs, badblocks_list *bb_list, + const char *bad_blocks_file) +{ + FILE *f; + errcode_t retval; + + f = xfopen(bad_blocks_file, "r"); + retval = ext2fs_read_bb_FILE(fs, f, bb_list, invalid_block); + fclose (f); + mke2fs_error_msg_and_die(retval, "read bad blocks from list"); +} + +/* + * Runs the badblocks program to test the disk + */ +static void test_disk(ext2_filsys fs, badblocks_list *bb_list) +{ + FILE *f; + errcode_t retval; + char buf[1024]; + + sprintf(buf, "badblocks -b %d %s%s%s %d", fs->blocksize, + quiet ? "" : "-s ", (cflag > 1) ? "-w " : "", + fs->device_name, fs->super->s_blocks_count); + mke2fs_verbose("Running command: %s\n", buf); + f = popen(buf, "r"); + if (!f) { + bb_perror_msg_and_die("cannot run '%s'", buf); + } + retval = ext2fs_read_bb_FILE(fs, f, bb_list, invalid_block); + pclose(f); + mke2fs_error_msg_and_die(retval, "read bad blocks from program"); +} + +static void handle_bad_blocks(ext2_filsys fs, badblocks_list bb_list) +{ + dgrp_t i; + blk_t j; + unsigned must_be_good; + blk_t blk; + badblocks_iterate bb_iter; + errcode_t retval; + blk_t group_block; + int group; + int group_bad; + + if (!bb_list) + return; + + /* + * The primary superblock and group descriptors *must* be + * good; if not, abort. + */ + must_be_good = fs->super->s_first_data_block + 1 + fs->desc_blocks; + for (i = fs->super->s_first_data_block; i <= must_be_good; i++) { + if (ext2fs_badblocks_list_test(bb_list, i)) { + bb_error_msg_and_die( + "Block %d in primary superblock/group descriptor area bad\n" + "Blocks %d through %d must be good in order to build a filesystem\n" + "Aborting ...", i, fs->super->s_first_data_block, must_be_good); + } + } + + /* + * See if any of the bad blocks are showing up in the backup + * superblocks and/or group descriptors. If so, issue a + * warning and adjust the block counts appropriately. + */ + group_block = fs->super->s_first_data_block + + fs->super->s_blocks_per_group; + + for (i = 1; i < fs->group_desc_count; i++) { + group_bad = 0; + for (j=0; j < fs->desc_blocks+1; j++) { + if (ext2fs_badblocks_list_test(bb_list, + group_block + j)) { + mke2fs_warning_msg(!group_bad, + "the backup superblock/group descriptors at block %d contain\n" + "bad blocks\n", group_block); + group_bad++; + group = ext2fs_group_of_blk(fs, group_block+j); + fs->group_desc[group].bg_free_blocks_count++; + fs->super->s_free_blocks_count++; + } + } + group_block += fs->super->s_blocks_per_group; + } + + /* + * Mark all the bad blocks as used... + */ + retval = ext2fs_badblocks_list_iterate_begin(bb_list, &bb_iter); + mke2fs_error_msg_and_die(retval, "mark bad blocks as used"); + + while (ext2fs_badblocks_list_iterate(bb_iter, &blk)) + ext2fs_mark_block_bitmap(fs->block_map, blk); + ext2fs_badblocks_list_iterate_end(bb_iter); +} + +/* + * These functions implement a generalized progress meter. + */ +struct progress_struct { + char format[20]; + char backup[80]; + __u32 max; + int skip_progress; +}; + +static void progress_init(struct progress_struct *progress, + const char *label,__u32 max) +{ + int i; + + memset(progress, 0, sizeof(struct progress_struct)); + if (quiet) + return; + + /* + * Figure out how many digits we need + */ + i = int_log10(max); + sprintf(progress->format, "%%%dd/%%%dld", i, i); + memset(progress->backup, '\b', sizeof(progress->backup)-1); + progress->backup[sizeof(progress->backup)-1] = 0; + if ((2*i)+1 < (int) sizeof(progress->backup)) + progress->backup[(2*i)+1] = 0; + progress->max = max; + + progress->skip_progress = 0; + if (getenv("MKE2FS_SKIP_PROGRESS")) + progress->skip_progress++; + + fputs(label, stdout); + fflush(stdout); +} + +static void progress_update(struct progress_struct *progress, __u32 val) +{ + if ((progress->format[0] == 0) || progress->skip_progress) + return; + printf(progress->format, val, progress->max); + fputs(progress->backup, stdout); +} + +static void progress_close(struct progress_struct *progress) +{ + if (progress->format[0] == 0) + return; + printf("%-28s\n", "done"); +} + + +/* + * Helper function which zeros out _num_ blocks starting at _blk_. In + * case of an error, the details of the error is returned via _ret_blk_ + * and _ret_count_ if they are non-NULL pointers. Returns 0 on + * success, and an error code on an error. + * + * As a special case, if the first argument is NULL, then it will + * attempt to free the static zeroizing buffer. (This is to keep + * programs that check for memory leaks happy.) + */ +static errcode_t zero_blocks(ext2_filsys fs, blk_t blk, int num, + struct progress_struct *progress, + blk_t *ret_blk, int *ret_count) +{ + int j, count, next_update, next_update_incr; + static char *buf; + errcode_t retval; + + /* If fs is null, clean up the static buffer and return */ + if (!fs) { + if (buf) { + free(buf); + buf = 0; + } + return 0; + } + /* Allocate the zeroizing buffer if necessary */ + if (!buf) { + buf = xzalloc(fs->blocksize * STRIDE_LENGTH); + } + /* OK, do the write loop */ + next_update = 0; + next_update_incr = num / 100; + if (next_update_incr < 1) + next_update_incr = 1; + for (j=0; j < num; j += STRIDE_LENGTH, blk += STRIDE_LENGTH) { + count = num - j; + if (count > STRIDE_LENGTH) + count = STRIDE_LENGTH; + retval = io_channel_write_blk(fs->io, blk, count, buf); + if (retval) { + if (ret_count) + *ret_count = count; + if (ret_blk) + *ret_blk = blk; + return retval; + } + if (progress && j > next_update) { + next_update += num / 100; + progress_update(progress, blk); + } + } + return 0; +} + +static void write_inode_tables(ext2_filsys fs) +{ + errcode_t retval; + blk_t blk; + dgrp_t i; + int num; + struct progress_struct progress; + + if (quiet) + memset(&progress, 0, sizeof(progress)); + else + progress_init(&progress, "Writing inode tables: ", + fs->group_desc_count); + + for (i = 0; i < fs->group_desc_count; i++) { + progress_update(&progress, i); + + blk = fs->group_desc[i].bg_inode_table; + num = fs->inode_blocks_per_group; + + retval = zero_blocks(fs, blk, num, 0, &blk, &num); + mke2fs_error_msg_and_die(retval, + "write %d blocks in inode table starting at %d.", + num, blk); + if (sync_kludge) { + if (sync_kludge == 1) + sync(); + else if ((i % sync_kludge) == 0) + sync(); + } + } + zero_blocks(0, 0, 0, 0, 0, 0); + progress_close(&progress); +} + +static void create_root_dir(ext2_filsys fs) +{ + errcode_t retval; + struct ext2_inode inode; + + retval = ext2fs_mkdir(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, 0); + mke2fs_error_msg_and_die(retval, "create root dir"); + if (geteuid()) { + retval = ext2fs_read_inode(fs, EXT2_ROOT_INO, &inode); + mke2fs_error_msg_and_die(retval, "read root inode"); + inode.i_uid = getuid(); + if (inode.i_uid) + inode.i_gid = getgid(); + retval = ext2fs_write_new_inode(fs, EXT2_ROOT_INO, &inode); + mke2fs_error_msg_and_die(retval, "set root inode ownership"); + } +} + +static void create_lost_and_found(ext2_filsys fs) +{ + errcode_t retval; + ext2_ino_t ino; + const char *name = "lost+found"; + int i = 1; + char *msg = "create"; + int lpf_size = 0; + + fs->umask = 077; + retval = ext2fs_mkdir(fs, EXT2_ROOT_INO, 0, name); + if (retval) { + goto CREATE_LOST_AND_FOUND_ERROR; + } + + retval = ext2fs_lookup(fs, EXT2_ROOT_INO, name, strlen(name), 0, &ino); + if (retval) { + msg = "lookup"; + goto CREATE_LOST_AND_FOUND_ERROR; + } + + for (; i < EXT2_NDIR_BLOCKS; i++) { + if ((lpf_size += fs->blocksize) >= 16*1024) + break; + retval = ext2fs_expand_dir(fs, ino); + msg = "expand"; +CREATE_LOST_AND_FOUND_ERROR: + mke2fs_error_msg_and_die(retval, "%s %s", msg, name); + } +} + +static void create_bad_block_inode(ext2_filsys fs, badblocks_list bb_list) +{ + errcode_t retval; + + ext2fs_mark_inode_bitmap(fs->inode_map, EXT2_BAD_INO); + fs->group_desc[0].bg_free_inodes_count--; + fs->super->s_free_inodes_count--; + retval = ext2fs_update_bb_inode(fs, bb_list); + mke2fs_error_msg_and_die(retval, "set bad block inode"); +} + +static void reserve_inodes(ext2_filsys fs) +{ + ext2_ino_t i; + int group; + + for (i = EXT2_ROOT_INO + 1; i < EXT2_FIRST_INODE(fs->super); i++) { + ext2fs_mark_inode_bitmap(fs->inode_map, i); + group = ext2fs_group_of_ino(fs, i); + fs->group_desc[group].bg_free_inodes_count--; + fs->super->s_free_inodes_count--; + } + ext2fs_mark_ib_dirty(fs); +} + +#define BSD_DISKMAGIC (0x82564557UL) /* The disk magic number */ +#define BSD_MAGICDISK (0x57455682UL) /* The disk magic number reversed */ +#define BSD_LABEL_OFFSET 64 + +static void zap_sector(ext2_filsys fs, int sect, int nsect) +{ + char *buf; + char *fmt = "could not %s %d"; + int retval; + unsigned int *magic; + + buf = xmalloc(512*nsect); + + if (sect == 0) { + /* Check for a BSD disklabel, and don't erase it if so */ + retval = io_channel_read_blk(fs->io, 0, -512, buf); + if (retval) + mke2fs_warning_msg(retval, fmt, "read block", 0); + else { + magic = (unsigned int *) (buf + BSD_LABEL_OFFSET); + if ((*magic == BSD_DISKMAGIC) || + (*magic == BSD_MAGICDISK)) + return; + } + } + + memset(buf, 0, 512*nsect); + io_channel_set_blksize(fs->io, 512); + retval = io_channel_write_blk(fs->io, sect, -512*nsect, buf); + io_channel_set_blksize(fs->io, fs->blocksize); + free(buf); + mke2fs_warning_msg(retval, fmt, "erase sector", sect); +} + +static void create_journal_dev(ext2_filsys fs) +{ + struct progress_struct progress; + errcode_t retval; + char *buf; + char *fmt = "%s journal superblock"; + blk_t blk; + int count; + + retval = ext2fs_create_journal_superblock(fs, + fs->super->s_blocks_count, 0, &buf); + mke2fs_error_msg_and_die(retval, fmt, "init"); + if (quiet) + memset(&progress, 0, sizeof(progress)); + else + progress_init(&progress, "Zeroing journal device: ", + fs->super->s_blocks_count); + + retval = zero_blocks(fs, 0, fs->super->s_blocks_count, + &progress, &blk, &count); + mke2fs_error_msg_and_die(retval, "zero journal device (block %u, count %d)", + blk, count); + zero_blocks(0, 0, 0, 0, 0, 0); + + retval = io_channel_write_blk(fs->io, + fs->super->s_first_data_block+1, + 1, buf); + mke2fs_error_msg_and_die(retval, fmt, "write"); + progress_close(&progress); +} + +static void show_stats(ext2_filsys fs) +{ + struct ext2_super_block *s = fs->super; + char *os; + blk_t group_block; + dgrp_t i; + int need, col_left; + + mke2fs_warning_msg((param.s_blocks_count != s->s_blocks_count), + "%d blocks unused\n", param.s_blocks_count - s->s_blocks_count); + os = e2p_os2string(fs->super->s_creator_os); + printf( "Filesystem label=%.*s\n" + "OS type: %s\n" + "Block size=%u (log=%u)\n" + "Fragment size=%u (log=%u)\n" + "%u inodes, %u blocks\n" + "%u blocks (%2.2f%%) reserved for the super user\n" + "First data block=%u\n", + (int) sizeof(s->s_volume_name), + s->s_volume_name, + os, + fs->blocksize, s->s_log_block_size, + fs->fragsize, s->s_log_frag_size, + s->s_inodes_count, s->s_blocks_count, + s->s_r_blocks_count, 100.0 * s->s_r_blocks_count / s->s_blocks_count, + s->s_first_data_block); + free(os); + if (s->s_reserved_gdt_blocks) { + printf("Maximum filesystem blocks=%lu\n", + (s->s_reserved_gdt_blocks + fs->desc_blocks) * + (fs->blocksize / sizeof(struct ext2_group_desc)) * + s->s_blocks_per_group); + } + printf( "%u block group%s\n" + "%u blocks per group, %u fragments per group\n" + "%u inodes per group\n", + fs->group_desc_count, (fs->group_desc_count > 1) ? "s" : "", + s->s_blocks_per_group, s->s_frags_per_group, + s->s_inodes_per_group); + if (fs->group_desc_count == 1) { + puts(""); + return; + } + + printf("Superblock backups stored on blocks: "); + group_block = s->s_first_data_block; + col_left = 0; + for (i = 1; i < fs->group_desc_count; i++) { + group_block += s->s_blocks_per_group; + if (!ext2fs_bg_has_super(fs, i)) + continue; + if (i != 1) + printf(", "); + need = int_log10(group_block) + 2; + if (need > col_left) { + printf("\n\t"); + col_left = 72; + } + col_left -= need; + printf("%u", group_block); + } + puts("\n"); +} + +/* + * Set the S_CREATOR_OS field. Return true if OS is known, + * otherwise, 0. + */ +static int set_os(struct ext2_super_block *sb, char *os) +{ + if (isdigit (*os)) { + sb->s_creator_os = atoi(os); + return 1; + } + + if((sb->s_creator_os = e2p_string2os(os)) >= 0) { + return 1; + } else if (!strcasecmp("GNU", os)) { + sb->s_creator_os = EXT2_OS_HURD; + return 1; + } + return 0; +} + +static void parse_extended_opts(struct ext2_super_block *sb_param, + const char *opts) +{ + char *buf, *token, *next, *p, *arg; + int r_usage = 0; + + buf = xstrdup(opts); + for (token = buf; token && *token; token = next) { + p = strchr(token, ','); + next = 0; + if (p) { + *p = 0; + next = p+1; + } + arg = strchr(token, '='); + if (arg) { + *arg = 0; + arg++; + } + if (strcmp(token, "stride") == 0) { + if (!arg) { + r_usage++; + continue; + } + fs_stride = strtoul(arg, &p, 0); + if (*p || (fs_stride == 0)) { + bb_error_msg("Invalid stride parameter: %s", arg); + r_usage++; + continue; + } + } else if (!strcmp(token, "resize")) { + unsigned long resize, bpg, rsv_groups; + unsigned long group_desc_count, desc_blocks; + unsigned int gdpb, blocksize; + int rsv_gdb; + + if (!arg) { + r_usage++; + continue; + } + + resize = parse_num_blocks(arg, + sb_param->s_log_block_size); + + if (resize == 0) { + bb_error_msg("Invalid resize parameter: %s", arg); + r_usage++; + continue; + } + if (resize <= sb_param->s_blocks_count) { + bb_error_msg("The resize maximum must be greater " + "than the filesystem size"); + r_usage++; + continue; + } + + blocksize = EXT2_BLOCK_SIZE(sb_param); + bpg = sb_param->s_blocks_per_group; + if (!bpg) + bpg = blocksize * 8; + gdpb = blocksize / sizeof(struct ext2_group_desc); + group_desc_count = (sb_param->s_blocks_count + + bpg - 1) / bpg; + desc_blocks = (group_desc_count + + gdpb - 1) / gdpb; + rsv_groups = (resize + bpg - 1) / bpg; + rsv_gdb = (rsv_groups + gdpb - 1) / gdpb - + desc_blocks; + if (rsv_gdb > EXT2_ADDR_PER_BLOCK(sb_param)) + rsv_gdb = EXT2_ADDR_PER_BLOCK(sb_param); + + if (rsv_gdb > 0) { + sb_param->s_feature_compat |= + EXT2_FEATURE_COMPAT_RESIZE_INODE; + + sb_param->s_reserved_gdt_blocks = rsv_gdb; + } + } else + r_usage++; + } + if (r_usage) { + bb_error_msg_and_die( + "\nBad options specified.\n\n" + "Extended options are separated by commas, " + "and may take an argument which\n" + "\tis set off by an equals ('=') sign.\n\n" + "Valid extended options are:\n" + "\tstride=\n" + "\tresize=\n"); + } +} + +static __u32 ok_features[3] = { + EXT3_FEATURE_COMPAT_HAS_JOURNAL | + EXT2_FEATURE_COMPAT_RESIZE_INODE | + EXT2_FEATURE_COMPAT_DIR_INDEX, /* Compat */ + EXT2_FEATURE_INCOMPAT_FILETYPE| /* Incompat */ + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV| + EXT2_FEATURE_INCOMPAT_META_BG, + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER /* R/O compat */ +}; + +static int PRS(int argc, char *argv[]) +{ + int c; + int size; + char * tmp; + int blocksize = 0; + int inode_ratio = 0; + int inode_size = 0; + int reserved_ratio = 5; + int sector_size = 0; + int show_version_only = 0; + ext2_ino_t num_inodes = 0; + errcode_t retval; + char * extended_opts = 0; + const char * fs_type = 0; + blk_t dev_size; + long sysval; + + /* Update our PATH to include /sbin */ + e2fs_set_sbin_path(); + + tmp = getenv("MKE2FS_SYNC"); + if (tmp) + sync_kludge = atoi(tmp); + + /* Determine the system page size if possible */ +#if (!defined(_SC_PAGESIZE) && defined(_SC_PAGE_SIZE)) +#define _SC_PAGESIZE _SC_PAGE_SIZE +#endif +#ifdef _SC_PAGESIZE + sysval = sysconf(_SC_PAGESIZE); + if (sysval > 0) + sys_page_size = sysval; +#endif /* _SC_PAGESIZE */ + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + memset(¶m, 0, sizeof(struct ext2_super_block)); + param.s_rev_level = 1; /* Create revision 1 filesystems now */ + param.s_feature_incompat |= EXT2_FEATURE_INCOMPAT_FILETYPE; + param.s_feature_ro_compat |= EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + +#ifdef __linux__ + linux_version_code = get_linux_version_code(); + if (linux_version_code && linux_version_code < KERNEL_VERSION(2,2,0)) { + param.s_rev_level = 0; + param.s_feature_incompat = 0; + param.s_feature_compat = 0; + param.s_feature_ro_compat = 0; + } +#endif + + /* If called as mkfs.ext3, create a journal inode */ + if (last_char_is(applet_name, '3')) + journal_size = -1; + + while ((c = getopt (argc, argv, + "b:cE:f:g:i:jl:m:no:qr:R:s:tvI:J:ST:FL:M:N:O:V")) != EOF) { + switch (c) { + case 'b': + blocksize = xatou_range(optarg, EXT2_MIN_BLOCK_SIZE, EXT2_MAX_BLOCK_SIZE); + mke2fs_warning_msg((blocksize > 4096), + "blocksize %d not usable on most systems", + blocksize); + param.s_log_block_size = + int_log2(blocksize >> EXT2_MIN_BLOCK_LOG_SIZE); + break; + case 'c': /* Check for bad blocks */ + case 't': /* deprecated */ + cflag++; + break; + case 'f': + size = xatou_range(optarg, EXT2_MIN_BLOCK_SIZE, EXT2_MAX_BLOCK_SIZE); + param.s_log_frag_size = + int_log2(size >> EXT2_MIN_BLOCK_LOG_SIZE); + mke2fs_warning_msg(1, "fragments not supported. Ignoring -f option"); + break; + case 'g': + param.s_blocks_per_group = xatou32(optarg); + if ((param.s_blocks_per_group % 8) != 0) { + bb_error_msg_and_die("blocks per group must be multiple of 8"); + } + break; + case 'i': + /* Huh? is "* 1024" correct? */ + inode_ratio = xatou_range(optarg, EXT2_MIN_BLOCK_SIZE, EXT2_MAX_BLOCK_SIZE * 1024); + break; + case 'J': + parse_journal_opts(&journal_device, &journal_flags, &journal_size, optarg); + break; + case 'j': + param.s_feature_compat |= + EXT3_FEATURE_COMPAT_HAS_JOURNAL; + if (!journal_size) + journal_size = -1; + break; + case 'l': + bad_blocks_filename = optarg; + break; + case 'm': + reserved_ratio = xatou_range(optarg, 0, 50); + break; + case 'n': + noaction++; + break; + case 'o': + creator_os = optarg; + break; + case 'r': + param.s_rev_level = xatoi_u(optarg); + if (param.s_rev_level == EXT2_GOOD_OLD_REV) { + param.s_feature_incompat = 0; + param.s_feature_compat = 0; + param.s_feature_ro_compat = 0; + } + break; + case 's': /* deprecated */ + if (xatou(optarg)) + param.s_feature_ro_compat |= + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + else + param.s_feature_ro_compat &= + ~EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + break; +#ifdef EXT2_DYNAMIC_REV + case 'I': + inode_size = xatoi_u(optarg); + break; +#endif + case 'N': + num_inodes = xatoi_u(optarg); + break; + case 'v': + quiet = 0; + break; + case 'q': + quiet = 1; + break; + case 'F': + force = 1; + break; + case 'L': + volume_label = optarg; + break; + case 'M': + mount_dir = optarg; + break; + case 'O': + if (!strcmp(optarg, "none")) { + param.s_feature_compat = 0; + param.s_feature_incompat = 0; + param.s_feature_ro_compat = 0; + break; + } + if (e2p_edit_feature(optarg, + ¶m.s_feature_compat, + ok_features)) { + bb_error_msg_and_die("Invalid filesystem option set: %s", optarg); + } + break; + case 'E': + case 'R': + extended_opts = optarg; + break; + case 'S': + super_only = 1; + break; + case 'T': + fs_type = optarg; + break; + case 'V': + /* Print version number and exit */ + show_version_only = 1; + quiet = 0; + break; + default: + bb_show_usage(); + } + } + if ((optind == argc) /*&& !show_version_only*/) + bb_show_usage(); + device_name = argv[optind++]; + + mke2fs_verbose("mke2fs %s (%s)\n", E2FSPROGS_VERSION, E2FSPROGS_DATE); + + if (show_version_only) { + return 0; + } + + /* + * If there's no blocksize specified and there is a journal + * device, use it to figure out the blocksize + */ + if (blocksize <= 0 && journal_device) { + ext2_filsys jfs; + io_manager io_ptr; + +#ifdef CONFIG_TESTIO_DEBUG + io_ptr = test_io_manager; + test_io_backing_manager = unix_io_manager; +#else + io_ptr = unix_io_manager; +#endif + retval = ext2fs_open(journal_device, + EXT2_FLAG_JOURNAL_DEV_OK, 0, + 0, io_ptr, &jfs); + mke2fs_error_msg_and_die(retval, "open journal device %s", journal_device); + if ((blocksize < 0) && (jfs->blocksize < (unsigned) (-blocksize))) { + bb_error_msg_and_die( + "Journal dev blocksize (%d) smaller than " + "minimum blocksize %d\n", jfs->blocksize, + -blocksize); + } + blocksize = jfs->blocksize; + param.s_log_block_size = + int_log2(blocksize >> EXT2_MIN_BLOCK_LOG_SIZE); + ext2fs_close(jfs); + } + + if (blocksize > sys_page_size) { + mke2fs_warning_msg(1, "%d-byte blocks too big for system (max %d)", + blocksize, sys_page_size); + if (!force) { + proceed_question(); + } + bb_error_msg("Forced to continue"); + } + mke2fs_warning_msg(((blocksize > 4096) && + (param.s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL)), + "some 2.4 kernels do not support " + "blocksizes greater than 4096 using ext3.\n" + "Use -b 4096 if this is an issue for you\n"); + + if (optind < argc) { + param.s_blocks_count = parse_num_blocks(argv[optind++], + param.s_log_block_size); + mke2fs_error_msg_and_die(!param.s_blocks_count, "invalid blocks count - %s", argv[optind - 1]); + } + if (optind < argc) + bb_show_usage(); + + if (param.s_feature_incompat & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { + if (!fs_type) + fs_type = "journal"; + reserved_ratio = 0; + param.s_feature_incompat = EXT3_FEATURE_INCOMPAT_JOURNAL_DEV; + param.s_feature_compat = 0; + param.s_feature_ro_compat = 0; + } + if (param.s_rev_level == EXT2_GOOD_OLD_REV && + (param.s_feature_compat || param.s_feature_ro_compat || + param.s_feature_incompat)) + param.s_rev_level = 1; /* Create a revision 1 filesystem */ + + check_plausibility(device_name , force); + check_mount(device_name, force, "filesystem"); + + param.s_log_frag_size = param.s_log_block_size; + + if (noaction && param.s_blocks_count) { + dev_size = param.s_blocks_count; + retval = 0; + } else { + retry: + retval = ext2fs_get_device_size(device_name, + EXT2_BLOCK_SIZE(¶m), + &dev_size); + if ((retval == EFBIG) && + (blocksize == 0) && + (param.s_log_block_size == 0)) { + param.s_log_block_size = 2; + blocksize = 4096; + goto retry; + } + } + + mke2fs_error_msg_and_die((retval && (retval != EXT2_ET_UNIMPLEMENTED)),"determine filesystem size"); + + if (!param.s_blocks_count) { + if (retval == EXT2_ET_UNIMPLEMENTED) { + mke2fs_error_msg_and_die(1, + "determine device size; you " + "must specify\nthe size of the " + "filesystem"); + } else { + if (dev_size == 0) { + bb_error_msg_and_die( + "Device size reported to be zero. " + "Invalid partition specified, or\n\t" + "partition table wasn't reread " + "after running fdisk, due to\n\t" + "a modified partition being busy " + "and in use. You may need to reboot\n\t" + "to re-read your partition table.\n" + ); + } + param.s_blocks_count = dev_size; + if (sys_page_size > EXT2_BLOCK_SIZE(¶m)) + param.s_blocks_count &= ~((sys_page_size / + EXT2_BLOCK_SIZE(¶m))-1); + } + + } else if (!force && (param.s_blocks_count > dev_size)) { + bb_error_msg("Filesystem larger than apparent device size"); + proceed_question(); + } + + /* + * If the user asked for HAS_JOURNAL, then make sure a journal + * gets created. + */ + if ((param.s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL) && + !journal_size) + journal_size = -1; + + /* Set first meta blockgroup via an environment variable */ + /* (this is mostly for debugging purposes) */ + if ((param.s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) && + ((tmp = getenv("MKE2FS_FIRST_META_BG")))) + param.s_first_meta_bg = atoi(tmp); + + /* Get the hardware sector size, if available */ + retval = ext2fs_get_device_sectsize(device_name, §or_size); + mke2fs_error_msg_and_die(retval, "determine hardware sector size"); + + if ((tmp = getenv("MKE2FS_DEVICE_SECTSIZE")) != NULL) + sector_size = atoi(tmp); + + set_fs_defaults(fs_type, ¶m, blocksize, sector_size, &inode_ratio); + blocksize = EXT2_BLOCK_SIZE(¶m); + + if (extended_opts) + parse_extended_opts(¶m, extended_opts); + + /* Since sparse_super is the default, we would only have a problem + * here if it was explicitly disabled. + */ + if ((param.s_feature_compat & EXT2_FEATURE_COMPAT_RESIZE_INODE) && + !(param.s_feature_ro_compat&EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) { + bb_error_msg_and_die("reserved online resize blocks not supported " + "on non-sparse filesystem"); + } + + if (param.s_blocks_per_group) { + if (param.s_blocks_per_group < 256 || + param.s_blocks_per_group > 8 * (unsigned) blocksize) { + bb_error_msg_and_die("blocks per group count out of range"); + } + } + + if (!force && param.s_blocks_count >= (1 << 31)) { + bb_error_msg_and_die("Filesystem too large. No more than 2**31-1 blocks\n" + "\t (8TB using a blocksize of 4k) are currently supported."); + } + + if (inode_size) { + if (inode_size < EXT2_GOOD_OLD_INODE_SIZE || + inode_size > EXT2_BLOCK_SIZE(¶m) || + inode_size & (inode_size - 1)) { + bb_error_msg_and_die("invalid inode size %d (min %d/max %d)", + inode_size, EXT2_GOOD_OLD_INODE_SIZE, + blocksize); + } + mke2fs_warning_msg((inode_size != EXT2_GOOD_OLD_INODE_SIZE), + "%d-byte inodes not usable on most systems", + inode_size); + param.s_inode_size = inode_size; + } + + /* + * Calculate number of inodes based on the inode ratio + */ + param.s_inodes_count = num_inodes ? num_inodes : + ((__u64) param.s_blocks_count * blocksize) + / inode_ratio; + + /* + * Calculate number of blocks to reserve + */ + param.s_r_blocks_count = (param.s_blocks_count * reserved_ratio) / 100; + return 1; +} + +static void mke2fs_clean_up(void) +{ + if (ENABLE_FEATURE_CLEAN_UP && journal_device) free(journal_device); +} + +int mke2fs_main (int argc, char *argv[]) +{ + errcode_t retval; + ext2_filsys fs; + badblocks_list bb_list = 0; + unsigned int i; + int val; + io_manager io_ptr; + + if (ENABLE_FEATURE_CLEAN_UP) + atexit(mke2fs_clean_up); + if(!PRS(argc, argv)) + return 0; + +#ifdef CONFIG_TESTIO_DEBUG + io_ptr = test_io_manager; + test_io_backing_manager = unix_io_manager; +#else + io_ptr = unix_io_manager; +#endif + + /* + * Initialize the superblock.... + */ + retval = ext2fs_initialize(device_name, 0, ¶m, + io_ptr, &fs); + mke2fs_error_msg_and_die(retval, "set up superblock"); + + /* + * Wipe out the old on-disk superblock + */ + if (!noaction) + zap_sector(fs, 2, 6); + + /* + * Generate a UUID for it... + */ + uuid_generate(fs->super->s_uuid); + + /* + * Initialize the directory index variables + */ + fs->super->s_def_hash_version = EXT2_HASH_TEA; + uuid_generate((unsigned char *) fs->super->s_hash_seed); + + /* + * Add "jitter" to the superblock's check interval so that we + * don't check all the filesystems at the same time. We use a + * kludgy hack of using the UUID to derive a random jitter value. + */ + for (i = 0, val = 0 ; i < sizeof(fs->super->s_uuid); i++) + val += fs->super->s_uuid[i]; + fs->super->s_max_mnt_count += val % EXT2_DFL_MAX_MNT_COUNT; + + /* + * Override the creator OS, if applicable + */ + if (creator_os && !set_os(fs->super, creator_os)) { + bb_error_msg_and_die("unknown os - %s", creator_os); + } + + /* + * For the Hurd, we will turn off filetype since it doesn't + * support it. + */ + if (fs->super->s_creator_os == EXT2_OS_HURD) + fs->super->s_feature_incompat &= + ~EXT2_FEATURE_INCOMPAT_FILETYPE; + + /* + * Set the volume label... + */ + if (volume_label) { + snprintf(fs->super->s_volume_name, sizeof(fs->super->s_volume_name), "%s", volume_label); + } + + /* + * Set the last mount directory + */ + if (mount_dir) { + snprintf(fs->super->s_last_mounted, sizeof(fs->super->s_last_mounted), "%s", mount_dir); + } + + if (!quiet || noaction) + show_stats(fs); + + if (noaction) + return 0; + + if (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { + create_journal_dev(fs); + return (ext2fs_close(fs) ? 1 : 0); + } + + if (bad_blocks_filename) + read_bb_file(fs, &bb_list, bad_blocks_filename); + if (cflag) + test_disk(fs, &bb_list); + + handle_bad_blocks(fs, bb_list); + fs->stride = fs_stride; + retval = ext2fs_allocate_tables(fs); + mke2fs_error_msg_and_die(retval, "allocate filesystem tables"); + if (super_only) { + fs->super->s_state |= EXT2_ERROR_FS; + fs->flags &= ~(EXT2_FLAG_IB_DIRTY|EXT2_FLAG_BB_DIRTY); + } else { + /* rsv must be a power of two (64kB is MD RAID sb alignment) */ + unsigned int rsv = 65536 / fs->blocksize; + unsigned long blocks = fs->super->s_blocks_count; + unsigned long start; + blk_t ret_blk; + +#ifdef ZAP_BOOTBLOCK + zap_sector(fs, 0, 2); +#endif + + /* + * Wipe out any old MD RAID (or other) metadata at the end + * of the device. This will also verify that the device is + * as large as we think. Be careful with very small devices. + */ + start = (blocks & ~(rsv - 1)); + if (start > rsv) + start -= rsv; + if (start > 0) + retval = zero_blocks(fs, start, blocks - start, + NULL, &ret_blk, NULL); + + mke2fs_warning_msg(retval, "cannot zero block %u at end of filesystem", ret_blk); + write_inode_tables(fs); + create_root_dir(fs); + create_lost_and_found(fs); + reserve_inodes(fs); + create_bad_block_inode(fs, bb_list); + if (fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_RESIZE_INODE) { + retval = ext2fs_create_resize_inode(fs); + mke2fs_error_msg_and_die(retval, "reserve blocks for online resize"); + } + } + + if (journal_device) { + make_journal_device(journal_device, fs, quiet, force); + } else if (journal_size) { + make_journal_blocks(fs, journal_size, journal_flags, quiet); + } + + mke2fs_verbose("Writing superblocks and filesystem accounting information: "); + retval = ext2fs_flush(fs); + mke2fs_warning_msg(retval, "had trouble writing out superblocks"); + mke2fs_verbose_done(); + if (!quiet && !getenv("MKE2FS_SKIP_CHECK_MSG")) + print_check_message(fs); + val = ext2fs_close(fs); + return (retval || val) ? 1 : 0; +} diff --git a/e2fsprogs/tune2fs.c b/e2fsprogs/tune2fs.c new file mode 100644 index 000000000..a2ca1ba09 --- /dev/null +++ b/e2fsprogs/tune2fs.c @@ -0,0 +1,729 @@ +/* vi: set sw=4 ts=4: */ +/* + * tune2fs.c - Change the file system parameters on an ext2 file system + * + * Copyright (C) 1992, 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * Copyright 1995, 1996, 1997, 1998, 1999, 2000 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +/* + * History: + * 93/06/01 - Creation + * 93/10/31 - Added the -c option to change the maximal mount counts + * 93/12/14 - Added -l flag to list contents of superblock + * M.J.E. Mol (marcel@duteca.et.tudelft.nl) + * F.W. ten Wolde (franky@duteca.et.tudelft.nl) + * 93/12/29 - Added the -e option to change errors behavior + * 94/02/27 - Ported to use the ext2fs library + * 94/03/06 - Added the checks interval from Uwe Ohse (uwe@tirka.gun.de) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "e2fsbb.h" +#include "ext2fs/ext2_fs.h" +#include "ext2fs/ext2fs.h" +#include "uuid/uuid.h" +#include "e2p/e2p.h" +#include "ext2fs/kernel-jbd.h" +#include "util.h" +#include "blkid/blkid.h" + +#include "busybox.h" + +static char * device_name = NULL; +static char * new_label, *new_last_mounted, *new_UUID; +static char * io_options; +static int c_flag, C_flag, e_flag, f_flag, g_flag, i_flag, l_flag, L_flag; +static int m_flag, M_flag, r_flag, s_flag = -1, u_flag, U_flag, T_flag; +static time_t last_check_time; +static int print_label; +static int max_mount_count, mount_count, mount_flags; +static unsigned long interval, reserved_blocks; +static unsigned reserved_ratio; +static unsigned long resgid, resuid; +static unsigned short errors; +static int open_flag; +static char *features_cmd; +static char *mntopts_cmd; + +static int journal_size, journal_flags; +static char *journal_device = NULL; + +static const char *please_fsck = "Please run e2fsck on the filesystem\n"; + +static __u32 ok_features[3] = { + EXT3_FEATURE_COMPAT_HAS_JOURNAL | EXT2_FEATURE_COMPAT_DIR_INDEX, + EXT2_FEATURE_INCOMPAT_FILETYPE, + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER +}; + +/* + * Remove an external journal from the filesystem + */ +static void remove_journal_device(ext2_filsys fs) +{ + char *journal_path; + ext2_filsys jfs; + char buf[1024]; + journal_superblock_t *jsb; + int i, nr_users; + errcode_t retval; + int commit_remove_journal = 0; + io_manager io_ptr; + + if (f_flag) + commit_remove_journal = 1; /* force removal even if error */ + + uuid_unparse(fs->super->s_journal_uuid, buf); + journal_path = blkid_get_devname(NULL, "UUID", buf); + + if (!journal_path) { + journal_path = + ext2fs_find_block_device(fs->super->s_journal_dev); + if (!journal_path) + return; + } + + io_ptr = unix_io_manager; + retval = ext2fs_open(journal_path, EXT2_FLAG_RW| + EXT2_FLAG_JOURNAL_DEV_OK, 0, + fs->blocksize, io_ptr, &jfs); + if (retval) { + bb_error_msg("Failed to open external journal"); + goto no_valid_journal; + } + if (!(jfs->super->s_feature_incompat & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)) { + bb_error_msg("%s is not a journal device", journal_path); + goto no_valid_journal; + } + + /* Get the journal superblock */ + if ((retval = io_channel_read_blk(jfs->io, 1, -1024, buf))) { + bb_error_msg("Failed to read journal superblock"); + goto no_valid_journal; + } + + jsb = (journal_superblock_t *) buf; + if ((jsb->s_header.h_magic != (unsigned) ntohl(JFS_MAGIC_NUMBER)) || + (jsb->s_header.h_blocktype != (unsigned) ntohl(JFS_SUPERBLOCK_V2))) { + bb_error_msg("Journal superblock not found!"); + goto no_valid_journal; + } + + /* Find the filesystem UUID */ + nr_users = ntohl(jsb->s_nr_users); + for (i=0; i < nr_users; i++) { + if (memcmp(fs->super->s_uuid, + &jsb->s_users[i*16], 16) == 0) + break; + } + if (i >= nr_users) { + bb_error_msg("Filesystem's UUID not found on journal device"); + commit_remove_journal = 1; + goto no_valid_journal; + } + nr_users--; + for (i=0; i < nr_users; i++) + memcpy(&jsb->s_users[i*16], &jsb->s_users[(i+1)*16], 16); + jsb->s_nr_users = htonl(nr_users); + + /* Write back the journal superblock */ + if ((retval = io_channel_write_blk(jfs->io, 1, -1024, buf))) { + bb_error_msg("Failed to write journal superblock"); + goto no_valid_journal; + } + + commit_remove_journal = 1; + +no_valid_journal: + if (commit_remove_journal == 0) + bb_error_msg_and_die("Journal NOT removed"); + fs->super->s_journal_dev = 0; + uuid_clear(fs->super->s_journal_uuid); + ext2fs_mark_super_dirty(fs); + puts("Journal removed"); + free(journal_path); +} + +/* Helper function for remove_journal_inode */ +static int release_blocks_proc(ext2_filsys fs, blk_t *blocknr, + int blockcnt EXT2FS_ATTR((unused)), + void *private EXT2FS_ATTR((unused))) +{ + blk_t block; + int group; + + block = *blocknr; + ext2fs_unmark_block_bitmap(fs->block_map,block); + group = ext2fs_group_of_blk(fs, block); + fs->group_desc[group].bg_free_blocks_count++; + fs->super->s_free_blocks_count++; + return 0; +} + +/* + * Remove the journal inode from the filesystem + */ +static void remove_journal_inode(ext2_filsys fs) +{ + struct ext2_inode inode; + errcode_t retval; + ino_t ino = fs->super->s_journal_inum; + char *msg = "to read"; + char *s = "journal inode"; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + goto REMOVE_JOURNAL_INODE_ERROR; + if (ino == EXT2_JOURNAL_INO) { + retval = ext2fs_read_bitmaps(fs); + if (retval) { + msg = "to read bitmaps"; + s = ""; + goto REMOVE_JOURNAL_INODE_ERROR; + } + retval = ext2fs_block_iterate(fs, ino, 0, NULL, + release_blocks_proc, NULL); + if (retval) { + msg = "clearing"; + goto REMOVE_JOURNAL_INODE_ERROR; + } + memset(&inode, 0, sizeof(inode)); + ext2fs_mark_bb_dirty(fs); + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + } else + inode.i_flags &= ~EXT2_IMMUTABLE_FL; + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) { + msg = "writing"; +REMOVE_JOURNAL_INODE_ERROR: + bb_error_msg_and_die("Failed %s %s", msg, s); + } + fs->super->s_journal_inum = 0; + ext2fs_mark_super_dirty(fs); +} + +/* + * Update the default mount options + */ +static void update_mntopts(ext2_filsys fs, char *mntopts) +{ + struct ext2_super_block *sb= fs->super; + + if (e2p_edit_mntopts(mntopts, &sb->s_default_mount_opts, ~0)) + bb_error_msg_and_die("Invalid mount option set: %s", mntopts); + ext2fs_mark_super_dirty(fs); +} + +/* + * Update the feature set as provided by the user. + */ +static void update_feature_set(ext2_filsys fs, char *features) +{ + int sparse, old_sparse, filetype, old_filetype; + int journal, old_journal, dxdir, old_dxdir; + struct ext2_super_block *sb= fs->super; + __u32 old_compat, old_incompat, old_ro_compat; + + old_compat = sb->s_feature_compat; + old_ro_compat = sb->s_feature_ro_compat; + old_incompat = sb->s_feature_incompat; + + old_sparse = sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + old_filetype = sb->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_FILETYPE; + old_journal = sb->s_feature_compat & + EXT3_FEATURE_COMPAT_HAS_JOURNAL; + old_dxdir = sb->s_feature_compat & + EXT2_FEATURE_COMPAT_DIR_INDEX; + if (e2p_edit_feature(features, &sb->s_feature_compat, ok_features)) + bb_error_msg_and_die("Invalid filesystem option set: %s", features); + sparse = sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + filetype = sb->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_FILETYPE; + journal = sb->s_feature_compat & + EXT3_FEATURE_COMPAT_HAS_JOURNAL; + dxdir = sb->s_feature_compat & + EXT2_FEATURE_COMPAT_DIR_INDEX; + if (old_journal && !journal) { + if ((mount_flags & EXT2_MF_MOUNTED) && + !(mount_flags & EXT2_MF_READONLY)) { + bb_error_msg_and_die( + "The has_journal flag may only be " + "cleared when the filesystem is\n" + "unmounted or mounted " + "read-only"); + } + if (sb->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_RECOVER) { + bb_error_msg_and_die( + "The needs_recovery flag is set. " + "%s before clearing the has_journal flag.", + please_fsck); + } + if (sb->s_journal_inum) { + remove_journal_inode(fs); + } + if (sb->s_journal_dev) { + remove_journal_device(fs); + } + } + if (journal && !old_journal) { + /* + * If adding a journal flag, let the create journal + * code below handle creating setting the flag and + * creating the journal. We supply a default size if + * necessary. + */ + if (!journal_size) + journal_size = -1; + sb->s_feature_compat &= ~EXT3_FEATURE_COMPAT_HAS_JOURNAL; + } + if (dxdir && !old_dxdir) { + if (!sb->s_def_hash_version) + sb->s_def_hash_version = EXT2_HASH_TEA; + if (uuid_is_null((unsigned char *) sb->s_hash_seed)) + uuid_generate((unsigned char *) sb->s_hash_seed); + } + + if (sb->s_rev_level == EXT2_GOOD_OLD_REV && + (sb->s_feature_compat || sb->s_feature_ro_compat || + sb->s_feature_incompat)) + ext2fs_update_dynamic_rev(fs); + if ((sparse != old_sparse) || + (filetype != old_filetype)) { + sb->s_state &= ~EXT2_VALID_FS; + printf("\n%s\n", please_fsck); + } + if ((old_compat != sb->s_feature_compat) || + (old_ro_compat != sb->s_feature_ro_compat) || + (old_incompat != sb->s_feature_incompat)) + ext2fs_mark_super_dirty(fs); +} + +/* + * Add a journal to the filesystem. + */ +static void add_journal(ext2_filsys fs) +{ + if (fs->super->s_feature_compat & + EXT3_FEATURE_COMPAT_HAS_JOURNAL) { + bb_error_msg_and_die("The filesystem already has a journal"); + } + if (journal_device) { + make_journal_device(journal_device, fs, 0, 0); + } else if (journal_size) { + make_journal_blocks(fs, journal_size, journal_flags, 0); + /* + * If the filesystem wasn't mounted, we need to force + * the block group descriptors out. + */ + if ((mount_flags & EXT2_MF_MOUNTED) == 0) + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + } + print_check_message(fs); + return; +} + +/* + * Busybox stuff + */ +static char * x_blkid_get_devname(const char *token) +{ + char * dev_name; + + if (!(dev_name = blkid_get_devname(NULL, token, NULL))) + bb_error_msg_and_die("Unable to resolve '%s'", token); + return dev_name; +} + +#ifdef CONFIG_E2LABEL +static void parse_e2label_options(int argc, char ** argv) +{ + if ((argc < 2) || (argc > 3)) + bb_show_usage(); + io_options = strchr(argv[1], '?'); + if (io_options) + *io_options++ = 0; + device_name = x_blkid_get_devname(argv[1]); + if (argc == 3) { + open_flag = EXT2_FLAG_RW | EXT2_FLAG_JOURNAL_DEV_OK; + L_flag = 1; + new_label = argv[2]; + } else + print_label++; +} +#else +#define parse_e2label_options(x,y) +#endif + +static time_t parse_time(char *str) +{ + struct tm ts; + + if (strcmp(str, "now") == 0) { + return time(0); + } + memset(&ts, 0, sizeof(ts)); +#ifdef HAVE_STRPTIME + strptime(str, "%Y%m%d%H%M%S", &ts); +#else + sscanf(str, "%4d%2d%2d%2d%2d%2d", &ts.tm_year, &ts.tm_mon, + &ts.tm_mday, &ts.tm_hour, &ts.tm_min, &ts.tm_sec); + ts.tm_year -= 1900; + ts.tm_mon -= 1; + if (ts.tm_year < 0 || ts.tm_mon < 0 || ts.tm_mon > 11 || + ts.tm_mday < 0 || ts.tm_mday > 31 || ts.tm_hour > 23 || + ts.tm_min > 59 || ts.tm_sec > 61) + ts.tm_mday = 0; +#endif + if (ts.tm_mday == 0) { + bb_error_msg_and_die("Cannot parse date/time specifier: %s", str); + } + return mktime(&ts); +} + +static void parse_tune2fs_options(int argc, char **argv) +{ + int c; + char * tmp; + + printf("tune2fs %s (%s)\n", E2FSPROGS_VERSION, E2FSPROGS_DATE); + while ((c = getopt(argc, argv, "c:e:fg:i:jlm:o:r:s:u:C:J:L:M:O:T:U:")) != EOF) + switch (c) + { + case 'c': + max_mount_count = xatou_range(optarg, 0, 16000); + if (max_mount_count == 0) + max_mount_count = -1; + c_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'C': + mount_count = xatou_range(optarg, 0, 16000); + C_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'e': + if (strcmp (optarg, "continue") == 0) + errors = EXT2_ERRORS_CONTINUE; + else if (strcmp (optarg, "remount-ro") == 0) + errors = EXT2_ERRORS_RO; + else if (strcmp (optarg, "panic") == 0) + errors = EXT2_ERRORS_PANIC; + else { + bb_error_msg_and_die("bad error behavior - %s", optarg); + } + e_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'f': /* Force */ + f_flag = 1; + break; + case 'g': + resgid = bb_strtoul(optarg, NULL, 10); + if (errno) + resgid = bb_xgetgrnam(optarg); + g_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'i': + interval = strtoul(optarg, &tmp, 0); + switch (*tmp) { + case 's': + tmp++; + break; + case '\0': + case 'd': + case 'D': /* days */ + interval *= 86400; + if (*tmp != '\0') + tmp++; + break; + case 'm': + case 'M': /* months! */ + interval *= 86400 * 30; + tmp++; + break; + case 'w': + case 'W': /* weeks */ + interval *= 86400 * 7; + tmp++; + break; + } + if (*tmp || interval > (365 * 86400)) { + bb_error_msg_and_die("bad interval - %s", optarg); + } + i_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'j': + if (!journal_size) + journal_size = -1; + open_flag = EXT2_FLAG_RW; + break; + case 'J': + parse_journal_opts(&journal_device, &journal_flags, &journal_size, optarg); + open_flag = EXT2_FLAG_RW; + break; + case 'l': + l_flag = 1; + break; + case 'L': + new_label = optarg; + L_flag = 1; + open_flag = EXT2_FLAG_RW | + EXT2_FLAG_JOURNAL_DEV_OK; + break; + case 'm': + reserved_ratio = xatou_range(optarg, 0, 50); + m_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'M': + new_last_mounted = optarg; + M_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'o': + if (mntopts_cmd) { + bb_error_msg_and_die("-o may only be specified once"); + } + mntopts_cmd = optarg; + open_flag = EXT2_FLAG_RW; + break; + + case 'O': + if (features_cmd) { + bb_error_msg_and_die("-O may only be specified once"); + } + features_cmd = optarg; + open_flag = EXT2_FLAG_RW; + break; + case 'r': + reserved_blocks = xatoul(optarg); + r_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 's': + s_flag = atoi(optarg); + open_flag = EXT2_FLAG_RW; + break; + case 'T': + T_flag = 1; + last_check_time = parse_time(optarg); + open_flag = EXT2_FLAG_RW; + break; + case 'u': + resuid = bb_strtoul(optarg, NULL, 10); + if (errno) + resuid = bb_xgetpwnam(optarg); + u_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'U': + new_UUID = optarg; + U_flag = 1; + open_flag = EXT2_FLAG_RW | + EXT2_FLAG_JOURNAL_DEV_OK; + break; + default: + bb_show_usage(); + } + if (optind < argc - 1 || optind == argc) + bb_show_usage(); + if (!open_flag && !l_flag) + bb_show_usage(); + io_options = strchr(argv[optind], '?'); + if (io_options) + *io_options++ = 0; + device_name = x_blkid_get_devname(argv[optind]); +} + +#ifdef CONFIG_FINDFS +static ATTRIBUTE_NORETURN void do_findfs(int argc, char **argv) +{ + if ((argc != 2) || + (strncmp(argv[1], "LABEL=", 6) && strncmp(argv[1], "UUID=", 5))) + bb_show_usage(); + device_name = x_blkid_get_devname(argv[1]); + puts(device_name); + exit(0); +} +#else +#define do_findfs(x, y) +#endif + +static void tune2fs_clean_up(void) +{ + if (ENABLE_FEATURE_CLEAN_UP && device_name) free(device_name); + if (ENABLE_FEATURE_CLEAN_UP && journal_device) free(journal_device); +} + +int tune2fs_main(int argc, char **argv) +{ + errcode_t retval; + ext2_filsys fs; + struct ext2_super_block *sb; + io_manager io_ptr; + + if (ENABLE_FEATURE_CLEAN_UP) + atexit(tune2fs_clean_up); + + if (ENABLE_FINDFS && (applet_name[0] == 'f')) /* findfs */ + do_findfs(argc, argv); /* no return */ + else if (ENABLE_E2LABEL && (applet_name[0] == 'e')) /* e2label */ + parse_e2label_options(argc, argv); + else + parse_tune2fs_options(argc, argv); /* tune2fs */ + + io_ptr = unix_io_manager; + retval = ext2fs_open2(device_name, io_options, open_flag, + 0, 0, io_ptr, &fs); + if (retval) + bb_error_msg_and_die("No valid superblock on %s", device_name); + sb = fs->super; + if (print_label) { + /* For e2label emulation */ + printf("%.*s\n", (int) sizeof(sb->s_volume_name), + sb->s_volume_name); + return 0; + } + retval = ext2fs_check_if_mounted(device_name, &mount_flags); + if (retval) + bb_error_msg_and_die("cannot determine if %s is mounted", device_name); + /* Normally we only need to write out the superblock */ + fs->flags |= EXT2_FLAG_SUPER_ONLY; + + if (c_flag) { + sb->s_max_mnt_count = max_mount_count; + ext2fs_mark_super_dirty(fs); + printf("Setting maximal mount count to %d\n", max_mount_count); + } + if (C_flag) { + sb->s_mnt_count = mount_count; + ext2fs_mark_super_dirty(fs); + printf("Setting current mount count to %d\n", mount_count); + } + if (e_flag) { + sb->s_errors = errors; + ext2fs_mark_super_dirty(fs); + printf("Setting error behavior to %d\n", errors); + } + if (g_flag) { + sb->s_def_resgid = resgid; + ext2fs_mark_super_dirty(fs); + printf("Setting reserved blocks gid to %lu\n", resgid); + } + if (i_flag) { + sb->s_checkinterval = interval; + ext2fs_mark_super_dirty(fs); + printf("Setting interval between check %lu seconds\n", interval); + } + if (m_flag) { + sb->s_r_blocks_count = (sb->s_blocks_count / 100) + * reserved_ratio; + ext2fs_mark_super_dirty(fs); + printf("Setting reserved blocks percentage to %u (%u blocks)\n", + reserved_ratio, sb->s_r_blocks_count); + } + if (r_flag) { + if (reserved_blocks >= sb->s_blocks_count/2) + bb_error_msg_and_die("reserved blocks count is too big (%lu)", reserved_blocks); + sb->s_r_blocks_count = reserved_blocks; + ext2fs_mark_super_dirty(fs); + printf("Setting reserved blocks count to %lu\n", reserved_blocks); + } + if (s_flag == 1) { + if (sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER) + bb_error_msg("\nThe filesystem already has sparse superblocks"); + else { + sb->s_feature_ro_compat |= + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + sb->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(fs); + printf("\nSparse superblock flag set. %s", please_fsck); + } + } + if (s_flag == 0) { + if (!(sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) + bb_error_msg("\nThe filesystem already has sparse superblocks disabled"); + else { + sb->s_feature_ro_compat &= + ~EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + sb->s_state &= ~EXT2_VALID_FS; + fs->flags |= EXT2_FLAG_MASTER_SB_ONLY; + ext2fs_mark_super_dirty(fs); + printf("\nSparse superblock flag cleared. %s", please_fsck); + } + } + if (T_flag) { + sb->s_lastcheck = last_check_time; + ext2fs_mark_super_dirty(fs); + printf("Setting time filesystem last checked to %s\n", + ctime(&last_check_time)); + } + if (u_flag) { + sb->s_def_resuid = resuid; + ext2fs_mark_super_dirty(fs); + printf("Setting reserved blocks uid to %lu\n", resuid); + } + if (L_flag) { + if (strlen(new_label) > sizeof(sb->s_volume_name)) + bb_error_msg("Warning: label too long, truncating"); + memset(sb->s_volume_name, 0, sizeof(sb->s_volume_name)); + safe_strncpy(sb->s_volume_name, new_label, + sizeof(sb->s_volume_name)); + ext2fs_mark_super_dirty(fs); + } + if (M_flag) { + memset(sb->s_last_mounted, 0, sizeof(sb->s_last_mounted)); + safe_strncpy(sb->s_last_mounted, new_last_mounted, + sizeof(sb->s_last_mounted)); + ext2fs_mark_super_dirty(fs); + } + if (mntopts_cmd) + update_mntopts(fs, mntopts_cmd); + if (features_cmd) + update_feature_set(fs, features_cmd); + if (journal_size || journal_device) + add_journal(fs); + + if (U_flag) { + if ((strcasecmp(new_UUID, "null") == 0) || + (strcasecmp(new_UUID, "clear") == 0)) { + uuid_clear(sb->s_uuid); + } else if (strcasecmp(new_UUID, "time") == 0) { + uuid_generate_time(sb->s_uuid); + } else if (strcasecmp(new_UUID, "random") == 0) { + uuid_generate(sb->s_uuid); + } else if (uuid_parse(new_UUID, sb->s_uuid)) { + bb_error_msg_and_die("Invalid UUID format"); + } + ext2fs_mark_super_dirty(fs); + } + + if (l_flag) + list_super (sb); + return (ext2fs_close (fs) ? 1 : 0); +} diff --git a/e2fsprogs/util.c b/e2fsprogs/util.c new file mode 100644 index 000000000..b30c294b8 --- /dev/null +++ b/e2fsprogs/util.c @@ -0,0 +1,267 @@ +/* vi: set sw=4 ts=4: */ +/* + * util.c --- helper functions used by tune2fs and mke2fs + * + * Copyright 1995, 1996, 1997, 1998, 1999, 2000 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include +#include + +#include "e2fsbb.h" +#include "e2p/e2p.h" +#include "ext2fs/ext2_fs.h" +#include "ext2fs/ext2fs.h" +#include "blkid/blkid.h" +#include "util.h" + +void proceed_question(void) +{ + fputs("Proceed anyway? (y,n) ", stdout); + if (bb_ask_confirmation() == 0) + exit(1); +} + +void check_plausibility(const char *device, int force) +{ + int val; + struct stat s; + val = stat(device, &s); + if (force) + return; + if(val == -1) + bb_perror_msg_and_die("cannot stat %s", device); + if (!S_ISBLK(s.st_mode)) { + printf("%s is not a block special device.\n", device); + proceed_question(); + return; + } + +#ifdef HAVE_LINUX_MAJOR_H +#ifndef MAJOR +#define MAJOR(dev) ((dev)>>8) +#define MINOR(dev) ((dev) & 0xff) +#endif +#ifndef SCSI_BLK_MAJOR +#ifdef SCSI_DISK0_MAJOR +#ifdef SCSI_DISK8_MAJOR +#define SCSI_DISK_MAJOR(M) ((M) == SCSI_DISK0_MAJOR || \ + ((M) >= SCSI_DISK1_MAJOR && (M) <= SCSI_DISK7_MAJOR) || \ + ((M) >= SCSI_DISK8_MAJOR && (M) <= SCSI_DISK15_MAJOR)) +#else +#define SCSI_DISK_MAJOR(M) ((M) == SCSI_DISK0_MAJOR || \ + ((M) >= SCSI_DISK1_MAJOR && (M) <= SCSI_DISK7_MAJOR)) +#endif /* defined(SCSI_DISK8_MAJOR) */ +#define SCSI_BLK_MAJOR(M) (SCSI_DISK_MAJOR((M)) || (M) == SCSI_CDROM_MAJOR) +#else +#define SCSI_BLK_MAJOR(M) ((M) == SCSI_DISK_MAJOR || (M) == SCSI_CDROM_MAJOR) +#endif /* defined(SCSI_DISK0_MAJOR) */ +#endif /* defined(SCSI_BLK_MAJOR) */ + if (((MAJOR(s.st_rdev) == HD_MAJOR && + MINOR(s.st_rdev)%64 == 0) || + (SCSI_BLK_MAJOR(MAJOR(s.st_rdev)) && + MINOR(s.st_rdev)%16 == 0))) { + printf("%s is entire device, not just one partition!\n", device); + proceed_question(); + } +#endif +} + +void check_mount(const char *device, int force, const char *type) +{ + errcode_t retval; + int mount_flags; + + retval = ext2fs_check_if_mounted(device, &mount_flags); + if (retval) { + bb_error_msg("cannot determine if %s is mounted", device); + return; + } + if (mount_flags & EXT2_MF_MOUNTED) { + bb_error_msg("%s is mounted !", device); +force_check: + if (force) + bb_error_msg("badblocks forced anyways"); + else + bb_error_msg_and_die("it's not safe to run badblocks!"); + } + + if (mount_flags & EXT2_MF_BUSY) { + bb_error_msg("%s is apparently in use by the system", device); + goto force_check; + } + +} + +void parse_journal_opts(char **journal_device, int *journal_flags, + int *journal_size, const char *opts) +{ + char *buf, *token, *next, *p, *arg; + int journal_usage = 0; + buf = xstrdup(opts); + for (token = buf; token && *token; token = next) { + p = strchr(token, ','); + next = 0; + if (p) { + *p = 0; + next = p+1; + } + arg = strchr(token, '='); + if (arg) { + *arg = 0; + arg++; + } + if (strcmp(token, "device") == 0) { + *journal_device = blkid_get_devname(NULL, arg, NULL); + if (!journal_device) { + journal_usage++; + continue; + } + } else if (strcmp(token, "size") == 0) { + if (!arg) { + journal_usage++; + continue; + } + (*journal_size) = strtoul(arg, &p, 0); + if (*p) + journal_usage++; + } else if (strcmp(token, "v1_superblock") == 0) { + (*journal_flags) |= EXT2_MKJOURNAL_V1_SUPER; + continue; + } else + journal_usage++; + } + if (journal_usage) + bb_error_msg_and_die( + "\nBad journal options specified.\n\n" + "Journal options are separated by commas, " + "and may take an argument which\n" + "\tis set off by an equals ('=') sign.\n\n" + "Valid journal options are:\n" + "\tsize=\n" + "\tdevice=\n\n" + "The journal size must be between " + "1024 and 102400 filesystem blocks.\n\n"); +} + +/* + * Determine the number of journal blocks to use, either via + * user-specified # of megabytes, or via some intelligently selected + * defaults. + * + * Find a reasonable journal file size (in blocks) given the number of blocks + * in the filesystem. For very small filesystems, it is not reasonable to + * have a journal that fills more than half of the filesystem. + */ +int figure_journal_size(int size, ext2_filsys fs) +{ + blk_t j_blocks; + + if (fs->super->s_blocks_count < 2048) { + bb_error_msg("Filesystem too small for a journal"); + return 0; + } + + if (size >= 0) { + j_blocks = size * 1024 / (fs->blocksize / 1024); + if (j_blocks < 1024 || j_blocks > 102400) + bb_error_msg_and_die("\nThe requested journal " + "size is %d blocks;\n it must be " + "between 1024 and 102400 blocks; Aborting", + j_blocks); + if (j_blocks > fs->super->s_free_blocks_count) + bb_error_msg_and_die("Journal size too big for filesystem"); + return j_blocks; + } + + if (fs->super->s_blocks_count < 32768) + j_blocks = 1024; + else if (fs->super->s_blocks_count < 256*1024) + j_blocks = 4096; + else if (fs->super->s_blocks_count < 512*1024) + j_blocks = 8192; + else if (fs->super->s_blocks_count < 1024*1024) + j_blocks = 16384; + else + j_blocks = 32768; + + return j_blocks; +} + +void print_check_message(ext2_filsys fs) +{ + printf("This filesystem will be automatically " + "checked every %d mounts or\n" + "%g days, whichever comes first. " + "Use tune2fs -c or -i to override.\n", + fs->super->s_max_mnt_count, + (double)fs->super->s_checkinterval / (3600 * 24)); +} + +void make_journal_device(char *journal_device, ext2_filsys fs, int quiet, int force) +{ + errcode_t retval; + ext2_filsys jfs; + io_manager io_ptr; + + check_plausibility(journal_device, force); + check_mount(journal_device, force, "journal"); + io_ptr = unix_io_manager; + retval = ext2fs_open(journal_device, EXT2_FLAG_RW| + EXT2_FLAG_JOURNAL_DEV_OK, 0, + fs->blocksize, io_ptr, &jfs); + if (retval) + bb_error_msg_and_die("cannot journal device %s", journal_device); + if(!quiet) + printf("Adding journal to device %s: ", journal_device); + fflush(stdout); + retval = ext2fs_add_journal_device(fs, jfs); + if(retval) + bb_error_msg_and_die("\nFailed to add journal to device %s", journal_device); + if(!quiet) + puts("done"); + ext2fs_close(jfs); +} + +void make_journal_blocks(ext2_filsys fs, int journal_size, int journal_flags, int quiet) +{ + unsigned long journal_blocks; + errcode_t retval; + + journal_blocks = figure_journal_size(journal_size, fs); + if (!journal_blocks) { + fs->super->s_feature_compat &= + ~EXT3_FEATURE_COMPAT_HAS_JOURNAL; + return; + } + if(!quiet) + printf("Creating journal (%ld blocks): ", journal_blocks); + fflush(stdout); + retval = ext2fs_add_journal_inode(fs, journal_blocks, + journal_flags); + if(retval) + bb_error_msg_and_die("cannot create journal"); + if(!quiet) + puts("done"); +} + +char *e2fs_set_sbin_path(void) +{ + char *oldpath = getenv("PATH"); + /* Update our PATH to include /sbin */ +#define PATH_SET "/sbin" + if (oldpath) + oldpath = xasprintf("%s:%s", PATH_SET, oldpath); + else + oldpath = PATH_SET; + putenv (oldpath); + return oldpath; +} diff --git a/e2fsprogs/util.h b/e2fsprogs/util.h new file mode 100644 index 000000000..80d241718 --- /dev/null +++ b/e2fsprogs/util.h @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * util.h --- header file defining prototypes for helper functions + * used by tune2fs and mke2fs + * + * Copyright 2000 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +extern void proceed_question(void); +extern void check_plausibility(const char *device, int force); +extern void parse_journal_opts(char **, int *, int *, const char *opts); +extern void check_mount(const char *device, int force, const char *type); +extern int figure_journal_size(int size, ext2_filsys fs); +extern void print_check_message(ext2_filsys fs); +extern void make_journal_device(char *journal_device, ext2_filsys fs, int quiet, int force); +extern void make_journal_blocks(ext2_filsys fs, int journal_size, int journal_flags, int quiet); +extern char *e2fs_set_sbin_path(void); diff --git a/e2fsprogs/uuid/Kbuild b/e2fsprogs/uuid/Kbuild new file mode 100644 index 000000000..dde981840 --- /dev/null +++ b/e2fsprogs/uuid/Kbuild @@ -0,0 +1,14 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +NEEDED-$(CONFIG_E2FSCK) = y +NEEDED-$(CONFIG_FSCK) = y +NEEDED-$(CONFIG_MKE2FS) = y +NEEDED-$(CONFIG_TUNE2FS) = y + +lib-y:= +lib-$(NEEDED-y) += compare.o gen_uuid.o pack.o parse.o unpack.o unparse.o \ + uuid_time.o diff --git a/e2fsprogs/uuid/compare.c b/e2fsprogs/uuid/compare.c new file mode 100644 index 000000000..348ea7c1f --- /dev/null +++ b/e2fsprogs/uuid/compare.c @@ -0,0 +1,55 @@ +/* vi: set sw=4 ts=4: */ +/* + * compare.c --- compare whether or not two UUID's are the same + * + * Returns 0 if the two UUID's are different, and 1 if they are the same. + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include "uuidP.h" +#include + +#define UUCMP(u1,u2) if (u1 != u2) return (u1 < u2) ? -1 : 1; + +int uuid_compare(const uuid_t uu1, const uuid_t uu2) +{ + struct uuid uuid1, uuid2; + + uuid_unpack(uu1, &uuid1); + uuid_unpack(uu2, &uuid2); + + UUCMP(uuid1.time_low, uuid2.time_low); + UUCMP(uuid1.time_mid, uuid2.time_mid); + UUCMP(uuid1.time_hi_and_version, uuid2.time_hi_and_version); + UUCMP(uuid1.clock_seq, uuid2.clock_seq); + return memcmp(uuid1.node, uuid2.node, 6); +} diff --git a/e2fsprogs/uuid/gen_uuid.c b/e2fsprogs/uuid/gen_uuid.c new file mode 100644 index 000000000..9d700a015 --- /dev/null +++ b/e2fsprogs/uuid/gen_uuid.c @@ -0,0 +1,305 @@ +/* vi: set sw=4 ts=4: */ +/* + * gen_uuid.c --- generate a DCE-compatible uuid + * + * Copyright (C) 1996, 1997, 1998, 1999 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#include +#ifdef HAVE_SYS_SOCKIO_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NET_IF_DL_H +#include +#endif + +#include "uuidP.h" + +#ifdef HAVE_SRANDOM +#define srand(x) srandom(x) +#define rand() random() +#endif + +static int get_random_fd(void) +{ + struct timeval tv; + static int fd = -2; + int i; + + if (fd == -2) { + gettimeofday(&tv, 0); + fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) + fd = open("/dev/random", O_RDONLY | O_NONBLOCK); + srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec); + } + /* Crank the random number generator a few times */ + gettimeofday(&tv, 0); + for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--) + rand(); + return fd; +} + + +/* + * Generate a series of random bytes. Use /dev/urandom if possible, + * and if not, use srandom/random. + */ +static void get_random_bytes(void *buf, int nbytes) +{ + int i, n = nbytes, fd = get_random_fd(); + int lose_counter = 0; + unsigned char *cp = (unsigned char *) buf; + + if (fd >= 0) { + while (n > 0) { + i = read(fd, cp, n); + if (i <= 0) { + if (lose_counter++ > 16) + break; + continue; + } + n -= i; + cp += i; + lose_counter = 0; + } + } + + /* + * We do this all the time, but this is the only source of + * randomness if /dev/random/urandom is out to lunch. + */ + for (cp = buf, i = 0; i < nbytes; i++) + *cp++ ^= (rand() >> 7) & 0xFF; + return; +} + +/* + * Get the ethernet hardware address, if we can find it... + */ +static int get_node_id(unsigned char *node_id) +{ +#ifdef HAVE_NET_IF_H + int sd; + struct ifreq ifr, *ifrp; + struct ifconf ifc; + char buf[1024]; + int n, i; + unsigned char *a; +#ifdef HAVE_NET_IF_DL_H + struct sockaddr_dl *sdlp; +#endif + +/* + * BSD 4.4 defines the size of an ifreq to be + * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len + * However, under earlier systems, sa_len isn't present, so the size is + * just sizeof(struct ifreq) + */ +#ifdef HAVE_SA_LEN +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif +#define ifreq_size(i) max(sizeof(struct ifreq),\ + sizeof((i).ifr_name)+(i).ifr_addr.sa_len) +#else +#define ifreq_size(i) sizeof(struct ifreq) +#endif /* HAVE_SA_LEN*/ + + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sd < 0) { + return -1; + } + memset(buf, 0, sizeof(buf)); + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl (sd, SIOCGIFCONF, (char *)&ifc) < 0) { + close(sd); + return -1; + } + n = ifc.ifc_len; + for (i = 0; i < n; i+= ifreq_size(*ifrp) ) { + ifrp = (struct ifreq *)((char *) ifc.ifc_buf+i); + strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); +#ifdef SIOCGIFHWADDR + if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) + continue; + a = (unsigned char *) &ifr.ifr_hwaddr.sa_data; +#else +#ifdef SIOCGENADDR + if (ioctl(sd, SIOCGENADDR, &ifr) < 0) + continue; + a = (unsigned char *) ifr.ifr_enaddr; +#else +#ifdef HAVE_NET_IF_DL_H + sdlp = (struct sockaddr_dl *) &ifrp->ifr_addr; + if ((sdlp->sdl_family != AF_LINK) || (sdlp->sdl_alen != 6)) + continue; + a = (unsigned char *) &sdlp->sdl_data[sdlp->sdl_nlen]; +#else + /* + * XXX we don't have a way of getting the hardware + * address + */ + close(sd); + return 0; +#endif /* HAVE_NET_IF_DL_H */ +#endif /* SIOCGENADDR */ +#endif /* SIOCGIFHWADDR */ + if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) + continue; + if (node_id) { + memcpy(node_id, a, 6); + close(sd); + return 1; + } + } + close(sd); +#endif + return 0; +} + +/* Assume that the gettimeofday() has microsecond granularity */ +#define MAX_ADJUSTMENT 10 + +static int get_clock(uint32_t *clock_high, uint32_t *clock_low, uint16_t *ret_clock_seq) +{ + static int adjustment = 0; + static struct timeval last = {0, 0}; + static uint16_t clock_seq; + struct timeval tv; + unsigned long long clock_reg; + +try_again: + gettimeofday(&tv, 0); + if ((last.tv_sec == 0) && (last.tv_usec == 0)) { + get_random_bytes(&clock_seq, sizeof(clock_seq)); + clock_seq &= 0x3FFF; + last = tv; + last.tv_sec--; + } + if ((tv.tv_sec < last.tv_sec) || + ((tv.tv_sec == last.tv_sec) && + (tv.tv_usec < last.tv_usec))) { + clock_seq = (clock_seq+1) & 0x3FFF; + adjustment = 0; + last = tv; + } else if ((tv.tv_sec == last.tv_sec) && + (tv.tv_usec == last.tv_usec)) { + if (adjustment >= MAX_ADJUSTMENT) + goto try_again; + adjustment++; + } else { + adjustment = 0; + last = tv; + } + + clock_reg = tv.tv_usec*10 + adjustment; + clock_reg += ((unsigned long long) tv.tv_sec)*10000000; + clock_reg += (((unsigned long long) 0x01B21DD2) << 32) + 0x13814000; + + *clock_high = clock_reg >> 32; + *clock_low = clock_reg; + *ret_clock_seq = clock_seq; + return 0; +} + +void uuid_generate_time(uuid_t out) +{ + static unsigned char node_id[6]; + static int has_init = 0; + struct uuid uu; + uint32_t clock_mid; + + if (!has_init) { + if (get_node_id(node_id) <= 0) { + get_random_bytes(node_id, 6); + /* + * Set multicast bit, to prevent conflicts + * with IEEE 802 addresses obtained from + * network cards + */ + node_id[0] |= 0x01; + } + has_init = 1; + } + get_clock(&clock_mid, &uu.time_low, &uu.clock_seq); + uu.clock_seq |= 0x8000; + uu.time_mid = (uint16_t) clock_mid; + uu.time_hi_and_version = ((clock_mid >> 16) & 0x0FFF) | 0x1000; + memcpy(uu.node, node_id, 6); + uuid_pack(&uu, out); +} + +void uuid_generate_random(uuid_t out) +{ + uuid_t buf; + struct uuid uu; + + get_random_bytes(buf, sizeof(buf)); + uuid_unpack(buf, &uu); + + uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000; + uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x4000; + uuid_pack(&uu, out); +} + +/* + * This is the generic front-end to uuid_generate_random and + * uuid_generate_time. It uses uuid_generate_random only if + * /dev/urandom is available, since otherwise we won't have + * high-quality randomness. + */ +void uuid_generate(uuid_t out) +{ + if (get_random_fd() >= 0) + uuid_generate_random(out); + else + uuid_generate_time(out); +} diff --git a/e2fsprogs/uuid/pack.c b/e2fsprogs/uuid/pack.c new file mode 100644 index 000000000..217cfce5d --- /dev/null +++ b/e2fsprogs/uuid/pack.c @@ -0,0 +1,69 @@ +/* vi: set sw=4 ts=4: */ +/* + * Internal routine for packing UUID's + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include "uuidP.h" + +void uuid_pack(const struct uuid *uu, uuid_t ptr) +{ + uint32_t tmp; + unsigned char *out = ptr; + + tmp = uu->time_low; + out[3] = (unsigned char) tmp; + tmp >>= 8; + out[2] = (unsigned char) tmp; + tmp >>= 8; + out[1] = (unsigned char) tmp; + tmp >>= 8; + out[0] = (unsigned char) tmp; + + tmp = uu->time_mid; + out[5] = (unsigned char) tmp; + tmp >>= 8; + out[4] = (unsigned char) tmp; + + tmp = uu->time_hi_and_version; + out[7] = (unsigned char) tmp; + tmp >>= 8; + out[6] = (unsigned char) tmp; + + tmp = uu->clock_seq; + out[9] = (unsigned char) tmp; + tmp >>= 8; + out[8] = (unsigned char) tmp; + + memcpy(out+10, uu->node, 6); +} diff --git a/e2fsprogs/uuid/parse.c b/e2fsprogs/uuid/parse.c new file mode 100644 index 000000000..9a3f9cb92 --- /dev/null +++ b/e2fsprogs/uuid/parse.c @@ -0,0 +1,80 @@ +/* vi: set sw=4 ts=4: */ +/* + * parse.c --- UUID parsing + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include +#include +#include + +#include "uuidP.h" + +int uuid_parse(const char *in, uuid_t uu) +{ + struct uuid uuid; + int i; + const char *cp; + char buf[3]; + + if (strlen(in) != 36) + return -1; + for (i=0, cp = in; i <= 36; i++,cp++) { + if ((i == 8) || (i == 13) || (i == 18) || + (i == 23)) { + if (*cp == '-') + continue; + else + return -1; + } + if (i== 36) + if (*cp == 0) + continue; + if (!isxdigit(*cp)) + return -1; + } + uuid.time_low = strtoul(in, NULL, 16); + uuid.time_mid = strtoul(in+9, NULL, 16); + uuid.time_hi_and_version = strtoul(in+14, NULL, 16); + uuid.clock_seq = strtoul(in+19, NULL, 16); + cp = in+24; + buf[2] = 0; + for (i=0; i < 6; i++) { + buf[0] = *cp++; + buf[1] = *cp++; + uuid.node[i] = strtoul(buf, NULL, 16); + } + + uuid_pack(&uuid, uu); + return 0; +} diff --git a/e2fsprogs/uuid/unpack.c b/e2fsprogs/uuid/unpack.c new file mode 100644 index 000000000..95d3aab4a --- /dev/null +++ b/e2fsprogs/uuid/unpack.c @@ -0,0 +1,63 @@ +/* vi: set sw=4 ts=4: */ +/* + * Internal routine for unpacking UUID + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include "uuidP.h" + +void uuid_unpack(const uuid_t in, struct uuid *uu) +{ + const uint8_t *ptr = in; + uint32_t tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_low = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_mid = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_hi_and_version = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->clock_seq = tmp; + + memcpy(uu->node, ptr, 6); +} diff --git a/e2fsprogs/uuid/unparse.c b/e2fsprogs/uuid/unparse.c new file mode 100644 index 000000000..d2948fe6d --- /dev/null +++ b/e2fsprogs/uuid/unparse.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* + * unparse.c -- convert a UUID to string + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include + +#include "uuidP.h" + +static const char *fmt_lower = + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x"; + +static const char *fmt_upper = + "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X"; + +#ifdef UUID_UNPARSE_DEFAULT_UPPER +#define FMT_DEFAULT fmt_upper +#else +#define FMT_DEFAULT fmt_lower +#endif + +static void uuid_unparse_x(const uuid_t uu, char *out, const char *fmt) +{ + struct uuid uuid; + + uuid_unpack(uu, &uuid); + sprintf(out, fmt, + uuid.time_low, uuid.time_mid, uuid.time_hi_and_version, + uuid.clock_seq >> 8, uuid.clock_seq & 0xFF, + uuid.node[0], uuid.node[1], uuid.node[2], + uuid.node[3], uuid.node[4], uuid.node[5]); +} + +void uuid_unparse_lower(const uuid_t uu, char *out) +{ + uuid_unparse_x(uu, out, fmt_lower); +} + +void uuid_unparse_upper(const uuid_t uu, char *out) +{ + uuid_unparse_x(uu, out, fmt_upper); +} + +void uuid_unparse(const uuid_t uu, char *out) +{ + uuid_unparse_x(uu, out, FMT_DEFAULT); +} diff --git a/e2fsprogs/uuid/uuid.h b/e2fsprogs/uuid/uuid.h new file mode 100644 index 000000000..bd53b15d0 --- /dev/null +++ b/e2fsprogs/uuid/uuid.h @@ -0,0 +1,104 @@ +/* vi: set sw=4 ts=4: */ +/* + * Public include file for the UUID library + * + * Copyright (C) 1996, 1997, 1998 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#ifndef _UUID_UUID_H +#define _UUID_UUID_H + +#include +#include + +typedef unsigned char uuid_t[16]; + +/* UUID Variant definitions */ +#define UUID_VARIANT_NCS 0 +#define UUID_VARIANT_DCE 1 +#define UUID_VARIANT_MICROSOFT 2 +#define UUID_VARIANT_OTHER 3 + +/* UUID Type definitions */ +#define UUID_TYPE_DCE_TIME 1 +#define UUID_TYPE_DCE_RANDOM 4 + +/* Allow UUID constants to be defined */ +#ifdef __GNUC__ +#define UUID_DEFINE(name,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15) \ + static const uuid_t name ATTRIBUTE_UNUSED = {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15} +#else +#define UUID_DEFINE(name,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15) \ + static const uuid_t name = {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15} +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* clear.c */ +/*void uuid_clear(uuid_t uu);*/ +#define uuid_clear(uu) memset(uu, 0, sizeof(uu)) + +/* compare.c */ +int uuid_compare(const uuid_t uu1, const uuid_t uu2); + +/* copy.c */ +/*void uuid_copy(uuid_t dst, const uuid_t src);*/ +#define uuid_copy(dst,src) memcpy(dst, src, sizeof(dst)) + +/* gen_uuid.c */ +void uuid_generate(uuid_t out); +void uuid_generate_random(uuid_t out); +void uuid_generate_time(uuid_t out); + +/* isnull.c */ +/*int uuid_is_null(const uuid_t uu);*/ +#define uuid_is_null(uu) (!memcmp(uu, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", sizeof(uu))) + +/* parse.c */ +int uuid_parse(const char *in, uuid_t uu); + +/* unparse.c */ +void uuid_unparse(const uuid_t uu, char *out); +void uuid_unparse_lower(const uuid_t uu, char *out); +void uuid_unparse_upper(const uuid_t uu, char *out); + +/* uuid_time.c */ +time_t uuid_time(const uuid_t uu, struct timeval *ret_tv); +int uuid_type(const uuid_t uu); +int uuid_variant(const uuid_t uu); + +#ifdef __cplusplus +} +#endif + +#endif /* _UUID_UUID_H */ diff --git a/e2fsprogs/uuid/uuidP.h b/e2fsprogs/uuid/uuidP.h new file mode 100644 index 000000000..87041ef0a --- /dev/null +++ b/e2fsprogs/uuid/uuidP.h @@ -0,0 +1,60 @@ +/* vi: set sw=4 ts=4: */ +/* + * uuid.h -- private header file for uuids + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include + +#include "uuid.h" + +/* + * Offset between 15-Oct-1582 and 1-Jan-70 + */ +#define TIME_OFFSET_HIGH 0x01B21DD2 +#define TIME_OFFSET_LOW 0x13814000 + +struct uuid { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint16_t clock_seq; + uint8_t node[6]; +}; + + +/* + * prototypes + */ +void uuid_pack(const struct uuid *uu, uuid_t ptr); +void uuid_unpack(const uuid_t in, struct uuid *uu); diff --git a/e2fsprogs/uuid/uuid_time.c b/e2fsprogs/uuid/uuid_time.c new file mode 100644 index 000000000..b54d67322 --- /dev/null +++ b/e2fsprogs/uuid/uuid_time.c @@ -0,0 +1,161 @@ +/* vi: set sw=4 ts=4: */ +/* + * uuid_time.c --- Interpret the time field from a uuid. This program + * violates the UUID abstraction barrier by reaching into the guts + * of a UUID and interpreting it. + * + * Copyright (C) 1998, 1999 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include +#include +#include +#include + +#include "uuidP.h" + +time_t uuid_time(const uuid_t uu, struct timeval *ret_tv) +{ + struct uuid uuid; + uint32_t high; + struct timeval tv; + unsigned long long clock_reg; + + uuid_unpack(uu, &uuid); + + high = uuid.time_mid | ((uuid.time_hi_and_version & 0xFFF) << 16); + clock_reg = uuid.time_low | ((unsigned long long) high << 32); + + clock_reg -= (((unsigned long long) 0x01B21DD2) << 32) + 0x13814000; + tv.tv_sec = clock_reg / 10000000; + tv.tv_usec = (clock_reg % 10000000) / 10; + + if (ret_tv) + *ret_tv = tv; + + return tv.tv_sec; +} + +int uuid_type(const uuid_t uu) +{ + struct uuid uuid; + + uuid_unpack(uu, &uuid); + return ((uuid.time_hi_and_version >> 12) & 0xF); +} + +int uuid_variant(const uuid_t uu) +{ + struct uuid uuid; + int var; + + uuid_unpack(uu, &uuid); + var = uuid.clock_seq; + + if ((var & 0x8000) == 0) + return UUID_VARIANT_NCS; + if ((var & 0x4000) == 0) + return UUID_VARIANT_DCE; + if ((var & 0x2000) == 0) + return UUID_VARIANT_MICROSOFT; + return UUID_VARIANT_OTHER; +} + +#ifdef DEBUG +static const char *variant_string(int variant) +{ + switch (variant) { + case UUID_VARIANT_NCS: + return "NCS"; + case UUID_VARIANT_DCE: + return "DCE"; + case UUID_VARIANT_MICROSOFT: + return "Microsoft"; + default: + return "Other"; + } +} + + +int +main(int argc, char **argv) +{ + uuid_t buf; + time_t time_reg; + struct timeval tv; + int type, variant; + + if (argc != 2) { + fprintf(stderr, "Usage: %s uuid\n", argv[0]); + exit(1); + } + if (uuid_parse(argv[1], buf)) { + fprintf(stderr, "Invalid UUID: %s\n", argv[1]); + exit(1); + } + variant = uuid_variant(buf); + type = uuid_type(buf); + time_reg = uuid_time(buf, &tv); + + printf("UUID variant is %d (%s)\n", variant, variant_string(variant)); + if (variant != UUID_VARIANT_DCE) { + printf("Warning: This program only knows how to interpret " + "DCE UUIDs.\n\tThe rest of the output is likely " + "to be incorrect!!\n"); + } + printf("UUID type is %d", type); + switch (type) { + case 1: + printf(" (time based)\n"); + break; + case 2: + printf(" (DCE)\n"); + break; + case 3: + printf(" (name-based)\n"); + break; + case 4: + printf(" (random)\n"); + break; + default: + puts(""); + } + if (type != 1) { + printf("Warning: not a time-based UUID, so UUID time " + "decoding will likely not work!\n"); + } + printf("UUID time is: (%ld, %ld): %s\n", tv.tv_sec, tv.tv_usec, + ctime(&time_reg)); + + return 0; +} +#endif diff --git a/editors/Config.in b/editors/Config.in new file mode 100644 index 000000000..4ba009019 --- /dev/null +++ b/editors/Config.in @@ -0,0 +1,131 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Editors" + +config AWK + bool "awk" + default n + help + Awk is used as a pattern scanning and processing language. This is + the BusyBox implementation of that programming language. + +config FEATURE_AWK_MATH + bool "Enable math functions (requires libm)" + default y + depends on AWK + help + Enable math functions of the Awk programming language. + NOTE: This will require libm to be present for linking. + +config ED + bool "ed" + default n + help + The original 1970's Unix text editor, from the days of teletypes. + Small, simple, evil. Part of SUSv3. If you're not already using + this, you don't need it. + +config PATCH + bool "patch" + default n + help + Apply a unified diff formatted patch. + +config SED + bool "sed" + default n + help + sed is used to perform text transformations on a file + or input from a pipeline. + +config VI + bool "vi" + default n + help + 'vi' is a text editor. More specifically, it is the One True + text editor . It does, however, have a rather steep + learning curve. If you are not already comfortable with 'vi' + you may wish to use something else. + +config FEATURE_VI_COLON + bool "Enable \":\" colon commands (no \"ex\" mode)" + default y + depends on VI + help + Enable a limited set of colon commands for vi. This does not + provide an "ex" mode. + +config FEATURE_VI_YANKMARK + bool "Enable yank/put commands and mark cmds" + default y + depends on VI + help + This will enable you to use yank and put, as well as mark in + busybox vi. + +config FEATURE_VI_SEARCH + bool "Enable search and replace cmds" + default y + depends on VI + help + Select this if you wish to be able to do search and replace in + busybox vi. + +config FEATURE_VI_USE_SIGNALS + bool "Catch signals" + default y + depends on VI + help + Selecting this option will make busybox vi signal aware. This will + make busybox vi support SIGWINCH to deal with Window Changes, catch + Ctrl-Z and Ctrl-C and alarms. + +config FEATURE_VI_DOT_CMD + bool "Remember previous cmd and \".\" cmd" + default y + depends on VI + help + Make busybox vi remember the last command and be able to repeat it. + +config FEATURE_VI_READONLY + bool "Enable -R option and \"view\" mode" + default y + depends on VI + help + Enable the read-only command line option, which allows the user to + open a file in read-only mode. + +config FEATURE_VI_SETOPTS + bool "Enable set-able options, ai ic showmatch" + default y + depends on VI + help + Enable the editor to set some (ai, ic, showmatch) options. + +config FEATURE_VI_SET + bool "Support for :set" + default y + depends on VI + help + Support for ":set". + +config FEATURE_VI_WIN_RESIZE + bool "Handle window resize" + default y + depends on VI + help + Make busybox vi behave nicely with terminals that get resized. + +config FEATURE_VI_OPTIMIZE_CURSOR + bool "Optimize cursor movement" + default y + depends on VI + help + This will make the cursor movement faster, but requires more memory + and it makes the applet a tiny bit larger. + +endmenu + diff --git a/editors/Kbuild b/editors/Kbuild new file mode 100644 index 000000000..d991e1faf --- /dev/null +++ b/editors/Kbuild @@ -0,0 +1,12 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_AWK) += awk.o +lib-$(CONFIG_ED) += ed.o +lib-$(CONFIG_PATCH) += patch.o +lib-$(CONFIG_SED) += sed.o +lib-$(CONFIG_VI) += vi.o diff --git a/editors/awk.c b/editors/awk.c new file mode 100644 index 000000000..9386f4ec0 --- /dev/null +++ b/editors/awk.c @@ -0,0 +1,2769 @@ +/* vi: set sw=4 ts=4: */ +/* + * awk implementation for busybox + * + * Copyright (C) 2002 by Dmitry Zakharov + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include "xregex.h" +#include + + +#define MAXVARFMT 240 +#define MINNVBLOCK 64 + +/* variable flags */ +#define VF_NUMBER 0x0001 /* 1 = primary type is number */ +#define VF_ARRAY 0x0002 /* 1 = it's an array */ + +#define VF_CACHED 0x0100 /* 1 = num/str value has cached str/num eq */ +#define VF_USER 0x0200 /* 1 = user input (may be numeric string) */ +#define VF_SPECIAL 0x0400 /* 1 = requires extra handling when changed */ +#define VF_WALK 0x0800 /* 1 = variable has alloc'd x.walker list */ +#define VF_FSTR 0x1000 /* 1 = string points to fstring buffer */ +#define VF_CHILD 0x2000 /* 1 = function arg; x.parent points to source */ +#define VF_DIRTY 0x4000 /* 1 = variable was set explicitly */ + +/* these flags are static, don't change them when value is changed */ +#define VF_DONTTOUCH (VF_ARRAY | VF_SPECIAL | VF_WALK | VF_CHILD | VF_DIRTY) + +/* Variable */ +typedef struct var_s { + unsigned short type; /* flags */ + double number; + char *string; + union { + int aidx; /* func arg idx (for compilation stage) */ + struct xhash_s *array; /* array ptr */ + struct var_s *parent; /* for func args, ptr to actual parameter */ + char **walker; /* list of array elements (for..in) */ + } x; +} var; + +/* Node chain (pattern-action chain, BEGIN, END, function bodies) */ +typedef struct chain_s { + struct node_s *first; + struct node_s *last; + char *programname; +} chain; + +/* Function */ +typedef struct func_s { + unsigned short nargs; + struct chain_s body; +} func; + +/* I/O stream */ +typedef struct rstream_s { + FILE *F; + char *buffer; + int adv; + int size; + int pos; + unsigned short is_pipe; +} rstream; + +typedef struct hash_item_s { + union { + struct var_s v; /* variable/array hash */ + struct rstream_s rs; /* redirect streams hash */ + struct func_s f; /* functions hash */ + } data; + struct hash_item_s *next; /* next in chain */ + char name[1]; /* really it's longer */ +} hash_item; + +typedef struct xhash_s { + unsigned int nel; /* num of elements */ + unsigned int csize; /* current hash size */ + unsigned int nprime; /* next hash size in PRIMES[] */ + unsigned int glen; /* summary length of item names */ + struct hash_item_s **items; +} xhash; + +/* Tree node */ +typedef struct node_s { + uint32_t info; + unsigned short lineno; + union { + struct node_s *n; + var *v; + int i; + char *s; + regex_t *re; + } l; + union { + struct node_s *n; + regex_t *ire; + func *f; + int argno; + } r; + union { + struct node_s *n; + } a; +} node; + +/* Block of temporary variables */ +typedef struct nvblock_s { + int size; + var *pos; + struct nvblock_s *prev; + struct nvblock_s *next; + var nv[0]; +} nvblock; + +typedef struct tsplitter_s { + node n; + regex_t re[2]; +} tsplitter; + +/* simple token classes */ +/* Order and hex values are very important!!! See next_token() */ +#define TC_SEQSTART 1 /* ( */ +#define TC_SEQTERM (1 << 1) /* ) */ +#define TC_REGEXP (1 << 2) /* /.../ */ +#define TC_OUTRDR (1 << 3) /* | > >> */ +#define TC_UOPPOST (1 << 4) /* unary postfix operator */ +#define TC_UOPPRE1 (1 << 5) /* unary prefix operator */ +#define TC_BINOPX (1 << 6) /* two-opnd operator */ +#define TC_IN (1 << 7) +#define TC_COMMA (1 << 8) +#define TC_PIPE (1 << 9) /* input redirection pipe */ +#define TC_UOPPRE2 (1 << 10) /* unary prefix operator */ +#define TC_ARRTERM (1 << 11) /* ] */ +#define TC_GRPSTART (1 << 12) /* { */ +#define TC_GRPTERM (1 << 13) /* } */ +#define TC_SEMICOL (1 << 14) +#define TC_NEWLINE (1 << 15) +#define TC_STATX (1 << 16) /* ctl statement (for, next...) */ +#define TC_WHILE (1 << 17) +#define TC_ELSE (1 << 18) +#define TC_BUILTIN (1 << 19) +#define TC_GETLINE (1 << 20) +#define TC_FUNCDECL (1 << 21) /* `function' `func' */ +#define TC_BEGIN (1 << 22) +#define TC_END (1 << 23) +#define TC_EOF (1 << 24) +#define TC_VARIABLE (1 << 25) +#define TC_ARRAY (1 << 26) +#define TC_FUNCTION (1 << 27) +#define TC_STRING (1 << 28) +#define TC_NUMBER (1 << 29) + +#define TC_UOPPRE (TC_UOPPRE1 | TC_UOPPRE2) + +/* combined token classes */ +#define TC_BINOP (TC_BINOPX | TC_COMMA | TC_PIPE | TC_IN) +#define TC_UNARYOP (TC_UOPPRE | TC_UOPPOST) +#define TC_OPERAND (TC_VARIABLE | TC_ARRAY | TC_FUNCTION | \ + TC_BUILTIN | TC_GETLINE | TC_SEQSTART | TC_STRING | TC_NUMBER) + +#define TC_STATEMNT (TC_STATX | TC_WHILE) +#define TC_OPTERM (TC_SEMICOL | TC_NEWLINE) + +/* word tokens, cannot mean something else if not expected */ +#define TC_WORD (TC_IN | TC_STATEMNT | TC_ELSE | TC_BUILTIN | \ + TC_GETLINE | TC_FUNCDECL | TC_BEGIN | TC_END) + +/* discard newlines after these */ +#define TC_NOTERM (TC_COMMA | TC_GRPSTART | TC_GRPTERM | \ + TC_BINOP | TC_OPTERM) + +/* what can expression begin with */ +#define TC_OPSEQ (TC_OPERAND | TC_UOPPRE | TC_REGEXP) +/* what can group begin with */ +#define TC_GRPSEQ (TC_OPSEQ | TC_OPTERM | TC_STATEMNT | TC_GRPSTART) + +/* if previous token class is CONCAT1 and next is CONCAT2, concatenation */ +/* operator is inserted between them */ +#define TC_CONCAT1 (TC_VARIABLE | TC_ARRTERM | TC_SEQTERM | \ + TC_STRING | TC_NUMBER | TC_UOPPOST) +#define TC_CONCAT2 (TC_OPERAND | TC_UOPPRE) + +#define OF_RES1 0x010000 +#define OF_RES2 0x020000 +#define OF_STR1 0x040000 +#define OF_STR2 0x080000 +#define OF_NUM1 0x100000 +#define OF_CHECKED 0x200000 + +/* combined operator flags */ +#define xx 0 +#define xV OF_RES2 +#define xS (OF_RES2 | OF_STR2) +#define Vx OF_RES1 +#define VV (OF_RES1 | OF_RES2) +#define Nx (OF_RES1 | OF_NUM1) +#define NV (OF_RES1 | OF_NUM1 | OF_RES2) +#define Sx (OF_RES1 | OF_STR1) +#define SV (OF_RES1 | OF_STR1 | OF_RES2) +#define SS (OF_RES1 | OF_STR1 | OF_RES2 | OF_STR2) + +#define OPCLSMASK 0xFF00 +#define OPNMASK 0x007F + +/* operator priority is a highest byte (even: r->l, odd: l->r grouping) + * For builtins it has different meaning: n n s3 s2 s1 v3 v2 v1, + * n - min. number of args, vN - resolve Nth arg to var, sN - resolve to string + */ +#define P(x) (x << 24) +#define PRIMASK 0x7F000000 +#define PRIMASK2 0x7E000000 + +/* Operation classes */ + +#define SHIFT_TIL_THIS 0x0600 +#define RECUR_FROM_THIS 0x1000 + +enum { + OC_DELETE=0x0100, OC_EXEC=0x0200, OC_NEWSOURCE=0x0300, + OC_PRINT=0x0400, OC_PRINTF=0x0500, OC_WALKINIT=0x0600, + + OC_BR=0x0700, OC_BREAK=0x0800, OC_CONTINUE=0x0900, + OC_EXIT=0x0a00, OC_NEXT=0x0b00, OC_NEXTFILE=0x0c00, + OC_TEST=0x0d00, OC_WALKNEXT=0x0e00, + + OC_BINARY=0x1000, OC_BUILTIN=0x1100, OC_COLON=0x1200, + OC_COMMA=0x1300, OC_COMPARE=0x1400, OC_CONCAT=0x1500, + OC_FBLTIN=0x1600, OC_FIELD=0x1700, OC_FNARG=0x1800, + OC_FUNC=0x1900, OC_GETLINE=0x1a00, OC_IN=0x1b00, + OC_LAND=0x1c00, OC_LOR=0x1d00, OC_MATCH=0x1e00, + OC_MOVE=0x1f00, OC_PGETLINE=0x2000, OC_REGEXP=0x2100, + OC_REPLACE=0x2200, OC_RETURN=0x2300, OC_SPRINTF=0x2400, + OC_TERNARY=0x2500, OC_UNARY=0x2600, OC_VAR=0x2700, + OC_DONE=0x2800, + + ST_IF=0x3000, ST_DO=0x3100, ST_FOR=0x3200, + ST_WHILE=0x3300 +}; + +/* simple builtins */ +enum { + F_in=0, F_rn, F_co, F_ex, F_lg, F_si, F_sq, F_sr, + F_ti, F_le, F_sy, F_ff, F_cl +}; + +/* builtins */ +enum { + B_a2=0, B_ix, B_ma, B_sp, B_ss, B_ti, B_lo, B_up, + B_ge, B_gs, B_su, + B_an, B_co, B_ls, B_or, B_rs, B_xo, +}; + +/* tokens and their corresponding info values */ + +#define NTC "\377" /* switch to next token class (tc<<1) */ +#define NTCC '\377' + +#define OC_B OC_BUILTIN + +static char * const tokenlist = + "\1(" NTC + "\1)" NTC + "\1/" NTC /* REGEXP */ + "\2>>" "\1>" "\1|" NTC /* OUTRDR */ + "\2++" "\2--" NTC /* UOPPOST */ + "\2++" "\2--" "\1$" NTC /* UOPPRE1 */ + "\2==" "\1=" "\2+=" "\2-=" /* BINOPX */ + "\2*=" "\2/=" "\2%=" "\2^=" + "\1+" "\1-" "\3**=" "\2**" + "\1/" "\1%" "\1^" "\1*" + "\2!=" "\2>=" "\2<=" "\1>" + "\1<" "\2!~" "\1~" "\2&&" + "\2||" "\1?" "\1:" NTC + "\2in" NTC + "\1," NTC + "\1|" NTC + "\1+" "\1-" "\1!" NTC /* UOPPRE2 */ + "\1]" NTC + "\1{" NTC + "\1}" NTC + "\1;" NTC + "\1\n" NTC + "\2if" "\2do" "\3for" "\5break" /* STATX */ + "\10continue" "\6delete" "\5print" + "\6printf" "\4next" "\10nextfile" + "\6return" "\4exit" NTC + "\5while" NTC + "\4else" NTC + + "\3and" "\5compl" "\6lshift" "\2or" + "\6rshift" "\3xor" + "\5close" "\6system" "\6fflush" "\5atan2" /* BUILTIN */ + "\3cos" "\3exp" "\3int" "\3log" + "\4rand" "\3sin" "\4sqrt" "\5srand" + "\6gensub" "\4gsub" "\5index" "\6length" + "\5match" "\5split" "\7sprintf" "\3sub" + "\6substr" "\7systime" "\10strftime" + "\7tolower" "\7toupper" NTC + "\7getline" NTC + "\4func" "\10function" NTC + "\5BEGIN" NTC + "\3END" "\0" + ; + +static const uint32_t tokeninfo[] = { + + 0, + 0, + OC_REGEXP, + xS|'a', xS|'w', xS|'|', + OC_UNARY|xV|P(9)|'p', OC_UNARY|xV|P(9)|'m', + OC_UNARY|xV|P(9)|'P', OC_UNARY|xV|P(9)|'M', + OC_FIELD|xV|P(5), + OC_COMPARE|VV|P(39)|5, OC_MOVE|VV|P(74), + OC_REPLACE|NV|P(74)|'+', OC_REPLACE|NV|P(74)|'-', + OC_REPLACE|NV|P(74)|'*', OC_REPLACE|NV|P(74)|'/', + OC_REPLACE|NV|P(74)|'%', OC_REPLACE|NV|P(74)|'&', + OC_BINARY|NV|P(29)|'+', OC_BINARY|NV|P(29)|'-', + OC_REPLACE|NV|P(74)|'&', OC_BINARY|NV|P(15)|'&', + OC_BINARY|NV|P(25)|'/', OC_BINARY|NV|P(25)|'%', + OC_BINARY|NV|P(15)|'&', OC_BINARY|NV|P(25)|'*', + OC_COMPARE|VV|P(39)|4, OC_COMPARE|VV|P(39)|3, + OC_COMPARE|VV|P(39)|0, OC_COMPARE|VV|P(39)|1, + OC_COMPARE|VV|P(39)|2, OC_MATCH|Sx|P(45)|'!', + OC_MATCH|Sx|P(45)|'~', OC_LAND|Vx|P(55), + OC_LOR|Vx|P(59), OC_TERNARY|Vx|P(64)|'?', + OC_COLON|xx|P(67)|':', + OC_IN|SV|P(49), + OC_COMMA|SS|P(80), + OC_PGETLINE|SV|P(37), + OC_UNARY|xV|P(19)|'+', OC_UNARY|xV|P(19)|'-', + OC_UNARY|xV|P(19)|'!', + 0, + 0, + 0, + 0, + 0, + ST_IF, ST_DO, ST_FOR, OC_BREAK, + OC_CONTINUE, OC_DELETE|Vx, OC_PRINT, + OC_PRINTF, OC_NEXT, OC_NEXTFILE, + OC_RETURN|Vx, OC_EXIT|Nx, + ST_WHILE, + 0, + + OC_B|B_an|P(0x83), OC_B|B_co|P(0x41), OC_B|B_ls|P(0x83), OC_B|B_or|P(0x83), + OC_B|B_rs|P(0x83), OC_B|B_xo|P(0x83), + OC_FBLTIN|Sx|F_cl, OC_FBLTIN|Sx|F_sy, OC_FBLTIN|Sx|F_ff, OC_B|B_a2|P(0x83), + OC_FBLTIN|Nx|F_co, OC_FBLTIN|Nx|F_ex, OC_FBLTIN|Nx|F_in, OC_FBLTIN|Nx|F_lg, + OC_FBLTIN|F_rn, OC_FBLTIN|Nx|F_si, OC_FBLTIN|Nx|F_sq, OC_FBLTIN|Nx|F_sr, + OC_B|B_ge|P(0xd6), OC_B|B_gs|P(0xb6), OC_B|B_ix|P(0x9b), OC_FBLTIN|Sx|F_le, + OC_B|B_ma|P(0x89), OC_B|B_sp|P(0x8b), OC_SPRINTF, OC_B|B_su|P(0xb6), + OC_B|B_ss|P(0x8f), OC_FBLTIN|F_ti, OC_B|B_ti|P(0x0b), + OC_B|B_lo|P(0x49), OC_B|B_up|P(0x49), + OC_GETLINE|SV|P(0), + 0, 0, + 0, + 0 +}; + +/* internal variable names and their initial values */ +/* asterisk marks SPECIAL vars; $ is just no-named Field0 */ +enum { + CONVFMT=0, OFMT, FS, OFS, + ORS, RS, RT, FILENAME, + SUBSEP, ARGIND, ARGC, ARGV, + ERRNO, FNR, + NR, NF, IGNORECASE, + ENVIRON, F0, _intvarcount_ +}; + +static char * vNames = + "CONVFMT\0" "OFMT\0" "FS\0*" "OFS\0" + "ORS\0" "RS\0*" "RT\0" "FILENAME\0" + "SUBSEP\0" "ARGIND\0" "ARGC\0" "ARGV\0" + "ERRNO\0" "FNR\0" + "NR\0" "NF\0*" "IGNORECASE\0*" + "ENVIRON\0" "$\0*" "\0"; + +static char * vValues = + "%.6g\0" "%.6g\0" " \0" " \0" + "\n\0" "\n\0" "\0" "\0" + "\034\0" + "\377"; + +/* hash size may grow to these values */ +#define FIRST_PRIME 61; +static const unsigned int PRIMES[] = { 251, 1021, 4093, 16381, 65521 }; +enum { NPRIMES = sizeof(PRIMES) / sizeof(unsigned int) }; + +/* globals */ + +extern char **environ; + +static var * V[_intvarcount_]; +static chain beginseq, mainseq, endseq, *seq; +static int nextrec, nextfile; +static node *break_ptr, *continue_ptr; +static rstream *iF; +static xhash *vhash, *ahash, *fdhash, *fnhash; +static char *programname; +static short lineno; +static int is_f0_split; +static int nfields; +static var *Fields; +static tsplitter fsplitter, rsplitter; +static nvblock *cb; +static char *pos; +static char *buf; +static int icase; +static int exiting; + +static struct { + uint32_t tclass; + uint32_t info; + char *string; + double number; + short lineno; + int rollback; +} t; + +/* function prototypes */ +static void handle_special(var *); +static node *parse_expr(uint32_t); +static void chain_group(void); +static var *evaluate(node *, var *); +static rstream *next_input_file(void); +static int fmt_num(char *, int, const char *, double, int); +static int awk_exit(int) ATTRIBUTE_NORETURN; + +/* ---- error handling ---- */ + +static const char EMSG_INTERNAL_ERROR[] = "Internal error"; +static const char EMSG_UNEXP_EOS[] = "Unexpected end of string"; +static const char EMSG_UNEXP_TOKEN[] = "Unexpected token"; +static const char EMSG_DIV_BY_ZERO[] = "Division by zero"; +static const char EMSG_INV_FMT[] = "Invalid format specifier"; +static const char EMSG_TOO_FEW_ARGS[] = "Too few arguments for builtin"; +static const char EMSG_NOT_ARRAY[] = "Not an array"; +static const char EMSG_POSSIBLE_ERROR[] = "Possible syntax error"; +static const char EMSG_UNDEF_FUNC[] = "Call to undefined function"; +#ifndef CONFIG_FEATURE_AWK_MATH +static const char EMSG_NO_MATH[] = "Math support is not compiled in"; +#endif + +static void syntax_error(const char * const message) ATTRIBUTE_NORETURN; +static void syntax_error(const char * const message) +{ + bb_error_msg_and_die("%s:%i: %s", programname, lineno, message); +} + +#define runtime_error(x) syntax_error(x) + + +/* ---- hash stuff ---- */ + +static unsigned int hashidx(const char *name) +{ + unsigned int idx=0; + + while (*name) idx = *name++ + (idx << 6) - idx; + return idx; +} + +/* create new hash */ +static xhash *hash_init(void) +{ + xhash *newhash; + + newhash = (xhash *)xzalloc(sizeof(xhash)); + newhash->csize = FIRST_PRIME; + newhash->items = (hash_item **)xzalloc(newhash->csize * sizeof(hash_item *)); + + return newhash; +} + +/* find item in hash, return ptr to data, NULL if not found */ +static void *hash_search(xhash *hash, const char *name) +{ + hash_item *hi; + + hi = hash->items [ hashidx(name) % hash->csize ]; + while (hi) { + if (strcmp(hi->name, name) == 0) + return &(hi->data); + hi = hi->next; + } + return NULL; +} + +/* grow hash if it becomes too big */ +static void hash_rebuild(xhash *hash) +{ + unsigned int newsize, i, idx; + hash_item **newitems, *hi, *thi; + + if (hash->nprime == NPRIMES) + return; + + newsize = PRIMES[hash->nprime++]; + newitems = (hash_item **)xzalloc(newsize * sizeof(hash_item *)); + + for (i=0; icsize; i++) { + hi = hash->items[i]; + while (hi) { + thi = hi; + hi = thi->next; + idx = hashidx(thi->name) % newsize; + thi->next = newitems[idx]; + newitems[idx] = thi; + } + } + + free(hash->items); + hash->csize = newsize; + hash->items = newitems; +} + +/* find item in hash, add it if necessary. Return ptr to data */ +static void *hash_find(xhash *hash, const char *name) +{ + hash_item *hi; + unsigned int idx; + int l; + + hi = hash_search(hash, name); + if (! hi) { + if (++hash->nel / hash->csize > 10) + hash_rebuild(hash); + + l = strlen(name) + 1; + hi = xzalloc(sizeof(hash_item) + l); + memcpy(hi->name, name, l); + + idx = hashidx(name) % hash->csize; + hi->next = hash->items[idx]; + hash->items[idx] = hi; + hash->glen += l; + } + return &(hi->data); +} + +#define findvar(hash, name) (var *) hash_find ( (hash) , (name) ) +#define newvar(name) (var *) hash_find ( vhash , (name) ) +#define newfile(name) (rstream *) hash_find ( fdhash , (name) ) +#define newfunc(name) (func *) hash_find ( fnhash , (name) ) + +static void hash_remove(xhash *hash, const char *name) +{ + hash_item *hi, **phi; + + phi = &(hash->items[ hashidx(name) % hash->csize ]); + while (*phi) { + hi = *phi; + if (strcmp(hi->name, name) == 0) { + hash->glen -= (strlen(name) + 1); + hash->nel--; + *phi = hi->next; + free(hi); + break; + } + phi = &(hi->next); + } +} + +/* ------ some useful functions ------ */ + +static void skip_spaces(char **s) +{ + char *p = *s; + + while(*p == ' ' || *p == '\t' || + (*p == '\\' && *(p+1) == '\n' && (++p, ++t.lineno))) { + p++; + } + *s = p; +} + +static char *nextword(char **s) +{ + char *p = *s; + + while (*(*s)++) ; + + return p; +} + +static char nextchar(char **s) +{ + char c, *pps; + + c = *((*s)++); + pps = *s; + if (c == '\\') c = bb_process_escape_sequence((const char**)s); + if (c == '\\' && *s == pps) c = *((*s)++); + return c; +} + +static int ATTRIBUTE_ALWAYS_INLINE isalnum_(int c) +{ + return (isalnum(c) || c == '_'); +} + +static FILE *afopen(const char *path, const char *mode) +{ + return (*path == '-' && *(path+1) == '\0') ? stdin : xfopen(path, mode); +} + +/* -------- working with variables (set/get/copy/etc) -------- */ + +static xhash *iamarray(var *v) +{ + var *a = v; + + while (a->type & VF_CHILD) + a = a->x.parent; + + if (! (a->type & VF_ARRAY)) { + a->type |= VF_ARRAY; + a->x.array = hash_init(); + } + return a->x.array; +} + +static void clear_array(xhash *array) +{ + unsigned int i; + hash_item *hi, *thi; + + for (i=0; icsize; i++) { + hi = array->items[i]; + while (hi) { + thi = hi; + hi = hi->next; + free(thi->data.v.string); + free(thi); + } + array->items[i] = NULL; + } + array->glen = array->nel = 0; +} + +/* clear a variable */ +static var *clrvar(var *v) +{ + if (!(v->type & VF_FSTR)) + free(v->string); + + v->type &= VF_DONTTOUCH; + v->type |= VF_DIRTY; + v->string = NULL; + return v; +} + +/* assign string value to variable */ +static var *setvar_p(var *v, char *value) +{ + clrvar(v); + v->string = value; + handle_special(v); + + return v; +} + +/* same as setvar_p but make a copy of string */ +static var *setvar_s(var *v, const char *value) +{ + return setvar_p(v, (value && *value) ? xstrdup(value) : NULL); +} + +/* same as setvar_s but set USER flag */ +static var *setvar_u(var *v, const char *value) +{ + setvar_s(v, value); + v->type |= VF_USER; + return v; +} + +/* set array element to user string */ +static void setari_u(var *a, int idx, const char *s) +{ + var *v; + static char sidx[12]; + + sprintf(sidx, "%d", idx); + v = findvar(iamarray(a), sidx); + setvar_u(v, s); +} + +/* assign numeric value to variable */ +static var *setvar_i(var *v, double value) +{ + clrvar(v); + v->type |= VF_NUMBER; + v->number = value; + handle_special(v); + return v; +} + +static char *getvar_s(var *v) +{ + /* if v is numeric and has no cached string, convert it to string */ + if ((v->type & (VF_NUMBER | VF_CACHED)) == VF_NUMBER) { + fmt_num(buf, MAXVARFMT, getvar_s(V[CONVFMT]), v->number, TRUE); + v->string = xstrdup(buf); + v->type |= VF_CACHED; + } + return (v->string == NULL) ? "" : v->string; +} + +static double getvar_i(var *v) +{ + char *s; + + if ((v->type & (VF_NUMBER | VF_CACHED)) == 0) { + v->number = 0; + s = v->string; + if (s && *s) { + v->number = strtod(s, &s); + if (v->type & VF_USER) { + skip_spaces(&s); + if (*s != '\0') + v->type &= ~VF_USER; + } + } else { + v->type &= ~VF_USER; + } + v->type |= VF_CACHED; + } + return v->number; +} + +static var *copyvar(var *dest, const var *src) +{ + if (dest != src) { + clrvar(dest); + dest->type |= (src->type & ~VF_DONTTOUCH); + dest->number = src->number; + if (src->string) + dest->string = xstrdup(src->string); + } + handle_special(dest); + return dest; +} + +static var *incvar(var *v) +{ + return setvar_i(v, getvar_i(v)+1.); +} + +/* return true if v is number or numeric string */ +static int is_numeric(var *v) +{ + getvar_i(v); + return ((v->type ^ VF_DIRTY) & (VF_NUMBER | VF_USER | VF_DIRTY)); +} + +/* return 1 when value of v corresponds to true, 0 otherwise */ +static int istrue(var *v) +{ + if (is_numeric(v)) + return (v->number == 0) ? 0 : 1; + else + return (v->string && *(v->string)) ? 1 : 0; +} + +/* temporary variables allocator. Last allocated should be first freed */ +static var *nvalloc(int n) +{ + nvblock *pb = NULL; + var *v, *r; + int size; + + while (cb) { + pb = cb; + if ((cb->pos - cb->nv) + n <= cb->size) break; + cb = cb->next; + } + + if (! cb) { + size = (n <= MINNVBLOCK) ? MINNVBLOCK : n; + cb = (nvblock *)xmalloc(sizeof(nvblock) + size * sizeof(var)); + cb->size = size; + cb->pos = cb->nv; + cb->prev = pb; + cb->next = NULL; + if (pb) pb->next = cb; + } + + v = r = cb->pos; + cb->pos += n; + + while (v < cb->pos) { + v->type = 0; + v->string = NULL; + v++; + } + + return r; +} + +static void nvfree(var *v) +{ + var *p; + + if (v < cb->nv || v >= cb->pos) + runtime_error(EMSG_INTERNAL_ERROR); + + for (p=v; ppos; p++) { + if ((p->type & (VF_ARRAY|VF_CHILD)) == VF_ARRAY) { + clear_array(iamarray(p)); + free(p->x.array->items); + free(p->x.array); + } + if (p->type & VF_WALK) + free(p->x.walker); + + clrvar(p); + } + + cb->pos = v; + while (cb->prev && cb->pos == cb->nv) { + cb = cb->prev; + } +} + +/* ------- awk program text parsing ------- */ + +/* Parse next token pointed by global pos, place results into global t. + * If token isn't expected, give away. Return token class + */ +static uint32_t next_token(uint32_t expected) +{ + char *p, *pp, *s; + char *tl; + uint32_t tc; + const uint32_t *ti; + int l; + static int concat_inserted; + static uint32_t save_tclass, save_info; + static uint32_t ltclass = TC_OPTERM; + + if (t.rollback) { + + t.rollback = FALSE; + + } else if (concat_inserted) { + + concat_inserted = FALSE; + t.tclass = save_tclass; + t.info = save_info; + + } else { + + p = pos; + + readnext: + skip_spaces(&p); + lineno = t.lineno; + if (*p == '#') + while (*p != '\n' && *p != '\0') p++; + + if (*p == '\n') + t.lineno++; + + if (*p == '\0') { + tc = TC_EOF; + + } else if (*p == '\"') { + /* it's a string */ + t.string = s = ++p; + while (*p != '\"') { + if (*p == '\0' || *p == '\n') + syntax_error(EMSG_UNEXP_EOS); + *(s++) = nextchar(&p); + } + p++; + *s = '\0'; + tc = TC_STRING; + + } else if ((expected & TC_REGEXP) && *p == '/') { + /* it's regexp */ + t.string = s = ++p; + while (*p != '/') { + if (*p == '\0' || *p == '\n') + syntax_error(EMSG_UNEXP_EOS); + if ((*s++ = *p++) == '\\') { + pp = p; + *(s-1) = bb_process_escape_sequence((const char **)&p); + if (*pp == '\\') *s++ = '\\'; + if (p == pp) *s++ = *p++; + } + } + p++; + *s = '\0'; + tc = TC_REGEXP; + + } else if (*p == '.' || isdigit(*p)) { + /* it's a number */ + t.number = strtod(p, &p); + if (*p == '.') + syntax_error(EMSG_UNEXP_TOKEN); + tc = TC_NUMBER; + + } else { + /* search for something known */ + tl = tokenlist; + tc = 0x00000001; + ti = tokeninfo; + while (*tl) { + l = *(tl++); + if (l == NTCC) { + tc <<= 1; + continue; + } + /* if token class is expected, token + * matches and it's not a longer word, + * then this is what we are looking for + */ + if ((tc & (expected | TC_WORD | TC_NEWLINE)) && + *tl == *p && strncmp(p, tl, l) == 0 && + !((tc & TC_WORD) && isalnum_(*(p + l)))) { + t.info = *ti; + p += l; + break; + } + ti++; + tl += l; + } + + if (! *tl) { + /* it's a name (var/array/function), + * otherwise it's something wrong + */ + if (! isalnum_(*p)) + syntax_error(EMSG_UNEXP_TOKEN); + + t.string = --p; + while(isalnum_(*(++p))) { + *(p-1) = *p; + } + *(p-1) = '\0'; + tc = TC_VARIABLE; + /* also consume whitespace between functionname and bracket */ + if (! (expected & TC_VARIABLE)) skip_spaces(&p); + if (*p == '(') { + tc = TC_FUNCTION; + } else { + if (*p == '[') { + p++; + tc = TC_ARRAY; + } + } + } + } + pos = p; + + /* skipping newlines in some cases */ + if ((ltclass & TC_NOTERM) && (tc & TC_NEWLINE)) + goto readnext; + + /* insert concatenation operator when needed */ + if ((ltclass&TC_CONCAT1) && (tc&TC_CONCAT2) && (expected&TC_BINOP)) { + concat_inserted = TRUE; + save_tclass = tc; + save_info = t.info; + tc = TC_BINOP; + t.info = OC_CONCAT | SS | P(35); + } + + t.tclass = tc; + } + ltclass = t.tclass; + + /* Are we ready for this? */ + if (! (ltclass & expected)) + syntax_error((ltclass & (TC_NEWLINE | TC_EOF)) ? + EMSG_UNEXP_EOS : EMSG_UNEXP_TOKEN); + + return ltclass; +} + +static void rollback_token(void) { t.rollback = TRUE; } + +static node *new_node(uint32_t info) +{ + node *n; + + n = (node *)xzalloc(sizeof(node)); + n->info = info; + n->lineno = lineno; + return n; +} + +static node *mk_re_node(char *s, node *n, regex_t *re) +{ + n->info = OC_REGEXP; + n->l.re = re; + n->r.ire = re + 1; + xregcomp(re, s, REG_EXTENDED); + xregcomp(re+1, s, REG_EXTENDED | REG_ICASE); + + return n; +} + +static node *condition(void) +{ + next_token(TC_SEQSTART); + return parse_expr(TC_SEQTERM); +} + +/* parse expression terminated by given argument, return ptr + * to built subtree. Terminator is eaten by parse_expr */ +static node *parse_expr(uint32_t iexp) +{ + node sn; + node *cn = &sn; + node *vn, *glptr; + uint32_t tc, xtc; + var *v; + + sn.info = PRIMASK; + sn.r.n = glptr = NULL; + xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP | iexp; + + while (! ((tc = next_token(xtc)) & iexp)) { + if (glptr && (t.info == (OC_COMPARE|VV|P(39)|2))) { + /* input redirection (<) attached to glptr node */ + cn = glptr->l.n = new_node(OC_CONCAT|SS|P(37)); + cn->a.n = glptr; + xtc = TC_OPERAND | TC_UOPPRE; + glptr = NULL; + + } else if (tc & (TC_BINOP | TC_UOPPOST)) { + /* for binary and postfix-unary operators, jump back over + * previous operators with higher priority */ + vn = cn; + while ( ((t.info & PRIMASK) > (vn->a.n->info & PRIMASK2)) || + ((t.info == vn->info) && ((t.info & OPCLSMASK) == OC_COLON)) ) + vn = vn->a.n; + if ((t.info & OPCLSMASK) == OC_TERNARY) + t.info += P(6); + cn = vn->a.n->r.n = new_node(t.info); + cn->a.n = vn->a.n; + if (tc & TC_BINOP) { + cn->l.n = vn; + xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP; + if ((t.info & OPCLSMASK) == OC_PGETLINE) { + /* it's a pipe */ + next_token(TC_GETLINE); + /* give maximum priority to this pipe */ + cn->info &= ~PRIMASK; + xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp; + } + } else { + cn->r.n = vn; + xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp; + } + vn->a.n = cn; + + } else { + /* for operands and prefix-unary operators, attach them + * to last node */ + vn = cn; + cn = vn->r.n = new_node(t.info); + cn->a.n = vn; + xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP; + if (tc & (TC_OPERAND | TC_REGEXP)) { + xtc = TC_UOPPRE | TC_UOPPOST | TC_BINOP | TC_OPERAND | iexp; + /* one should be very careful with switch on tclass - + * only simple tclasses should be used! */ + switch (tc) { + case TC_VARIABLE: + case TC_ARRAY: + cn->info = OC_VAR; + if ((v = hash_search(ahash, t.string)) != NULL) { + cn->info = OC_FNARG; + cn->l.i = v->x.aidx; + } else { + cn->l.v = newvar(t.string); + } + if (tc & TC_ARRAY) { + cn->info |= xS; + cn->r.n = parse_expr(TC_ARRTERM); + } + break; + + case TC_NUMBER: + case TC_STRING: + cn->info = OC_VAR; + v = cn->l.v = xzalloc(sizeof(var)); + if (tc & TC_NUMBER) + setvar_i(v, t.number); + else + setvar_s(v, t.string); + break; + + case TC_REGEXP: + mk_re_node(t.string, cn, + (regex_t *)xzalloc(sizeof(regex_t)*2)); + break; + + case TC_FUNCTION: + cn->info = OC_FUNC; + cn->r.f = newfunc(t.string); + cn->l.n = condition(); + break; + + case TC_SEQSTART: + cn = vn->r.n = parse_expr(TC_SEQTERM); + cn->a.n = vn; + break; + + case TC_GETLINE: + glptr = cn; + xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp; + break; + + case TC_BUILTIN: + cn->l.n = condition(); + break; + } + } + } + } + return sn.r.n; +} + +/* add node to chain. Return ptr to alloc'd node */ +static node *chain_node(uint32_t info) +{ + node *n; + + if (! seq->first) + seq->first = seq->last = new_node(0); + + if (seq->programname != programname) { + seq->programname = programname; + n = chain_node(OC_NEWSOURCE); + n->l.s = xstrdup(programname); + } + + n = seq->last; + n->info = info; + seq->last = n->a.n = new_node(OC_DONE); + + return n; +} + +static void chain_expr(uint32_t info) +{ + node *n; + + n = chain_node(info); + n->l.n = parse_expr(TC_OPTERM | TC_GRPTERM); + if (t.tclass & TC_GRPTERM) + rollback_token(); +} + +static node *chain_loop(node *nn) +{ + node *n, *n2, *save_brk, *save_cont; + + save_brk = break_ptr; + save_cont = continue_ptr; + + n = chain_node(OC_BR | Vx); + continue_ptr = new_node(OC_EXEC); + break_ptr = new_node(OC_EXEC); + chain_group(); + n2 = chain_node(OC_EXEC | Vx); + n2->l.n = nn; + n2->a.n = n; + continue_ptr->a.n = n2; + break_ptr->a.n = n->r.n = seq->last; + + continue_ptr = save_cont; + break_ptr = save_brk; + + return n; +} + +/* parse group and attach it to chain */ +static void chain_group(void) +{ + uint32_t c; + node *n, *n2, *n3; + + do { + c = next_token(TC_GRPSEQ); + } while (c & TC_NEWLINE); + + if (c & TC_GRPSTART) { + while(next_token(TC_GRPSEQ | TC_GRPTERM) != TC_GRPTERM) { + if (t.tclass & TC_NEWLINE) continue; + rollback_token(); + chain_group(); + } + } else if (c & (TC_OPSEQ | TC_OPTERM)) { + rollback_token(); + chain_expr(OC_EXEC | Vx); + } else { /* TC_STATEMNT */ + switch (t.info & OPCLSMASK) { + case ST_IF: + n = chain_node(OC_BR | Vx); + n->l.n = condition(); + chain_group(); + n2 = chain_node(OC_EXEC); + n->r.n = seq->last; + if (next_token(TC_GRPSEQ | TC_GRPTERM | TC_ELSE)==TC_ELSE) { + chain_group(); + n2->a.n = seq->last; + } else { + rollback_token(); + } + break; + + case ST_WHILE: + n2 = condition(); + n = chain_loop(NULL); + n->l.n = n2; + break; + + case ST_DO: + n2 = chain_node(OC_EXEC); + n = chain_loop(NULL); + n2->a.n = n->a.n; + next_token(TC_WHILE); + n->l.n = condition(); + break; + + case ST_FOR: + next_token(TC_SEQSTART); + n2 = parse_expr(TC_SEMICOL | TC_SEQTERM); + if (t.tclass & TC_SEQTERM) { /* for-in */ + if ((n2->info & OPCLSMASK) != OC_IN) + syntax_error(EMSG_UNEXP_TOKEN); + n = chain_node(OC_WALKINIT | VV); + n->l.n = n2->l.n; + n->r.n = n2->r.n; + n = chain_loop(NULL); + n->info = OC_WALKNEXT | Vx; + n->l.n = n2->l.n; + } else { /* for(;;) */ + n = chain_node(OC_EXEC | Vx); + n->l.n = n2; + n2 = parse_expr(TC_SEMICOL); + n3 = parse_expr(TC_SEQTERM); + n = chain_loop(n3); + n->l.n = n2; + if (! n2) + n->info = OC_EXEC; + } + break; + + case OC_PRINT: + case OC_PRINTF: + n = chain_node(t.info); + n->l.n = parse_expr(TC_OPTERM | TC_OUTRDR | TC_GRPTERM); + if (t.tclass & TC_OUTRDR) { + n->info |= t.info; + n->r.n = parse_expr(TC_OPTERM | TC_GRPTERM); + } + if (t.tclass & TC_GRPTERM) + rollback_token(); + break; + + case OC_BREAK: + n = chain_node(OC_EXEC); + n->a.n = break_ptr; + break; + + case OC_CONTINUE: + n = chain_node(OC_EXEC); + n->a.n = continue_ptr; + break; + + /* delete, next, nextfile, return, exit */ + default: + chain_expr(t.info); + + } + } +} + +static void parse_program(char *p) +{ + uint32_t tclass; + node *cn; + func *f; + var *v; + + pos = p; + t.lineno = 1; + while((tclass = next_token(TC_EOF | TC_OPSEQ | TC_GRPSTART | + TC_OPTERM | TC_BEGIN | TC_END | TC_FUNCDECL)) != TC_EOF) { + + if (tclass & TC_OPTERM) + continue; + + seq = &mainseq; + if (tclass & TC_BEGIN) { + seq = &beginseq; + chain_group(); + + } else if (tclass & TC_END) { + seq = &endseq; + chain_group(); + + } else if (tclass & TC_FUNCDECL) { + next_token(TC_FUNCTION); + pos++; + f = newfunc(t.string); + f->body.first = NULL; + f->nargs = 0; + while(next_token(TC_VARIABLE | TC_SEQTERM) & TC_VARIABLE) { + v = findvar(ahash, t.string); + v->x.aidx = (f->nargs)++; + + if (next_token(TC_COMMA | TC_SEQTERM) & TC_SEQTERM) + break; + } + seq = &(f->body); + chain_group(); + clear_array(ahash); + + } else if (tclass & TC_OPSEQ) { + rollback_token(); + cn = chain_node(OC_TEST); + cn->l.n = parse_expr(TC_OPTERM | TC_EOF | TC_GRPSTART); + if (t.tclass & TC_GRPSTART) { + rollback_token(); + chain_group(); + } else { + chain_node(OC_PRINT); + } + cn->r.n = mainseq.last; + + } else /* if (tclass & TC_GRPSTART) */ { + rollback_token(); + chain_group(); + } + } +} + + +/* -------- program execution part -------- */ + +static node *mk_splitter(char *s, tsplitter *spl) +{ + regex_t *re, *ire; + node *n; + + re = &spl->re[0]; + ire = &spl->re[1]; + n = &spl->n; + if ((n->info & OPCLSMASK) == OC_REGEXP) { + regfree(re); + regfree(ire); + } + if (strlen(s) > 1) { + mk_re_node(s, n, re); + } else { + n->info = (uint32_t) *s; + } + + return n; +} + +/* use node as a regular expression. Supplied with node ptr and regex_t + * storage space. Return ptr to regex (if result points to preg, it should + * be later regfree'd manually + */ +static regex_t *as_regex(node *op, regex_t *preg) +{ + var *v; + char *s; + + if ((op->info & OPCLSMASK) == OC_REGEXP) { + return icase ? op->r.ire : op->l.re; + } else { + v = nvalloc(1); + s = getvar_s(evaluate(op, v)); + xregcomp(preg, s, icase ? REG_EXTENDED | REG_ICASE : REG_EXTENDED); + nvfree(v); + return preg; + } +} + +/* gradually increasing buffer */ +static void qrealloc(char **b, int n, int *size) +{ + if (! *b || n >= *size) + *b = xrealloc(*b, *size = n + (n>>1) + 80); +} + +/* resize field storage space */ +static void fsrealloc(int size) +{ + static int maxfields = 0; + int i; + + if (size >= maxfields) { + i = maxfields; + maxfields = size + 16; + Fields = (var *)xrealloc(Fields, maxfields * sizeof(var)); + for (; iinfo; + c[2] = c[3] = '\0'; + if (*getvar_s(V[RS]) == '\0') c[2] = '\n'; + + if ((spl->info & OPCLSMASK) == OC_REGEXP) { /* regex split */ + while (*s) { + l = strcspn(s, c+2); + if (regexec(icase ? spl->r.ire : spl->l.re, s, 1, pmatch, 0) == 0 && + pmatch[0].rm_so <= l) { + l = pmatch[0].rm_so; + if (pmatch[0].rm_eo == 0) { l++; pmatch[0].rm_eo++; } + } else { + pmatch[0].rm_eo = l; + if (*(s+l)) pmatch[0].rm_eo++; + } + + memcpy(s1, s, l); + *(s1+l) = '\0'; + nextword(&s1); + s += pmatch[0].rm_eo; + n++; + } + } else if (c[0] == '\0') { /* null split */ + while(*s) { + *(s1++) = *(s++); + *(s1++) = '\0'; + n++; + } + } else if (c[0] != ' ') { /* single-character split */ + if (icase) { + c[0] = toupper(c[0]); + c[1] = tolower(c[1]); + } + if (*s1) n++; + while ((s1 = strpbrk(s1, c))) { + *(s1++) = '\0'; + n++; + } + } else { /* space split */ + while (*s) { + s = skip_whitespace(s); + if (! *s) break; + n++; + while (*s && !isspace(*s)) + *(s1++) = *(s++); + *(s1++) = '\0'; + } + } + return n; +} + +static void split_f0(void) +{ + static char *fstrings = NULL; + int i, n; + char *s; + + if (is_f0_split) + return; + + is_f0_split = TRUE; + free(fstrings); + fsrealloc(0); + n = awk_split(getvar_s(V[F0]), &fsplitter.n, &fstrings); + fsrealloc(n); + s = fstrings; + for (i=0; itype = VF_NUMBER | VF_SPECIAL; + V[NF]->number = nfields; +} + +/* perform additional actions when some internal variables changed */ +static void handle_special(var *v) +{ + int n; + char *b, *sep, *s; + int sl, l, len, i, bsize; + + if (! (v->type & VF_SPECIAL)) + return; + + if (v == V[NF]) { + n = (int)getvar_i(v); + fsrealloc(n); + + /* recalculate $0 */ + sep = getvar_s(V[OFS]); + sl = strlen(sep); + b = NULL; + len = 0; + for (i=0; i v-Fields ? n : v-Fields+1); + /* right here v is invalid. Just to note... */ + } +} + +/* step through func/builtin/etc arguments */ +static node *nextarg(node **pn) +{ + node *n; + + n = *pn; + if (n && (n->info & OPCLSMASK) == OC_COMMA) { + *pn = n->r.n; + n = n->l.n; + } else { + *pn = NULL; + } + return n; +} + +static void hashwalk_init(var *v, xhash *array) +{ + char **w; + hash_item *hi; + int i; + + if (v->type & VF_WALK) + free(v->x.walker); + + v->type |= VF_WALK; + w = v->x.walker = (char **)xzalloc(2 + 2*sizeof(char *) + array->glen); + *w = *(w+1) = (char *)(w + 2); + for (i=0; icsize; i++) { + hi = array->items[i]; + while(hi) { + strcpy(*w, hi->name); + nextword(w); + hi = hi->next; + } + } +} + +static int hashwalk_next(var *v) +{ + char **w; + + w = v->x.walker; + if (*(w+1) == *w) + return FALSE; + + setvar_s(v, nextword(w+1)); + return TRUE; +} + +/* evaluate node, return 1 when result is true, 0 otherwise */ +static int ptest(node *pattern) +{ + static var v; + return istrue(evaluate(pattern, &v)); +} + +/* read next record from stream rsm into a variable v */ +static int awk_getline(rstream *rsm, var *v) +{ + char *b; + regmatch_t pmatch[2]; + int a, p, pp=0, size; + int fd, so, eo, r, rp; + char c, *m, *s; + + /* we're using our own buffer since we need access to accumulating + * characters + */ + fd = fileno(rsm->F); + m = rsm->buffer; + a = rsm->adv; + p = rsm->pos; + size = rsm->size; + c = (char) rsplitter.n.info; + rp = 0; + + if (! m) qrealloc(&m, 256, &size); + do { + b = m + a; + so = eo = p; + r = 1; + if (p > 0) { + if ((rsplitter.n.info & OPCLSMASK) == OC_REGEXP) { + if (regexec(icase ? rsplitter.n.r.ire : rsplitter.n.l.re, + b, 1, pmatch, 0) == 0) { + so = pmatch[0].rm_so; + eo = pmatch[0].rm_eo; + if (b[eo] != '\0') + break; + } + } else if (c != '\0') { + s = strchr(b+pp, c); + if (! s) s = memchr(b+pp, '\0', p - pp); + if (s) { + so = eo = s-b; + eo++; + break; + } + } else { + while (b[rp] == '\n') + rp++; + s = strstr(b+rp, "\n\n"); + if (s) { + so = eo = s-b; + while (b[eo] == '\n') eo++; + if (b[eo] != '\0') + break; + } + } + } + + if (a > 0) { + memmove(m, (const void *)(m+a), p+1); + b = m; + a = 0; + } + + qrealloc(&m, a+p+128, &size); + b = m + a; + pp = p; + p += safe_read(fd, b+p, size-p-1); + if (p < pp) { + p = 0; + r = 0; + setvar_i(V[ERRNO], errno); + } + b[p] = '\0'; + + } while (p > pp); + + if (p == 0) { + r--; + } else { + c = b[so]; b[so] = '\0'; + setvar_s(v, b+rp); + v->type |= VF_USER; + b[so] = c; + c = b[eo]; b[eo] = '\0'; + setvar_s(V[RT], b+so); + b[eo] = c; + } + + rsm->buffer = m; + rsm->adv = a + eo; + rsm->pos = p - eo; + rsm->size = size; + + return r; +} + +static int fmt_num(char *b, int size, const char *format, double n, int int_as_int) +{ + int r=0; + char c; + const char *s=format; + + if (int_as_int && n == (int)n) { + r = snprintf(b, size, "%d", (int)n); + } else { + do { c = *s; } while (*s && *++s); + if (strchr("diouxX", c)) { + r = snprintf(b, size, format, (int)n); + } else if (strchr("eEfgG", c)) { + r = snprintf(b, size, format, n); + } else { + runtime_error(EMSG_INV_FMT); + } + } + return r; +} + + +/* formatted output into an allocated buffer, return ptr to buffer */ +static char *awk_printf(node *n) +{ + char *b = NULL; + char *fmt, *s, *s1, *f; + int i, j, incr, bsize; + char c, c1; + var *v, *arg; + + v = nvalloc(1); + fmt = f = xstrdup(getvar_s(evaluate(nextarg(&n), v))); + + i = 0; + while (*f) { + s = f; + while (*f && (*f != '%' || *(++f) == '%')) + f++; + while (*f && !isalpha(*f)) + f++; + + incr = (f - s) + MAXVARFMT; + qrealloc(&b, incr+i, &bsize); + c = *f; if (c != '\0') f++; + c1 = *f ; *f = '\0'; + arg = evaluate(nextarg(&n), v); + + j = i; + if (c == 'c' || !c) { + i += sprintf(b+i, s, + is_numeric(arg) ? (char)getvar_i(arg) : *getvar_s(arg)); + + } else if (c == 's') { + s1 = getvar_s(arg); + qrealloc(&b, incr+i+strlen(s1), &bsize); + i += sprintf(b+i, s, s1); + + } else { + i += fmt_num(b+i, incr, s, getvar_i(arg), FALSE); + } + *f = c1; + + /* if there was an error while sprintf, return value is negative */ + if (i < j) i = j; + + } + + b = xrealloc(b, i+1); + free(fmt); + nvfree(v); + b[i] = '\0'; + return b; +} + +/* common substitution routine + * replace (nm) substring of (src) that match (n) with (repl), store + * result into (dest), return number of substitutions. If nm=0, replace + * all matches. If src or dst is NULL, use $0. If ex=TRUE, enable + * subexpression matching (\1-\9) + */ +static int awk_sub(node *rn, char *repl, int nm, var *src, var *dest, int ex) +{ + char *ds = NULL; + char *sp, *s; + int c, i, j, di, rl, so, eo, nbs, n, dssize; + regmatch_t pmatch[10]; + regex_t sreg, *re; + + re = as_regex(rn, &sreg); + if (! src) src = V[F0]; + if (! dest) dest = V[F0]; + + i = di = 0; + sp = getvar_s(src); + rl = strlen(repl); + while (regexec(re, sp, 10, pmatch, sp==getvar_s(src) ? 0:REG_NOTBOL) == 0) { + so = pmatch[0].rm_so; + eo = pmatch[0].rm_eo; + + qrealloc(&ds, di + eo + rl, &dssize); + memcpy(ds + di, sp, eo); + di += eo; + if (++i >= nm) { + /* replace */ + di -= (eo - so); + nbs = 0; + for (s = repl; *s; s++) { + ds[di++] = c = *s; + if (c == '\\') { + nbs++; + continue; + } + if (c == '&' || (ex && c >= '0' && c <= '9')) { + di -= ((nbs + 3) >> 1); + j = 0; + if (c != '&') { + j = c - '0'; + nbs++; + } + if (nbs % 2) { + ds[di++] = c; + } else { + n = pmatch[j].rm_eo - pmatch[j].rm_so; + qrealloc(&ds, di + rl + n, &dssize); + memcpy(ds + di, sp + pmatch[j].rm_so, n); + di += n; + } + } + nbs = 0; + } + } + + sp += eo; + if (i == nm) break; + if (eo == so) { + if (! (ds[di++] = *sp++)) break; + } + } + + qrealloc(&ds, di + strlen(sp), &dssize); + strcpy(ds + di, sp); + setvar_p(dest, ds); + if (re == &sreg) regfree(re); + return i; +} + +static var *exec_builtin(node *op, var *res) +{ + int (*to_xxx)(int); + var *tv; + node *an[4]; + var *av[4]; + char *as[4]; + regmatch_t pmatch[2]; + regex_t sreg, *re; + static tsplitter tspl; + node *spl; + uint32_t isr, info; + int nargs; + time_t tt; + char *s, *s1; + int i, l, ll, n; + + tv = nvalloc(4); + isr = info = op->info; + op = op->l.n; + + av[2] = av[3] = NULL; + for (i=0 ; i<4 && op ; i++) { + an[i] = nextarg(&op); + if (isr & 0x09000000) av[i] = evaluate(an[i], &tv[i]); + if (isr & 0x08000000) as[i] = getvar_s(av[i]); + isr >>= 1; + } + + nargs = i; + if (nargs < (info >> 30)) + runtime_error(EMSG_TOO_FEW_ARGS); + + switch (info & OPNMASK) { + + case B_a2: +#ifdef CONFIG_FEATURE_AWK_MATH + setvar_i(res, atan2(getvar_i(av[i]), getvar_i(av[1]))); +#else + runtime_error(EMSG_NO_MATH); +#endif + break; + + case B_sp: + if (nargs > 2) { + spl = (an[2]->info & OPCLSMASK) == OC_REGEXP ? + an[2] : mk_splitter(getvar_s(evaluate(an[2], &tv[2])), &tspl); + } else { + spl = &fsplitter.n; + } + + n = awk_split(as[0], spl, &s); + s1 = s; + clear_array(iamarray(av[1])); + for (i=1; i<=n; i++) + setari_u(av[1], i, nextword(&s1)); + free(s); + setvar_i(res, n); + break; + + case B_ss: + l = strlen(as[0]); + i = getvar_i(av[1]) - 1; + if (i>l) i=l; if (i<0) i=0; + n = (nargs > 2) ? getvar_i(av[2]) : l-i; + if (n<0) n=0; + s = xmalloc(n+1); + strncpy(s, as[0]+i, n); + s[n] = '\0'; + setvar_p(res, s); + break; + + case B_an: + setvar_i(res, (long)getvar_i(av[0]) & (long)getvar_i(av[1])); + break; + + case B_co: + setvar_i(res, ~(long)getvar_i(av[0])); + break; + + case B_ls: + setvar_i(res, (long)getvar_i(av[0]) << (long)getvar_i(av[1])); + break; + + case B_or: + setvar_i(res, (long)getvar_i(av[0]) | (long)getvar_i(av[1])); + break; + + case B_rs: + setvar_i(res, (long)((unsigned long)getvar_i(av[0]) >> (unsigned long)getvar_i(av[1]))); + break; + + case B_xo: + setvar_i(res, (long)getvar_i(av[0]) ^ (long)getvar_i(av[1])); + break; + + case B_lo: + to_xxx = tolower; + goto lo_cont; + + case B_up: + to_xxx = toupper; +lo_cont: + s1 = s = xstrdup(as[0]); + while (*s1) { + *s1 = (*to_xxx)(*s1); + s1++; + } + setvar_p(res, s); + break; + + case B_ix: + n = 0; + ll = strlen(as[1]); + l = strlen(as[0]) - ll; + if (ll > 0 && l >= 0) { + if (! icase) { + s = strstr(as[0], as[1]); + if (s) n = (s - as[0]) + 1; + } else { + /* this piece of code is terribly slow and + * really should be rewritten + */ + for (i=0; i<=l; i++) { + if (strncasecmp(as[0]+i, as[1], ll) == 0) { + n = i+1; + break; + } + } + } + } + setvar_i(res, n); + break; + + case B_ti: + if (nargs > 1) + tt = getvar_i(av[1]); + else + time(&tt); + s = (nargs > 0) ? as[0] : "%a %b %d %H:%M:%S %Z %Y"; + i = strftime(buf, MAXVARFMT, s, localtime(&tt)); + buf[i] = '\0'; + setvar_s(res, buf); + break; + + case B_ma: + re = as_regex(an[1], &sreg); + n = regexec(re, as[0], 1, pmatch, 0); + if (n == 0) { + pmatch[0].rm_so++; + pmatch[0].rm_eo++; + } else { + pmatch[0].rm_so = 0; + pmatch[0].rm_eo = -1; + } + setvar_i(newvar("RSTART"), pmatch[0].rm_so); + setvar_i(newvar("RLENGTH"), pmatch[0].rm_eo - pmatch[0].rm_so); + setvar_i(res, pmatch[0].rm_so); + if (re == &sreg) regfree(re); + break; + + case B_ge: + awk_sub(an[0], as[1], getvar_i(av[2]), av[3], res, TRUE); + break; + + case B_gs: + setvar_i(res, awk_sub(an[0], as[1], 0, av[2], av[2], FALSE)); + break; + + case B_su: + setvar_i(res, awk_sub(an[0], as[1], 1, av[2], av[2], FALSE)); + break; + } + + nvfree(tv); + return res; +} + +/* + * Evaluate node - the heart of the program. Supplied with subtree + * and place where to store result. returns ptr to result. + */ +#define XC(n) ((n) >> 8) + +static var *evaluate(node *op, var *res) +{ + /* This procedure is recursive so we should count every byte */ + static var *fnargs = NULL; + static unsigned int seed = 1; + static regex_t sreg; + node *op1; + var *v1; + union { + var *v; + char *s; + double d; + int i; + } L, R; + uint32_t opinfo; + short opn; + union { + char *s; + rstream *rsm; + FILE *F; + var *v; + regex_t *re; + uint32_t info; + } X; + + if (! op) + return setvar_s(res, NULL); + + v1 = nvalloc(2); + + while (op) { + + opinfo = op->info; + opn = (short)(opinfo & OPNMASK); + lineno = op->lineno; + + /* execute inevitable things */ + op1 = op->l.n; + if (opinfo & OF_RES1) X.v = L.v = evaluate(op1, v1); + if (opinfo & OF_RES2) R.v = evaluate(op->r.n, v1+1); + if (opinfo & OF_STR1) L.s = getvar_s(L.v); + if (opinfo & OF_STR2) R.s = getvar_s(R.v); + if (opinfo & OF_NUM1) L.d = getvar_i(L.v); + + switch (XC(opinfo & OPCLSMASK)) { + + /* -- iterative node type -- */ + + /* test pattern */ + case XC( OC_TEST ): + if ((op1->info & OPCLSMASK) == OC_COMMA) { + /* it's range pattern */ + if ((opinfo & OF_CHECKED) || ptest(op1->l.n)) { + op->info |= OF_CHECKED; + if (ptest(op1->r.n)) + op->info &= ~OF_CHECKED; + + op = op->a.n; + } else { + op = op->r.n; + } + } else { + op = (ptest(op1)) ? op->a.n : op->r.n; + } + break; + + /* just evaluate an expression, also used as unconditional jump */ + case XC( OC_EXEC ): + break; + + /* branch, used in if-else and various loops */ + case XC( OC_BR ): + op = istrue(L.v) ? op->a.n : op->r.n; + break; + + /* initialize for-in loop */ + case XC( OC_WALKINIT ): + hashwalk_init(L.v, iamarray(R.v)); + break; + + /* get next array item */ + case XC( OC_WALKNEXT ): + op = hashwalk_next(L.v) ? op->a.n : op->r.n; + break; + + case XC( OC_PRINT ): + case XC( OC_PRINTF ): + X.F = stdout; + if (op->r.n) { + X.rsm = newfile(R.s); + if (! X.rsm->F) { + if (opn == '|') { + if((X.rsm->F = popen(R.s, "w")) == NULL) + bb_perror_msg_and_die("popen"); + X.rsm->is_pipe = 1; + } else { + X.rsm->F = xfopen(R.s, opn=='w' ? "w" : "a"); + } + } + X.F = X.rsm->F; + } + + if ((opinfo & OPCLSMASK) == OC_PRINT) { + if (! op1) { + fputs(getvar_s(V[F0]), X.F); + } else { + while (op1) { + L.v = evaluate(nextarg(&op1), v1); + if (L.v->type & VF_NUMBER) { + fmt_num(buf, MAXVARFMT, getvar_s(V[OFMT]), + getvar_i(L.v), TRUE); + fputs(buf, X.F); + } else { + fputs(getvar_s(L.v), X.F); + } + + if (op1) fputs(getvar_s(V[OFS]), X.F); + } + } + fputs(getvar_s(V[ORS]), X.F); + + } else { /* OC_PRINTF */ + L.s = awk_printf(op1); + fputs(L.s, X.F); + free(L.s); + } + fflush(X.F); + break; + + case XC( OC_DELETE ): + X.info = op1->info & OPCLSMASK; + if (X.info == OC_VAR) { + R.v = op1->l.v; + } else if (X.info == OC_FNARG) { + R.v = &fnargs[op1->l.i]; + } else { + runtime_error(EMSG_NOT_ARRAY); + } + + if (op1->r.n) { + clrvar(L.v); + L.s = getvar_s(evaluate(op1->r.n, v1)); + hash_remove(iamarray(R.v), L.s); + } else { + clear_array(iamarray(R.v)); + } + break; + + case XC( OC_NEWSOURCE ): + programname = op->l.s; + break; + + case XC( OC_RETURN ): + copyvar(res, L.v); + break; + + case XC( OC_NEXTFILE ): + nextfile = TRUE; + case XC( OC_NEXT ): + nextrec = TRUE; + case XC( OC_DONE ): + clrvar(res); + break; + + case XC( OC_EXIT ): + awk_exit(L.d); + + /* -- recursive node type -- */ + + case XC( OC_VAR ): + L.v = op->l.v; + if (L.v == V[NF]) + split_f0(); + goto v_cont; + + case XC( OC_FNARG ): + L.v = &fnargs[op->l.i]; + +v_cont: + res = (op->r.n) ? findvar(iamarray(L.v), R.s) : L.v; + break; + + case XC( OC_IN ): + setvar_i(res, hash_search(iamarray(R.v), L.s) ? 1 : 0); + break; + + case XC( OC_REGEXP ): + op1 = op; + L.s = getvar_s(V[F0]); + goto re_cont; + + case XC( OC_MATCH ): + op1 = op->r.n; +re_cont: + X.re = as_regex(op1, &sreg); + R.i = regexec(X.re, L.s, 0, NULL, 0); + if (X.re == &sreg) regfree(X.re); + setvar_i(res, (R.i == 0 ? 1 : 0) ^ (opn == '!' ? 1 : 0)); + break; + + case XC( OC_MOVE ): + /* if source is a temporary string, jusk relink it to dest */ + if (R.v == v1+1 && R.v->string) { + res = setvar_p(L.v, R.v->string); + R.v->string = NULL; + } else { + res = copyvar(L.v, R.v); + } + break; + + case XC( OC_TERNARY ): + if ((op->r.n->info & OPCLSMASK) != OC_COLON) + runtime_error(EMSG_POSSIBLE_ERROR); + res = evaluate(istrue(L.v) ? op->r.n->l.n : op->r.n->r.n, res); + break; + + case XC( OC_FUNC ): + if (! op->r.f->body.first) + runtime_error(EMSG_UNDEF_FUNC); + + X.v = R.v = nvalloc(op->r.f->nargs+1); + while (op1) { + L.v = evaluate(nextarg(&op1), v1); + copyvar(R.v, L.v); + R.v->type |= VF_CHILD; + R.v->x.parent = L.v; + if (++R.v - X.v >= op->r.f->nargs) + break; + } + + R.v = fnargs; + fnargs = X.v; + + L.s = programname; + res = evaluate(op->r.f->body.first, res); + programname = L.s; + + nvfree(fnargs); + fnargs = R.v; + break; + + case XC( OC_GETLINE ): + case XC( OC_PGETLINE ): + if (op1) { + X.rsm = newfile(L.s); + if (! X.rsm->F) { + if ((opinfo & OPCLSMASK) == OC_PGETLINE) { + X.rsm->F = popen(L.s, "r"); + X.rsm->is_pipe = TRUE; + } else { + X.rsm->F = fopen(L.s, "r"); /* not xfopen! */ + } + } + } else { + if (! iF) iF = next_input_file(); + X.rsm = iF; + } + + if (! X.rsm->F) { + setvar_i(V[ERRNO], errno); + setvar_i(res, -1); + break; + } + + if (! op->r.n) + R.v = V[F0]; + + L.i = awk_getline(X.rsm, R.v); + if (L.i > 0) { + if (! op1) { + incvar(V[FNR]); + incvar(V[NR]); + } + } + setvar_i(res, L.i); + break; + + /* simple builtins */ + case XC( OC_FBLTIN ): + switch (opn) { + + case F_in: + R.d = (int)L.d; + break; + + case F_rn: + R.d = (double)rand() / (double)RAND_MAX; + break; + +#ifdef CONFIG_FEATURE_AWK_MATH + case F_co: + R.d = cos(L.d); + break; + + case F_ex: + R.d = exp(L.d); + break; + + case F_lg: + R.d = log(L.d); + break; + + case F_si: + R.d = sin(L.d); + break; + + case F_sq: + R.d = sqrt(L.d); + break; +#else + case F_co: + case F_ex: + case F_lg: + case F_si: + case F_sq: + runtime_error(EMSG_NO_MATH); + break; +#endif + + case F_sr: + R.d = (double)seed; + seed = op1 ? (unsigned int)L.d : (unsigned int)time(NULL); + srand(seed); + break; + + case F_ti: + R.d = time(NULL); + break; + + case F_le: + if (! op1) + L.s = getvar_s(V[F0]); + R.d = strlen(L.s); + break; + + case F_sy: + fflush(NULL); + R.d = (L.s && *L.s) ? (system(L.s) >> 8) : 0; + break; + + case F_ff: + if (! op1) + fflush(stdout); + else { + if (L.s && *L.s) { + X.rsm = newfile(L.s); + fflush(X.rsm->F); + } else { + fflush(NULL); + } + } + break; + + case F_cl: + X.rsm = (rstream *)hash_search(fdhash, L.s); + if (X.rsm) { + R.i = X.rsm->is_pipe ? pclose(X.rsm->F) : fclose(X.rsm->F); + free(X.rsm->buffer); + hash_remove(fdhash, L.s); + } + if (R.i != 0) + setvar_i(V[ERRNO], errno); + R.d = (double)R.i; + break; + } + setvar_i(res, R.d); + break; + + case XC( OC_BUILTIN ): + res = exec_builtin(op, res); + break; + + case XC( OC_SPRINTF ): + setvar_p(res, awk_printf(op1)); + break; + + case XC( OC_UNARY ): + X.v = R.v; + L.d = R.d = getvar_i(R.v); + switch (opn) { + case 'P': + L.d = ++R.d; + goto r_op_change; + case 'p': + R.d++; + goto r_op_change; + case 'M': + L.d = --R.d; + goto r_op_change; + case 'm': + R.d--; + goto r_op_change; + case '!': + L.d = istrue(X.v) ? 0 : 1; + break; + case '-': + L.d = -R.d; + break; + r_op_change: + setvar_i(X.v, R.d); + } + setvar_i(res, L.d); + break; + + case XC( OC_FIELD ): + R.i = (int)getvar_i(R.v); + if (R.i == 0) { + res = V[F0]; + } else { + split_f0(); + if (R.i > nfields) + fsrealloc(R.i); + + res = &Fields[R.i-1]; + } + break; + + /* concatenation (" ") and index joining (",") */ + case XC( OC_CONCAT ): + case XC( OC_COMMA ): + opn = strlen(L.s) + strlen(R.s) + 2; + X.s = (char *)xmalloc(opn); + strcpy(X.s, L.s); + if ((opinfo & OPCLSMASK) == OC_COMMA) { + L.s = getvar_s(V[SUBSEP]); + X.s = (char *)xrealloc(X.s, opn + strlen(L.s)); + strcat(X.s, L.s); + } + strcat(X.s, R.s); + setvar_p(res, X.s); + break; + + case XC( OC_LAND ): + setvar_i(res, istrue(L.v) ? ptest(op->r.n) : 0); + break; + + case XC( OC_LOR ): + setvar_i(res, istrue(L.v) ? 1 : ptest(op->r.n)); + break; + + case XC( OC_BINARY ): + case XC( OC_REPLACE ): + R.d = getvar_i(R.v); + switch (opn) { + case '+': + L.d += R.d; + break; + case '-': + L.d -= R.d; + break; + case '*': + L.d *= R.d; + break; + case '/': + if (R.d == 0) runtime_error(EMSG_DIV_BY_ZERO); + L.d /= R.d; + break; + case '&': +#ifdef CONFIG_FEATURE_AWK_MATH + L.d = pow(L.d, R.d); +#else + runtime_error(EMSG_NO_MATH); +#endif + break; + case '%': + if (R.d == 0) runtime_error(EMSG_DIV_BY_ZERO); + L.d -= (int)(L.d / R.d) * R.d; + break; + } + res = setvar_i(((opinfo&OPCLSMASK) == OC_BINARY) ? res : X.v, L.d); + break; + + case XC( OC_COMPARE ): + if (is_numeric(L.v) && is_numeric(R.v)) { + L.d = getvar_i(L.v) - getvar_i(R.v); + } else { + L.s = getvar_s(L.v); + R.s = getvar_s(R.v); + L.d = icase ? strcasecmp(L.s, R.s) : strcmp(L.s, R.s); + } + switch (opn & 0xfe) { + case 0: + R.i = (L.d > 0); + break; + case 2: + R.i = (L.d >= 0); + break; + case 4: + R.i = (L.d == 0); + break; + } + setvar_i(res, (opn & 0x1 ? R.i : !R.i) ? 1 : 0); + break; + + default: + runtime_error(EMSG_POSSIBLE_ERROR); + } + if ((opinfo & OPCLSMASK) <= SHIFT_TIL_THIS) + op = op->a.n; + if ((opinfo & OPCLSMASK) >= RECUR_FROM_THIS) + break; + if (nextrec) + break; + } + nvfree(v1); + return res; +} + + +/* -------- main & co. -------- */ + +static int awk_exit(int r) +{ + unsigned int i; + hash_item *hi; + static var tv; + + if (! exiting) { + exiting = TRUE; + nextrec = FALSE; + evaluate(endseq.first, &tv); + } + + /* waiting for children */ + for (i=0; icsize; i++) { + hi = fdhash->items[i]; + while(hi) { + if (hi->data.rs.F && hi->data.rs.is_pipe) + pclose(hi->data.rs.F); + hi = hi->next; + } + } + + exit(r); +} + +/* if expr looks like "var=value", perform assignment and return 1, + * otherwise return 0 */ +static int is_assignment(const char *expr) +{ + char *exprc, *s, *s0, *s1; + + exprc = xstrdup(expr); + if (!isalnum_(*exprc) || (s = strchr(exprc, '=')) == NULL) { + free(exprc); + return FALSE; + } + + *(s++) = '\0'; + s0 = s1 = s; + while (*s) + *(s1++) = nextchar(&s); + + *s1 = '\0'; + setvar_u(newvar(exprc), s0); + free(exprc); + return TRUE; +} + +/* switch to next input file */ +static rstream *next_input_file(void) +{ + static rstream rsm; + FILE *F = NULL; + char *fname, *ind; + static int files_happen = FALSE; + + if (rsm.F) fclose(rsm.F); + rsm.F = NULL; + rsm.pos = rsm.adv = 0; + + do { + if (getvar_i(V[ARGIND])+1 >= getvar_i(V[ARGC])) { + if (files_happen) + return NULL; + fname = "-"; + F = stdin; + } else { + ind = getvar_s(incvar(V[ARGIND])); + fname = getvar_s(findvar(iamarray(V[ARGV]), ind)); + if (fname && *fname && !is_assignment(fname)) + F = afopen(fname, "r"); + } + } while (!F); + + files_happen = TRUE; + setvar_s(V[FILENAME], fname); + rsm.F = F; + return &rsm; +} + +int awk_main(int argc, char **argv) +{ + unsigned opt; + char *opt_F, *opt_v, *opt_W; + char *s, *s1; + int i, j, c, flen; + var *v; + static var tv; + char **envp; + static int from_file = FALSE; + rstream *rsm; + FILE *F, *stdfiles[3]; + static char * stdnames = "/dev/stdin\0/dev/stdout\0/dev/stderr"; + + /* allocate global buffer */ + buf = xmalloc(MAXVARFMT+1); + + vhash = hash_init(); + ahash = hash_init(); + fdhash = hash_init(); + fnhash = hash_init(); + + /* initialize variables */ + for (i=0; *vNames; i++) { + V[i] = v = newvar(nextword(&vNames)); + if (*vValues != '\377') + setvar_s(v, nextword(&vValues)); + else + setvar_i(v, 0); + + if (*vNames == '*') { + v->type |= VF_SPECIAL; + vNames++; + } + } + + handle_special(V[FS]); + handle_special(V[RS]); + + stdfiles[0] = stdin; + stdfiles[1] = stdout; + stdfiles[2] = stderr; + for (i=0; i<3; i++) { + rsm = newfile(nextword(&stdnames)); + rsm->F = stdfiles[i]; + } + + for (envp=environ; *envp; envp++) { + s = xstrdup(*envp); + s1 = strchr(s, '='); + if (!s1) { + goto keep_going; + } + *(s1++) = '\0'; + setvar_u(findvar(iamarray(V[ENVIRON]), s), s1); +keep_going: + free(s); + } + + opt = getopt32(argc, argv, "F:v:f:W:", &opt_F, &opt_v, &programname, &opt_W); + if (opt & 0x1) setvar_s(V[FS], opt_F); // -F + if (opt & 0x2) if (!is_assignment(opt_v)) bb_show_usage(); // -v + if (opt & 0x4) { // -f + from_file = TRUE; + F = afopen(programname, "r"); + s = NULL; + /* one byte is reserved for some trick in next_token */ + if (fseek(F, 0, SEEK_END) == 0) { + flen = ftell(F); + s = (char *)xmalloc(flen+4); + fseek(F, 0, SEEK_SET); + i = 1 + fread(s+1, 1, flen, F); + } else { + for (i=j=1; j>0; i+=j) { + s = (char *)xrealloc(s, i+4096); + j = fread(s+i, 1, 4094, F); + } + } + s[i] = '\0'; + fclose(F); + parse_program(s+1); + free(s); + } + if (opt & 0x8) // -W + bb_error_msg("warning: unrecognized option '-W %s' ignored", opt_W); + + if (!from_file) { + if (argc == optind) + bb_show_usage(); + programname = "cmd. line"; + parse_program(argv[optind++]); + + } + + /* fill in ARGV array */ + setvar_i(V[ARGC], argc - optind + 1); + setari_u(V[ARGV], 0, "awk"); + for(i=optind; i < argc; i++) + setari_u(V[ARGV], i+1-optind, argv[i]); + + evaluate(beginseq.first, &tv); + if (! mainseq.first && ! endseq.first) + awk_exit(EXIT_SUCCESS); + + /* input file could already be opened in BEGIN block */ + if (! iF) iF = next_input_file(); + + /* passing through input files */ + while (iF) { + + nextfile = FALSE; + setvar_i(V[FNR], 0); + + while ((c = awk_getline(iF, V[F0])) > 0) { + + nextrec = FALSE; + incvar(V[NR]); + incvar(V[FNR]); + evaluate(mainseq.first, &tv); + + if (nextfile) + break; + } + + if (c < 0) + runtime_error(strerror(errno)); + + iF = next_input_file(); + + } + + awk_exit(EXIT_SUCCESS); + + return 0; +} diff --git a/editors/ed.c b/editors/ed.c new file mode 100644 index 000000000..3aca75912 --- /dev/null +++ b/editors/ed.c @@ -0,0 +1,1231 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (c) 2002 by David I. Bell + * Permission is granted to use, distribute, or modify this source, + * provided that this copyright notice remains intact. + * + * The "ed" built-in command (much simplified) + */ + +#include "busybox.h" + +#define USERSIZE 1024 /* max line length typed in by user */ +#define INITBUF_SIZE 1024 /* initial buffer size */ +typedef struct LINE { + struct LINE *next; + struct LINE *prev; + int len; + char data[1]; +} LINE; + +static LINE lines, *curLine; +static int curNum, lastNum, marks[26], dirty; +static char *bufBase, *bufPtr, *fileName, searchString[USERSIZE]; +static int bufUsed, bufSize; + +static void doCommands(void); +static void subCommand(const char *cmd, int num1, int num2); +static int getNum(const char **retcp, int *retHaveNum, int *retNum); +static int setCurNum(int num); +static int initEdit(void); +static void termEdit(void); +static void addLines(int num); +static int insertLine(int num, const char *data, int len); +static int deleteLines(int num1, int num2); +static int printLines(int num1, int num2, int expandFlag); +static int writeLines(const char *file, int num1, int num2); +static int readLines(const char *file, int num); +static int searchLines(const char *str, int num1, int num2); +static LINE *findLine(int num); + +static int findString(const LINE *lp, const char * str, int len, int offset); + +int ed_main(int argc, char **argv) +{ + if (!initEdit()) + return EXIT_FAILURE; + + if (argc > 1) { + fileName = strdup(argv[1]); + + if (fileName == NULL) { + bb_error_msg("no memory"); + termEdit(); + return EXIT_SUCCESS; + } + + if (!readLines(fileName, 1)) { + termEdit(); + return EXIT_SUCCESS; + } + + if (lastNum) + setCurNum(1); + + dirty = FALSE; + } + + doCommands(); + + termEdit(); + return EXIT_SUCCESS; +} + +/* + * Read commands until we are told to stop. + */ +static void doCommands(void) +{ + const char *cp; + char *endbuf, *newname, buf[USERSIZE]; + int len, num1, num2, have1, have2; + + while (TRUE) { + printf(": "); + fflush(stdout); + + if (fgets(buf, sizeof(buf), stdin) == NULL) + return; + + len = strlen(buf); + + if (len == 0) + return; + + endbuf = &buf[len - 1]; + + if (*endbuf != '\n') { + bb_error_msg("command line too long"); + + do { + len = fgetc(stdin); + } while ((len != EOF) && (len != '\n')); + + continue; + } + + while ((endbuf > buf) && isblank(endbuf[-1])) + endbuf--; + + *endbuf = '\0'; + + cp = buf; + + while (isblank(*cp)) + cp++; + + have1 = FALSE; + have2 = FALSE; + + if ((curNum == 0) && (lastNum > 0)) { + curNum = 1; + curLine = lines.next; + } + + if (!getNum(&cp, &have1, &num1)) + continue; + + while (isblank(*cp)) + cp++; + + if (*cp == ',') { + cp++; + + if (!getNum(&cp, &have2, &num2)) + continue; + + if (!have1) + num1 = 1; + + if (!have2) + num2 = lastNum; + + have1 = TRUE; + have2 = TRUE; + } + + if (!have1) + num1 = curNum; + + if (!have2) + num2 = num1; + + switch (*cp++) { + case 'a': + addLines(num1 + 1); + break; + + case 'c': + deleteLines(num1, num2); + addLines(num1); + break; + + case 'd': + deleteLines(num1, num2); + break; + + case 'f': + if (*cp && !isblank(*cp)) { + bb_error_msg("bad file command"); + break; + } + + while (isblank(*cp)) + cp++; + + if (*cp == '\0') { + if (fileName) + printf("\"%s\"\n", fileName); + else + printf("No file name\n"); + break; + } + + newname = strdup(cp); + + if (newname == NULL) { + bb_error_msg("no memory for file name"); + break; + } + + if (fileName) + free(fileName); + + fileName = newname; + break; + + case 'i': + addLines(num1); + break; + + case 'k': + while (isblank(*cp)) + cp++; + + if ((*cp < 'a') || (*cp > 'a') || cp[1]) { + bb_error_msg("bad mark name"); + break; + } + + marks[*cp - 'a'] = num2; + break; + + case 'l': + printLines(num1, num2, TRUE); + break; + + case 'p': + printLines(num1, num2, FALSE); + break; + + case 'q': + while (isblank(*cp)) + cp++; + + if (have1 || *cp) { + bb_error_msg("bad quit command"); + break; + } + + if (!dirty) + return; + + printf("Really quit? "); + fflush(stdout); + + buf[0] = '\0'; + fgets(buf, sizeof(buf), stdin); + cp = buf; + + while (isblank(*cp)) + cp++; + + if ((*cp == 'y') || (*cp == 'Y')) + return; + + break; + + case 'r': + if (*cp && !isblank(*cp)) { + bb_error_msg("bad read command"); + break; + } + + while (isblank(*cp)) + cp++; + + if (*cp == '\0') { + bb_error_msg("no file name"); + break; + } + + if (!have1) + num1 = lastNum; + + if (readLines(cp, num1 + 1)) + break; + + if (fileName == NULL) + fileName = strdup(cp); + + break; + + case 's': + subCommand(cp, num1, num2); + break; + + case 'w': + if (*cp && !isblank(*cp)) { + bb_error_msg("bad write command"); + break; + } + + while (isblank(*cp)) + cp++; + + if (!have1) { + num1 = 1; + num2 = lastNum; + } + + if (*cp == '\0') + cp = fileName; + + if (cp == NULL) { + bb_error_msg("no file name specified"); + break; + } + + writeLines(cp, num1, num2); + break; + + case 'z': + switch (*cp) { + case '-': + printLines(curNum-21, curNum, FALSE); + break; + case '.': + printLines(curNum-11, curNum+10, FALSE); + break; + default: + printLines(curNum, curNum+21, FALSE); + break; + } + break; + + case '.': + if (have1) { + bb_error_msg("no arguments allowed"); + break; + } + + printLines(curNum, curNum, FALSE); + break; + + case '-': + if (setCurNum(curNum - 1)) + printLines(curNum, curNum, FALSE); + + break; + + case '=': + printf("%d\n", num1); + break; + + case '\0': + if (have1) { + printLines(num2, num2, FALSE); + break; + } + + if (setCurNum(curNum + 1)) + printLines(curNum, curNum, FALSE); + + break; + + default: + bb_error_msg("unimplemented command"); + break; + } + } +} + + +/* + * Do the substitute command. + * The current line is set to the last substitution done. + */ +static void subCommand(const char * cmd, int num1, int num2) +{ + char *cp, *oldStr, *newStr, buf[USERSIZE]; + int delim, oldLen, newLen, deltaLen, offset; + LINE *lp, *nlp; + int globalFlag, printFlag, didSub, needPrint; + + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { + bb_error_msg("bad line range for substitute"); + return; + } + + globalFlag = FALSE; + printFlag = FALSE; + didSub = FALSE; + needPrint = FALSE; + + /* + * Copy the command so we can modify it. + */ + strcpy(buf, cmd); + cp = buf; + + if (isblank(*cp) || (*cp == '\0')) { + bb_error_msg("bad delimiter for substitute"); + return; + } + + delim = *cp++; + oldStr = cp; + + cp = strchr(cp, delim); + + if (cp == NULL) { + bb_error_msg("missing 2nd delimiter for substitute"); + return; + } + + *cp++ = '\0'; + + newStr = cp; + cp = strchr(cp, delim); + + if (cp) + *cp++ = '\0'; + else + cp = ""; + + while (*cp) switch (*cp++) { + case 'g': + globalFlag = TRUE; + break; + + case 'p': + printFlag = TRUE; + break; + + default: + bb_error_msg("unknown option for substitute"); + return; + } + + if (*oldStr == '\0') { + if (searchString[0] == '\0') { + bb_error_msg("no previous search string"); + return; + } + + oldStr = searchString; + } + + if (oldStr != searchString) + strcpy(searchString, oldStr); + + lp = findLine(num1); + + if (lp == NULL) + return; + + oldLen = strlen(oldStr); + newLen = strlen(newStr); + deltaLen = newLen - oldLen; + offset = 0; + nlp = NULL; + + while (num1 <= num2) { + offset = findString(lp, oldStr, oldLen, offset); + + if (offset < 0) { + if (needPrint) { + printLines(num1, num1, FALSE); + needPrint = FALSE; + } + + offset = 0; + lp = lp->next; + num1++; + + continue; + } + + needPrint = printFlag; + didSub = TRUE; + dirty = TRUE; + + /* + * If the replacement string is the same size or shorter + * than the old string, then the substitution is easy. + */ + if (deltaLen <= 0) { + memcpy(&lp->data[offset], newStr, newLen); + + if (deltaLen) { + memcpy(&lp->data[offset + newLen], + &lp->data[offset + oldLen], + lp->len - offset - oldLen); + + lp->len += deltaLen; + } + + offset += newLen; + + if (globalFlag) + continue; + + if (needPrint) { + printLines(num1, num1, FALSE); + needPrint = FALSE; + } + + lp = lp->next; + num1++; + + continue; + } + + /* + * The new string is larger, so allocate a new line + * structure and use that. Link it in in place of + * the old line structure. + */ + nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen); + + if (nlp == NULL) { + bb_error_msg("cannot get memory for line"); + return; + } + + nlp->len = lp->len + deltaLen; + + memcpy(nlp->data, lp->data, offset); + + memcpy(&nlp->data[offset], newStr, newLen); + + memcpy(&nlp->data[offset + newLen], + &lp->data[offset + oldLen], + lp->len - offset - oldLen); + + nlp->next = lp->next; + nlp->prev = lp->prev; + nlp->prev->next = nlp; + nlp->next->prev = nlp; + + if (curLine == lp) + curLine = nlp; + + free(lp); + lp = nlp; + + offset += newLen; + + if (globalFlag) + continue; + + if (needPrint) { + printLines(num1, num1, FALSE); + needPrint = FALSE; + } + + lp = lp->next; + num1++; + } + + if (!didSub) + bb_error_msg("no substitutions found for \"%s\"", oldStr); +} + + +/* + * Search a line for the specified string starting at the specified + * offset in the line. Returns the offset of the found string, or -1. + */ +static int findString( const LINE * lp, const char * str, int len, int offset) +{ + int left; + const char *cp, *ncp; + + cp = &lp->data[offset]; + left = lp->len - offset; + + while (left >= len) { + ncp = memchr(cp, *str, left); + + if (ncp == NULL) + return -1; + + left -= (ncp - cp); + + if (left < len) + return -1; + + cp = ncp; + + if (memcmp(cp, str, len) == 0) + return (cp - lp->data); + + cp++; + left--; + } + + return -1; +} + + +/* + * Add lines which are typed in by the user. + * The lines are inserted just before the specified line number. + * The lines are terminated by a line containing a single dot (ugly!), + * or by an end of file. + */ +static void addLines(int num) +{ + int len; + char buf[USERSIZE + 1]; + + while (fgets(buf, sizeof(buf), stdin)) { + if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0')) + return; + + len = strlen(buf); + + if (len == 0) + return; + + if (buf[len - 1] != '\n') { + bb_error_msg("line too long"); + do { + len = fgetc(stdin); + } while ((len != EOF) && (len != '\n')); + return; + } + + if (!insertLine(num++, buf, len)) + return; + } +} + + +/* + * Parse a line number argument if it is present. This is a sum + * or difference of numbers, '.', '$', 'x, or a search string. + * Returns TRUE if successful (whether or not there was a number). + * Returns FALSE if there was a parsing error, with a message output. + * Whether there was a number is returned indirectly, as is the number. + * The character pointer which stopped the scan is also returned. + */ +static int getNum(const char **retcp, int *retHaveNum, int *retNum) +{ + const char *cp; + char *endStr, str[USERSIZE]; + int haveNum, value, num, sign; + + cp = *retcp; + haveNum = FALSE; + value = 0; + sign = 1; + + while (TRUE) { + while (isblank(*cp)) + cp++; + + switch (*cp) { + case '.': + haveNum = TRUE; + num = curNum; + cp++; + break; + + case '$': + haveNum = TRUE; + num = lastNum; + cp++; + break; + + case '\'': + cp++; + + if ((*cp < 'a') || (*cp > 'z')) { + bb_error_msg("bad mark name"); + return FALSE; + } + + haveNum = TRUE; + num = marks[*cp++ - 'a']; + break; + + case '/': + strcpy(str, ++cp); + endStr = strchr(str, '/'); + + if (endStr) { + *endStr++ = '\0'; + cp += (endStr - str); + } + else + cp = ""; + + num = searchLines(str, curNum, lastNum); + + if (num == 0) + return FALSE; + + haveNum = TRUE; + break; + + default: + if (!isdigit(*cp)) { + *retcp = cp; + *retHaveNum = haveNum; + *retNum = value; + return TRUE; + } + + num = 0; + + while (isdigit(*cp)) + num = num * 10 + *cp++ - '0'; + + haveNum = TRUE; + break; + } + + value += num * sign; + + while (isblank(*cp)) + cp++; + + switch (*cp) { + case '-': + sign = -1; + cp++; + break; + + case '+': + sign = 1; + cp++; + break; + + default: + *retcp = cp; + *retHaveNum = haveNum; + *retNum = value; + return TRUE; + } + } +} + + +/* + * Initialize everything for editing. + */ +static int initEdit(void) +{ + int i; + + bufSize = INITBUF_SIZE; + bufBase = malloc(bufSize); + + if (bufBase == NULL) { + bb_error_msg("no memory for buffer"); + return FALSE; + } + + bufPtr = bufBase; + bufUsed = 0; + + lines.next = &lines; + lines.prev = &lines; + + curLine = NULL; + curNum = 0; + lastNum = 0; + dirty = FALSE; + fileName = NULL; + searchString[0] = '\0'; + + for (i = 0; i < 26; i++) + marks[i] = 0; + + return TRUE; +} + + +/* + * Finish editing. + */ +static void termEdit(void) +{ + if (bufBase) + free(bufBase); + + bufBase = NULL; + bufPtr = NULL; + bufSize = 0; + bufUsed = 0; + + if (fileName) + free(fileName); + + fileName = NULL; + + searchString[0] = '\0'; + + if (lastNum) + deleteLines(1, lastNum); + + lastNum = 0; + curNum = 0; + curLine = NULL; +} + + +/* + * Read lines from a file at the specified line number. + * Returns TRUE if the file was successfully read. + */ +static int readLines(const char * file, int num) +{ + int fd, cc; + int len, lineCount, charCount; + char *cp; + + if ((num < 1) || (num > lastNum + 1)) { + bb_error_msg("bad line for read"); + return FALSE; + } + + fd = open(file, 0); + + if (fd < 0) { + perror(file); + return FALSE; + } + + bufPtr = bufBase; + bufUsed = 0; + lineCount = 0; + charCount = 0; + cc = 0; + + printf("\"%s\", ", file); + fflush(stdout); + + do { + cp = memchr(bufPtr, '\n', bufUsed); + + if (cp) { + len = (cp - bufPtr) + 1; + + if (!insertLine(num, bufPtr, len)) { + close(fd); + return FALSE; + } + + bufPtr += len; + bufUsed -= len; + charCount += len; + lineCount++; + num++; + + continue; + } + + if (bufPtr != bufBase) { + memcpy(bufBase, bufPtr, bufUsed); + bufPtr = bufBase + bufUsed; + } + + if (bufUsed >= bufSize) { + len = (bufSize * 3) / 2; + cp = realloc(bufBase, len); + + if (cp == NULL) { + bb_error_msg("no memory for buffer"); + close(fd); + return FALSE; + } + + bufBase = cp; + bufPtr = bufBase + bufUsed; + bufSize = len; + } + + cc = read(fd, bufPtr, bufSize - bufUsed); + bufUsed += cc; + bufPtr = bufBase; + + } while (cc > 0); + + if (cc < 0) { + perror(file); + close(fd); + return FALSE; + } + + if (bufUsed) { + if (!insertLine(num, bufPtr, bufUsed)) { + close(fd); + return -1; + } + + lineCount++; + charCount += bufUsed; + } + + close(fd); + + printf("%d lines%s, %d chars\n", lineCount, + (bufUsed ? " (incomplete)" : ""), charCount); + + return TRUE; +} + + +/* + * Write the specified lines out to the specified file. + * Returns TRUE if successful, or FALSE on an error with a message output. + */ +static int writeLines(const char * file, int num1, int num2) +{ + LINE *lp; + int fd, lineCount, charCount; + + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { + bb_error_msg("bad line range for write"); + return FALSE; + } + + lineCount = 0; + charCount = 0; + + fd = creat(file, 0666); + + if (fd < 0) { + perror(file); + return FALSE; + } + + printf("\"%s\", ", file); + fflush(stdout); + + lp = findLine(num1); + + if (lp == NULL) { + close(fd); + return FALSE; + } + + while (num1++ <= num2) { + if (write(fd, lp->data, lp->len) != lp->len) { + perror(file); + close(fd); + return FALSE; + } + + charCount += lp->len; + lineCount++; + lp = lp->next; + } + + if (close(fd) < 0) { + perror(file); + return FALSE; + } + + printf("%d lines, %d chars\n", lineCount, charCount); + return TRUE; +} + + +/* + * Print lines in a specified range. + * The last line printed becomes the current line. + * If expandFlag is TRUE, then the line is printed specially to + * show magic characters. + */ +static int printLines(int num1, int num2, int expandFlag) +{ + const LINE *lp; + const char *cp; + int ch, count; + + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { + bb_error_msg("bad line range for print"); + return FALSE; + } + + lp = findLine(num1); + + if (lp == NULL) + return FALSE; + + while (num1 <= num2) { + if (!expandFlag) { + write(1, lp->data, lp->len); + setCurNum(num1++); + lp = lp->next; + + continue; + } + + /* + * Show control characters and characters with the + * high bit set specially. + */ + cp = lp->data; + count = lp->len; + + if ((count > 0) && (cp[count - 1] == '\n')) + count--; + + while (count-- > 0) { + ch = *cp++; + + if (ch & 0x80) { + fputs("M-", stdout); + ch &= 0x7f; + } + + if (ch < ' ') { + fputc('^', stdout); + ch += '@'; + } + + if (ch == 0x7f) { + fputc('^', stdout); + ch = '?'; + } + + fputc(ch, stdout); + } + + fputs("$\n", stdout); + + setCurNum(num1++); + lp = lp->next; + } + + return TRUE; +} + + +/* + * Insert a new line with the specified text. + * The line is inserted so as to become the specified line, + * thus pushing any existing and further lines down one. + * The inserted line is also set to become the current line. + * Returns TRUE if successful. + */ +static int insertLine(int num, const char * data, int len) +{ + LINE *newLp, *lp; + + if ((num < 1) || (num > lastNum + 1)) { + bb_error_msg("inserting at bad line number"); + return FALSE; + } + + newLp = malloc(sizeof(LINE) + len - 1); + + if (newLp == NULL) { + bb_error_msg("failed to allocate memory for line"); + return FALSE; + } + + memcpy(newLp->data, data, len); + newLp->len = len; + + if (num > lastNum) + lp = &lines; + else { + lp = findLine(num); + + if (lp == NULL) { + free((char *) newLp); + return FALSE; + } + } + + newLp->next = lp; + newLp->prev = lp->prev; + lp->prev->next = newLp; + lp->prev = newLp; + + lastNum++; + dirty = TRUE; + return setCurNum(num); +} + + +/* + * Delete lines from the given range. + */ +static int deleteLines(int num1, int num2) +{ + LINE *lp, *nlp, *plp; + int count; + + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { + bb_error_msg("bad line numbers for delete"); + return FALSE; + } + + lp = findLine(num1); + + if (lp == NULL) + return FALSE; + + if ((curNum >= num1) && (curNum <= num2)) { + if (num2 < lastNum) + setCurNum(num2 + 1); + else if (num1 > 1) + setCurNum(num1 - 1); + else + curNum = 0; + } + + count = num2 - num1 + 1; + + if (curNum > num2) + curNum -= count; + + lastNum -= count; + + while (count-- > 0) { + nlp = lp->next; + plp = lp->prev; + plp->next = nlp; + nlp->prev = plp; + lp->next = NULL; + lp->prev = NULL; + lp->len = 0; + free(lp); + lp = nlp; + } + + dirty = TRUE; + + return TRUE; +} + + +/* + * Search for a line which contains the specified string. + * If the string is NULL, then the previously searched for string + * is used. The currently searched for string is saved for future use. + * Returns the line number which matches, or 0 if there was no match + * with an error printed. + */ +static int searchLines(const char *str, int num1, int num2) +{ + const LINE *lp; + int len; + + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { + bb_error_msg("bad line numbers for search"); + return 0; + } + + if (*str == '\0') { + if (searchString[0] == '\0') { + bb_error_msg("no previous search string"); + return 0; + } + + str = searchString; + } + + if (str != searchString) + strcpy(searchString, str); + + len = strlen(str); + + lp = findLine(num1); + + if (lp == NULL) + return 0; + + while (num1 <= num2) { + if (findString(lp, str, len, 0) >= 0) + return num1; + + num1++; + lp = lp->next; + } + + bb_error_msg("cannot find string \"%s\"", str); + return 0; +} + + +/* + * Return a pointer to the specified line number. + */ +static LINE *findLine(int num) +{ + LINE *lp; + int lnum; + + if ((num < 1) || (num > lastNum)) { + bb_error_msg("line number %d does not exist", num); + return NULL; + } + + if (curNum <= 0) { + curNum = 1; + curLine = lines.next; + } + + if (num == curNum) + return curLine; + + lp = curLine; + lnum = curNum; + + if (num < (curNum / 2)) { + lp = lines.next; + lnum = 1; + } + else if (num > ((curNum + lastNum) / 2)) { + lp = lines.prev; + lnum = lastNum; + } + + while (lnum < num) { + lp = lp->next; + lnum++; + } + + while (lnum > num) { + lp = lp->prev; + lnum--; + } + return lp; +} + + +/* + * Set the current line number. + * Returns TRUE if successful. + */ +static int setCurNum(int num) +{ + LINE *lp; + + lp = findLine(num); + + if (lp == NULL) + return FALSE; + + curNum = num; + curLine = lp; + return TRUE; +} diff --git a/editors/patch.c b/editors/patch.c new file mode 100644 index 000000000..8e885d06e --- /dev/null +++ b/editors/patch.c @@ -0,0 +1,275 @@ +/* vi: set sw=4 ts=4: */ +/* + * busybox patch applet to handle the unified diff format. + * Copyright (C) 2003 Glenn McGrath + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * This applet is written to work with patches generated by GNU diff, + * where there is equivalent functionality busybox patch shall behave + * as per GNU patch. + * + * There is a SUSv3 specification for patch, however it looks to be + * incomplete, it doesnt even mention unified diff format. + * http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html + * + * Issues + * - Non-interactive + * - Patches must apply cleanly or the hunk will fail. + * - Reject file isnt saved + * - + */ + +#include +#include +#include +#include +#include "busybox.h" + +static unsigned int copy_lines(FILE *src_stream, FILE *dest_stream, const unsigned int lines_count) +{ + unsigned int i = 0; + + while (src_stream && (i < lines_count)) { + char *line; + line = xmalloc_fgets(src_stream); + if (line == NULL) { + break; + } + if (fputs(line, dest_stream) == EOF) { + bb_perror_msg_and_die("error writing to new file"); + } + free(line); + + i++; + } + return i; +} + +/* If patch_level is -1 it will remove all directory names + * char *line must be greater than 4 chars + * returns NULL if the file doesnt exist or error + * returns malloc'ed filename + */ + +static char *extract_filename(char *line, int patch_level) +{ + char *temp, *filename_start_ptr = line + 4; + int i; + + /* Terminate string at end of source filename */ + temp = strchr(filename_start_ptr, '\t'); + if (temp) *temp = 0; + + /* skip over (patch_level) number of leading directories */ + for (i = 0; i < patch_level; i++) { + if(!(temp = strchr(filename_start_ptr, '/'))) break; + filename_start_ptr = temp + 1; + } + + return xstrdup(filename_start_ptr); +} + +static int file_doesnt_exist(const char *filename) +{ + struct stat statbuf; + return stat(filename, &statbuf); +} + +int patch_main(int argc, char **argv) +{ + int patch_level = -1; + char *patch_line; + int ret; + FILE *patch_file = NULL; + + { + char *p, *i; + ret = getopt32(argc, argv, "p:i:", &p, &i); + if (ret & 1) + patch_level = xatol_range(p, -1, USHRT_MAX); + if (ret & 2) { + patch_file = xfopen(i, "r"); + } else { + patch_file = stdin; + } + ret = 0; + } + + patch_line = xmalloc_fgets(patch_file); + while (patch_line) { + FILE *src_stream; + FILE *dst_stream; + char *original_filename; + char *new_filename; + char *backup_filename; + unsigned int src_cur_line = 1; + unsigned int dest_cur_line = 0; + unsigned int dest_beg_line; + unsigned int bad_hunk_count = 0; + unsigned int hunk_count = 0; + char copy_trailing_lines_flag = 0; + + /* Skip everything upto the "---" marker + * No need to parse the lines "Only in ", and "diff " + */ + while (patch_line && strncmp(patch_line, "--- ", 4) != 0) { + free(patch_line); + patch_line = xmalloc_fgets(patch_file); + } + /* FIXME: patch_line NULL check?? */ + + /* Extract the filename used before the patch was generated */ + original_filename = extract_filename(patch_line, patch_level); + free(patch_line); + + patch_line = xmalloc_fgets(patch_file); + /* FIXME: NULL check?? */ + if (strncmp(patch_line, "+++ ", 4) != 0) { + ret = 2; + bb_error_msg("invalid patch"); + continue; + } + new_filename = extract_filename(patch_line, patch_level); + free(patch_line); + + if (file_doesnt_exist(new_filename)) { + char *line_ptr; + /* Create leading directories */ + line_ptr = strrchr(new_filename, '/'); + if (line_ptr) { + *line_ptr = '\0'; + bb_make_directory(new_filename, -1, FILEUTILS_RECUR); + *line_ptr = '/'; + } + dst_stream = xfopen(new_filename, "w+"); + backup_filename = NULL; + } else { + backup_filename = xmalloc(strlen(new_filename) + 6); + strcpy(backup_filename, new_filename); + strcat(backup_filename, ".orig"); + if (rename(new_filename, backup_filename) == -1) { + bb_perror_msg_and_die("cannot create file %s", + backup_filename); + } + dst_stream = xfopen(new_filename, "w"); + } + + if ((backup_filename == NULL) || file_doesnt_exist(original_filename)) { + src_stream = NULL; + } else { + if (strcmp(original_filename, new_filename) == 0) { + src_stream = xfopen(backup_filename, "r"); + } else { + src_stream = xfopen(original_filename, "r"); + } + } + + printf("patching file %s\n", new_filename); + + /* Handle each hunk */ + patch_line = xmalloc_fgets(patch_file); + while (patch_line) { + unsigned int count; + unsigned int src_beg_line; + unsigned int unused; + unsigned int hunk_offset_start = 0; + int hunk_error = 0; + + /* This bit should be improved */ + if ((sscanf(patch_line, "@@ -%d,%d +%d,%d @@", &src_beg_line, &unused, &dest_beg_line, &unused) != 4) && + (sscanf(patch_line, "@@ -%d,%d +%d @@", &src_beg_line, &unused, &dest_beg_line) != 3) && + (sscanf(patch_line, "@@ -%d +%d,%d @@", &src_beg_line, &dest_beg_line, &unused) != 3)) { + /* No more hunks for this file */ + break; + } + free(patch_line); + hunk_count++; + + if (src_beg_line && dest_beg_line) { + /* Copy unmodified lines upto start of hunk */ + /* src_beg_line will be 0 if its a new file */ + count = src_beg_line - src_cur_line; + if (copy_lines(src_stream, dst_stream, count) != count) { + bb_error_msg_and_die("bad src file"); + } + src_cur_line += count; + dest_cur_line += count; + copy_trailing_lines_flag = 1; + } + hunk_offset_start = src_cur_line; + + while ((patch_line = xmalloc_fgets(patch_file)) != NULL) { + if ((*patch_line == '-') || (*patch_line == ' ')) { + char *src_line = NULL; + if (src_stream) { + src_line = xmalloc_fgets(src_stream); + if (!src_line) { + hunk_error++; + break; + } else { + src_cur_line++; + } + if (strcmp(src_line, patch_line + 1) != 0) { + bb_error_msg("hunk #%d FAILED at %d", hunk_count, hunk_offset_start); + hunk_error++; + free(patch_line); + break; + } + free(src_line); + } + if (*patch_line == ' ') { + fputs(patch_line + 1, dst_stream); + dest_cur_line++; + } + } else if (*patch_line == '+') { + fputs(patch_line + 1, dst_stream); + dest_cur_line++; + } else { + break; + } + free(patch_line); + } + if (hunk_error) { + bad_hunk_count++; + } + } + + /* Cleanup last patched file */ + if (copy_trailing_lines_flag) { + copy_lines(src_stream, dst_stream, -1); + } + if (src_stream) { + fclose(src_stream); + } + if (dst_stream) { + fclose(dst_stream); + } + if (bad_hunk_count) { + if (!ret) { + ret = 1; + } + bb_error_msg("%d out of %d hunk FAILED", bad_hunk_count, hunk_count); + } else { + /* It worked, we can remove the backup */ + if (backup_filename) { + unlink(backup_filename); + } + if ((dest_cur_line == 0) || (dest_beg_line == 0)) { + /* The new patched file is empty, remove it */ + if (unlink(new_filename) == -1) { + bb_perror_msg_and_die("cannot remove file %s", new_filename); + } + if (unlink(original_filename) == -1) { + bb_perror_msg_and_die("cannot remove original file %s", new_filename); + } + } + } + } + + /* 0 = SUCCESS + * 1 = Some hunks failed + * 2 = More serious problems + */ + return ret; +} diff --git a/editors/sed.c b/editors/sed.c new file mode 100644 index 000000000..ac3d8d463 --- /dev/null +++ b/editors/sed.c @@ -0,0 +1,1281 @@ +/* vi: set sw=4 ts=4: */ +/* + * sed.c - very minimalist version of sed + * + * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley + * Copyright (C) 1999,2000,2001 by Mark Whitley + * Copyright (C) 2002 Matt Kraai + * Copyright (C) 2003 by Glenn McGrath + * Copyright (C) 2003,2004 by Rob Landley + * + * MAINTAINER: Rob Landley + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +/* Code overview. + + Files are laid out to avoid unnecessary function declarations. So for + example, every function add_cmd calls occurs before add_cmd in this file. + + add_cmd() is called on each line of sed command text (from a file or from + the command line). It calls get_address() and parse_cmd_args(). The + resulting sed_cmd_t structures are appended to a linked list + (bbg.sed_cmd_head/bbg.sed_cmd_tail). + + add_input_file() adds a FILE * to the list of input files. We need to + know all input sources ahead of time to find the last line for the $ match. + + process_files() does actual sedding, reading data lines from each input FILE * + (which could be stdin) and applying the sed command list (sed_cmd_head) to + each of the resulting lines. + + sed_main() is where external code calls into this, with a command line. +*/ + + +/* + Supported features and commands in this version of sed: + + - comments ('#') + - address matching: num|/matchstr/[,num|/matchstr/|$]command + - commands: (p)rint, (d)elete, (s)ubstitue (with g & I flags) + - edit commands: (a)ppend, (i)nsert, (c)hange + - file commands: (r)ead + - backreferences in substitution expressions (\0, \1, \2...\9) + - grouped commands: {cmd1;cmd2} + - transliteration (y/source-chars/dest-chars/) + - pattern space hold space storing / swapping (g, h, x) + - labels / branching (: label, b, t, T) + + (Note: Specifying an address (range) to match is *optional*; commands + default to the whole pattern space if no specific address match was + requested.) + + Todo: + - Create a wrapper around regex to make libc's regex conform with sed + + Reference http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html +*/ + +#include "busybox.h" +#include "xregex.h" + +/* Each sed command turns into one of these structures. */ +typedef struct sed_cmd_s { + /* Ordered by alignment requirements: currently 36 bytes on x86 */ + + /* address storage */ + regex_t *beg_match; /* sed -e '/match/cmd' */ + regex_t *end_match; /* sed -e '/match/,/end_match/cmd' */ + regex_t *sub_match; /* For 's/sub_match/string/' */ + int beg_line; /* 'sed 1p' 0 == apply commands to all lines */ + int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */ + + FILE *file; /* File (sw) command writes to, -1 for none. */ + char *string; /* Data string for (saicytb) commands. */ + + unsigned short which_match; /* (s) Which match to replace (0 for all) */ + + /* Bitfields (gcc won't group them if we don't) */ + unsigned int invert:1; /* the '!' after the address */ + unsigned int in_match:1; /* Next line also included in match? */ + unsigned int sub_p:1; /* (s) print option */ + + int last_char; /* Last line written by (sw) had no '\n' */ + + /* GENERAL FIELDS */ + char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */ + struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */ +} sed_cmd_t; + +static const char *const semicolon_whitespace = "; \n\r\t\v"; + +struct sed_globals { + /* options */ + int be_quiet, regex_type; + FILE *nonstdout; + char *outname, *hold_space; + + /* List of input files */ + int input_file_count, current_input_file; + FILE **input_file_list; + + regmatch_t regmatch[10]; + regex_t *previous_regex_ptr; + + /* linked list of sed commands */ + sed_cmd_t sed_cmd_head, *sed_cmd_tail; + + /* Linked list of append lines */ + llist_t *append_head; + + char *add_cmd_line; + + struct pipeline { + char *buf; /* Space to hold string */ + int idx; /* Space used */ + int len; /* Space allocated */ + } pipeline; +} bbg; + + +void sed_free_and_close_stuff(void); +#if ENABLE_FEATURE_CLEAN_UP +static void sed_free_and_close_stuff(void) +{ + sed_cmd_t *sed_cmd = bbg.sed_cmd_head.next; + + llist_free(bbg.append_head, free); + + while (sed_cmd) { + sed_cmd_t *sed_cmd_next = sed_cmd->next; + + if (sed_cmd->file) + xprint_and_close_file(sed_cmd->file); + + if (sed_cmd->beg_match) { + regfree(sed_cmd->beg_match); + free(sed_cmd->beg_match); + } + if (sed_cmd->end_match) { + regfree(sed_cmd->end_match); + free(sed_cmd->end_match); + } + if (sed_cmd->sub_match) { + regfree(sed_cmd->sub_match); + free(sed_cmd->sub_match); + } + free(sed_cmd->string); + free(sed_cmd); + sed_cmd = sed_cmd_next; + } + + if (bbg.hold_space) free(bbg.hold_space); + + while (bbg.current_input_file < bbg.input_file_count) + fclose(bbg.input_file_list[bbg.current_input_file++]); +} +#endif + +/* If something bad happens during -i operation, delete temp file */ + +static void cleanup_outname(void) +{ + if (bbg.outname) unlink(bbg.outname); +} + +/* strdup, replacing "\n" with '\n', and "\delimiter" with 'delimiter' */ + +static void parse_escapes(char *dest, char *string, int len, char from, char to) +{ + int i = 0; + + while (i < len) { + if (string[i] == '\\') { + if (!to || string[i+1] == from) { + *(dest++) = to ? to : string[i+1]; + i += 2; + continue; + } else *(dest++) = string[i++]; + } + *(dest++) = string[i++]; + } + *dest = 0; +} + +static char *copy_parsing_escapes(char *string, int len) +{ + char *dest = xmalloc(len + 1); + + parse_escapes(dest, string, len, 'n', '\n'); + return dest; +} + + +/* + * index_of_next_unescaped_regexp_delim - walks left to right through a string + * beginning at a specified index and returns the index of the next regular + * expression delimiter (typically a forward * slash ('/')) not preceded by + * a backslash ('\'). A negative delimiter disables square bracket checking. + */ +static int index_of_next_unescaped_regexp_delim(int delimiter, char *str) +{ + int bracket = -1; + int escaped = 0; + int idx = 0; + char ch; + + if (delimiter < 0) { + bracket--; + delimiter = -delimiter; + } + + for (; (ch = str[idx]); idx++) { + if (bracket >= 0) { + if (ch == ']' && !(bracket == idx - 1 || (bracket == idx - 2 + && str[idx - 1] == '^'))) + bracket = -1; + } else if (escaped) + escaped = 0; + else if (ch == '\\') + escaped = 1; + else if (bracket == -1 && ch == '[') + bracket = idx; + else if (ch == delimiter) + return idx; + } + + /* if we make it to here, we've hit the end of the string */ + bb_error_msg_and_die("unmatched '%c'", delimiter); +} + +/* + * Returns the index of the third delimiter + */ +static int parse_regex_delim(char *cmdstr, char **match, char **replace) +{ + char *cmdstr_ptr = cmdstr; + char delimiter; + int idx = 0; + + /* verify that the 's' or 'y' is followed by something. That something + * (typically a 'slash') is now our regexp delimiter... */ + if (*cmdstr == '\0') + bb_error_msg_and_die("bad format in substitution expression"); + delimiter = *cmdstr_ptr++; + + /* save the match string */ + idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr); + *match = copy_parsing_escapes(cmdstr_ptr, idx); + + /* save the replacement string */ + cmdstr_ptr += idx + 1; + idx = index_of_next_unescaped_regexp_delim(-delimiter, cmdstr_ptr); + *replace = copy_parsing_escapes(cmdstr_ptr, idx); + + return ((cmdstr_ptr - cmdstr) + idx); +} + +/* + * returns the index in the string just past where the address ends. + */ +static int get_address(char *my_str, int *linenum, regex_t ** regex) +{ + char *pos = my_str; + + if (isdigit(*my_str)) { + *linenum = strtol(my_str, &pos, 10); + /* endstr shouldnt ever equal NULL */ + } else if (*my_str == '$') { + *linenum = -1; + pos++; + } else if (*my_str == '/' || *my_str == '\\') { + int next; + char delimiter; + char *temp; + + delimiter = '/'; + if (*my_str == '\\') delimiter = *++pos; + next = index_of_next_unescaped_regexp_delim(delimiter, ++pos); + temp = copy_parsing_escapes(pos, next); + *regex = xmalloc(sizeof(regex_t)); + xregcomp(*regex, temp, bbg.regex_type|REG_NEWLINE); + free(temp); + /* Move position to next character after last delimiter */ + pos += (next+1); + } + return pos - my_str; +} + +/* Grab a filename. Whitespace at start is skipped, then goes to EOL. */ +static int parse_file_cmd(sed_cmd_t *sed_cmd, char *filecmdstr, char **retval) +{ + int start = 0, idx, hack = 0; + + /* Skip whitespace, then grab filename to end of line */ + while (isspace(filecmdstr[start])) start++; + idx = start; + while (filecmdstr[idx] && filecmdstr[idx] != '\n') idx++; + + /* If lines glued together, put backslash back. */ + if (filecmdstr[idx] == '\n') hack = 1; + if (idx == start) + bb_error_msg_and_die("empty filename"); + *retval = xstrndup(filecmdstr+start, idx-start+hack+1); + if (hack) (*retval)[idx] = '\\'; + + return idx; +} + +static int parse_subst_cmd(sed_cmd_t *sed_cmd, char *substr) +{ + int cflags = bbg.regex_type; + char *match; + int idx = 0; + + /* + * A substitution command should look something like this: + * s/match/replace/ #gIpw + * || | ||| + * mandatory optional + */ + idx = parse_regex_delim(substr, &match, &sed_cmd->string); + + /* determine the number of back references in the match string */ + /* Note: we compute this here rather than in the do_subst_command() + * function to save processor time, at the expense of a little more memory + * (4 bits) per sed_cmd */ + + /* process the flags */ + + sed_cmd->which_match = 1; + while (substr[++idx]) { + /* Parse match number */ + if (isdigit(substr[idx])) { + if (match[0] != '^') { + /* Match 0 treated as all, multiple matches we take the last one. */ + char *pos = substr + idx; + /* FIXME: error check? */ + sed_cmd->which_match = (unsigned short)strtol(substr+idx, &pos, 10); + idx = pos - substr; + } + continue; + } + /* Skip spaces */ + if (isspace(substr[idx])) continue; + + switch (substr[idx]) { + /* Replace all occurrences */ + case 'g': + if (match[0] != '^') sed_cmd->which_match = 0; + break; + /* Print pattern space */ + case 'p': + sed_cmd->sub_p = 1; + break; + /* Write to file */ + case 'w': + { + char *temp; + idx += parse_file_cmd(sed_cmd, substr+idx, &temp); + + break; + } + /* Ignore case (gnu exension) */ + case 'I': + cflags |= REG_ICASE; + break; + /* Comment */ + case '#': + while (substr[++idx]) /*skip all*/; + /* Fall through */ + /* End of command */ + case ';': + case '}': + goto out; + default: + bb_error_msg_and_die("bad option in substitution expression"); + } + } +out: + /* compile the match string into a regex */ + if (*match != '\0') { + /* If match is empty, we use last regex used at runtime */ + sed_cmd->sub_match = (regex_t *) xmalloc(sizeof(regex_t)); + xregcomp(sed_cmd->sub_match, match, cflags); + } + free(match); + + return idx; +} + +/* + * Process the commands arguments + */ +static char *parse_cmd_args(sed_cmd_t *sed_cmd, char *cmdstr) +{ + /* handle (s)ubstitution command */ + if (sed_cmd->cmd == 's') + cmdstr += parse_subst_cmd(sed_cmd, cmdstr); + /* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */ + else if (strchr("aic", sed_cmd->cmd)) { + if ((sed_cmd->end_line || sed_cmd->end_match) && sed_cmd->cmd != 'c') + bb_error_msg_and_die + ("only a beginning address can be specified for edit commands"); + for (;;) { + if (*cmdstr == '\n' || *cmdstr == '\\') { + cmdstr++; + break; + } else if (isspace(*cmdstr)) + cmdstr++; + else + break; + } + sed_cmd->string = xstrdup(cmdstr); + parse_escapes(sed_cmd->string, sed_cmd->string, strlen(cmdstr), 0, 0); + cmdstr += strlen(cmdstr); + /* handle file cmds: (r)ead */ + } else if (strchr("rw", sed_cmd->cmd)) { + if (sed_cmd->end_line || sed_cmd->end_match) + bb_error_msg_and_die("command only uses one address"); + cmdstr += parse_file_cmd(sed_cmd, cmdstr, &sed_cmd->string); + if (sed_cmd->cmd == 'w') + sed_cmd->file = xfopen(sed_cmd->string, "w"); + /* handle branch commands */ + } else if (strchr(":btT", sed_cmd->cmd)) { + int length; + + cmdstr = skip_whitespace(cmdstr); + length = strcspn(cmdstr, semicolon_whitespace); + if (length) { + sed_cmd->string = xstrndup(cmdstr, length); + cmdstr += length; + } + } + /* translation command */ + else if (sed_cmd->cmd == 'y') { + char *match, *replace; + int i = cmdstr[0]; + + cmdstr += parse_regex_delim(cmdstr, &match, &replace)+1; + /* \n already parsed, but \delimiter needs unescaping. */ + parse_escapes(match, match, strlen(match), i, i); + parse_escapes(replace, replace, strlen(replace), i, i); + + sed_cmd->string = xzalloc((strlen(match) + 1) * 2); + for (i = 0; match[i] && replace[i]; i++) { + sed_cmd->string[i*2] = match[i]; + sed_cmd->string[i*2+1] = replace[i]; + } + free(match); + free(replace); + } + /* if it wasnt a single-letter command that takes no arguments + * then it must be an invalid command. + */ + else if (strchr("dDgGhHlnNpPqx={}", sed_cmd->cmd) == 0) { + bb_error_msg_and_die("unsupported command %c", sed_cmd->cmd); + } + + /* give back whatever's left over */ + return cmdstr; +} + + +/* Parse address+command sets, skipping comment lines. */ + +static void add_cmd(char *cmdstr) +{ + sed_cmd_t *sed_cmd; + int temp; + + /* Append this line to any unfinished line from last time. */ + if (bbg.add_cmd_line) { + cmdstr = xasprintf("%s\n%s", bbg.add_cmd_line, cmdstr); + free(bbg.add_cmd_line); + bbg.add_cmd_line = cmdstr; + } + + /* If this line ends with backslash, request next line. */ + temp = strlen(cmdstr); + if (temp && cmdstr[temp-1] == '\\') { + if (!bbg.add_cmd_line) + bbg.add_cmd_line = xstrdup(cmdstr); + bbg.add_cmd_line[temp-1] = 0; + return; + } + + /* Loop parsing all commands in this line. */ + while (*cmdstr) { + /* Skip leading whitespace and semicolons */ + cmdstr += strspn(cmdstr, semicolon_whitespace); + + /* If no more commands, exit. */ + if (!*cmdstr) break; + + /* if this is a comment, jump past it and keep going */ + if (*cmdstr == '#') { + /* "#n" is the same as using -n on the command line */ + if (cmdstr[1] == 'n') + bbg.be_quiet++; + cmdstr = strpbrk(cmdstr, "\n\r"); + if (!cmdstr) break; + continue; + } + + /* parse the command + * format is: [addr][,addr][!]cmd + * |----||-----||-| + * part1 part2 part3 + */ + + sed_cmd = xzalloc(sizeof(sed_cmd_t)); + + /* first part (if present) is an address: either a '$', a number or a /regex/ */ + cmdstr += get_address(cmdstr, &sed_cmd->beg_line, &sed_cmd->beg_match); + + /* second part (if present) will begin with a comma */ + if (*cmdstr == ',') { + int idx; + + cmdstr++; + idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match); + if (!idx) + bb_error_msg_and_die("no address after comma"); + cmdstr += idx; + } + + /* skip whitespace before the command */ + cmdstr = skip_whitespace(cmdstr); + + /* Check for inversion flag */ + if (*cmdstr == '!') { + sed_cmd->invert = 1; + cmdstr++; + + /* skip whitespace before the command */ + cmdstr = skip_whitespace(cmdstr); + } + + /* last part (mandatory) will be a command */ + if (!*cmdstr) + bb_error_msg_and_die("missing command"); + sed_cmd->cmd = *(cmdstr++); + cmdstr = parse_cmd_args(sed_cmd, cmdstr); + + /* Add the command to the command array */ + bbg.sed_cmd_tail->next = sed_cmd; + bbg.sed_cmd_tail = bbg.sed_cmd_tail->next; + } + + /* If we glued multiple lines together, free the memory. */ + free(bbg.add_cmd_line); + bbg.add_cmd_line = NULL; +} + +/* Append to a string, reallocating memory as necessary. */ + +#define PIPE_GROW 64 + +static void pipe_putc(char c) +{ + if (bbg.pipeline.idx == bbg.pipeline.len) { + bbg.pipeline.buf = xrealloc(bbg.pipeline.buf, + bbg.pipeline.len + PIPE_GROW); + bbg.pipeline.len += PIPE_GROW; + } + bbg.pipeline.buf[bbg.pipeline.idx++] = c; +} + +static void do_subst_w_backrefs(char *line, char *replace) +{ + int i,j; + + /* go through the replacement string */ + for (i = 0; replace[i]; i++) { + /* if we find a backreference (\1, \2, etc.) print the backref'ed * text */ + if (replace[i] == '\\' && replace[i+1] >= '0' && replace[i+1] <= '9') { + int backref = replace[++i]-'0'; + + /* print out the text held in bbg.regmatch[backref] */ + if (bbg.regmatch[backref].rm_so != -1) { + j = bbg.regmatch[backref].rm_so; + while (j < bbg.regmatch[backref].rm_eo) + pipe_putc(line[j++]); + } + } + + /* if we find a backslash escaped character, print the character */ + else if (replace[i] == '\\') pipe_putc(replace[++i]); + + /* if we find an unescaped '&' print out the whole matched text. */ + else if (replace[i] == '&') { + j = bbg.regmatch[0].rm_so; + while (j < bbg.regmatch[0].rm_eo) + pipe_putc(line[j++]); + } + /* Otherwise just output the character. */ + else pipe_putc(replace[i]); + } +} + +static int do_subst_command(sed_cmd_t *sed_cmd, char **line) +{ + char *oldline = *line; + int altered = 0; + int match_count = 0; + regex_t *current_regex; + + /* Handle empty regex. */ + if (sed_cmd->sub_match == NULL) { + current_regex = bbg.previous_regex_ptr; + if (!current_regex) + bb_error_msg_and_die("no previous regexp"); + } else + bbg.previous_regex_ptr = current_regex = sed_cmd->sub_match; + + /* Find the first match */ + if (REG_NOMATCH == regexec(current_regex, oldline, 10, bbg.regmatch, 0)) + return 0; + + /* Initialize temporary output buffer. */ + bbg.pipeline.buf = xmalloc(PIPE_GROW); + bbg.pipeline.len = PIPE_GROW; + bbg.pipeline.idx = 0; + + /* Now loop through, substituting for matches */ + do { + int i; + + /* Work around bug in glibc regexec, demonstrated by: + echo " a.b" | busybox sed 's [^ .]* x g' + The match_count check is so not to break + echo "hi" | busybox sed 's/^/!/g' */ + if (!bbg.regmatch[0].rm_so && !bbg.regmatch[0].rm_eo && match_count) { + pipe_putc(*oldline++); + continue; + } + + match_count++; + + /* If we aren't interested in this match, output old line to + end of match and continue */ + if (sed_cmd->which_match && sed_cmd->which_match != match_count) { + for (i = 0; i < bbg.regmatch[0].rm_eo; i++) + pipe_putc(*oldline++); + continue; + } + + /* print everything before the match */ + for (i = 0; i < bbg.regmatch[0].rm_so; i++) + pipe_putc(oldline[i]); + + /* then print the substitution string */ + do_subst_w_backrefs(oldline, sed_cmd->string); + + /* advance past the match */ + oldline += bbg.regmatch[0].rm_eo; + /* flag that something has changed */ + altered++; + + /* if we're not doing this globally, get out now */ + if (sed_cmd->which_match) break; + } while (*oldline && (regexec(current_regex, oldline, 10, bbg.regmatch, 0) != REG_NOMATCH)); + + /* Copy rest of string into output pipeline */ + + while (*oldline) + pipe_putc(*oldline++); + pipe_putc(0); + + free(*line); + *line = bbg.pipeline.buf; + return altered; +} + +/* Set command pointer to point to this label. (Does not handle null label.) */ +static sed_cmd_t *branch_to(char *label) +{ + sed_cmd_t *sed_cmd; + + for (sed_cmd = bbg.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { + if (sed_cmd->cmd == ':' && sed_cmd->string && !strcmp(sed_cmd->string, label)) { + return sed_cmd; + } + } + bb_error_msg_and_die("can't find label for jump to '%s'", label); +} + +static void append(char *s) +{ + llist_add_to_end(&bbg.append_head, xstrdup(s)); +} + +static void flush_append(void) +{ + char *data; + + /* Output appended lines. */ + while ((data = (char *)llist_pop(&bbg.append_head))) { + fprintf(bbg.nonstdout, "%s\n", data); + free(data); + } +} + +static void add_input_file(FILE *file) +{ + bbg.input_file_list = xrealloc(bbg.input_file_list, + (bbg.input_file_count + 1) * sizeof(FILE *)); + bbg.input_file_list[bbg.input_file_count++] = file; +} + +/* Get next line of input from bbg.input_file_list, flushing append buffer and + * noting if we ran out of files without a newline on the last line we read. + */ +static char *get_next_line(int *last_char) +{ + char *temp = NULL; + int len, lc; + + lc = 0; + flush_append(); + while (bbg.current_input_file < bbg.input_file_count) { + temp = bb_get_chunk_from_file( + bbg.input_file_list[bbg.current_input_file], &len); + if (temp) { + /* len > 0 here, it's ok to do temp[len-1] */ + char c = temp[len-1]; + if (c == '\n' || c == '\0') { + temp[len-1] = '\0'; + lc |= (unsigned char)c; + break; + } + /* will be returned if last line in the file + * doesn't end with either '\n' or '\0' */ + lc |= 0x100; + break; + } + /* Close this file and advance to next one */ + fclose(bbg.input_file_list[bbg.current_input_file++]); + /* "this is the first line from new input file" */ + lc |= 0x200; + } + *last_char = lc; + return temp; +} + +/* Output line of text. */ +/* Note: + * The tricks with 0x200 and last_puts_char are there to emulate gnu sed. + * Without them, we had this: + * echo -n thingy >z1 + * echo -n again >z2 + * >znull + * sed "s/i/z/" z1 z2 znull | hexdump -vC output: + * gnu sed 4.1.5: + * 00000000 74 68 7a 6e 67 79 0a 61 67 61 7a 6e |thzngy.agazn| + * bbox: + * 00000000 74 68 7a 6e 67 79 61 67 61 7a 6e |thzngyagazn| + */ + +static int puts_maybe_newline(char *s, FILE *file, int prev_last_char, int last_char) +{ + static char last_puts_char; + + /* Is this a first line from new file + * and old file didn't end with '\n'? */ + if ((last_char & 0x200) && last_puts_char != '\n') { + fputc('\n', file); + last_puts_char = '\n'; + } + fputs(s, file); + /* 'x': we don't care what is it, but we know it isn't '\n' */ + if (s[0]) last_puts_char = 'x'; + if (!(last_char & 0x100)) { /* had trailing '\n' or '\0'? */ + last_char &= 0xff; + fputc(last_char, file); + last_puts_char = last_char; + } + + if (ferror(file)) { + xfunc_error_retval = 4; /* It's what gnu sed exits with... */ + bb_error_msg_and_die(bb_msg_write_error); + } + + return last_char; +} + +#define sed_puts(s, n) \ + (prev_last_char = puts_maybe_newline(s, bbg.nonstdout, prev_last_char, n)) + +/* Process all the lines in all the files */ + +static void process_files(void) +{ + char *pattern_space, *next_line; + int linenum = 0, prev_last_char = 0; + int last_char, next_last_char = 0; + sed_cmd_t *sed_cmd; + int substituted; + + /* Prime the pump */ + next_line = get_next_line(&next_last_char); + + /* go through every line in each file */ +again: + substituted = 0; + + /* Advance to next line. Stop if out of lines. */ + pattern_space = next_line; + if (!pattern_space) return; + last_char = next_last_char; + + /* Read one line in advance so we can act on the last line, + * the '$' address */ + next_line = get_next_line(&next_last_char); + linenum++; +restart: + /* for every line, go through all the commands */ + for (sed_cmd = bbg.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { + int old_matched, matched; + + old_matched = sed_cmd->in_match; + + /* Determine if this command matches this line: */ + + /* Are we continuing a previous multi-line match? */ + sed_cmd->in_match = sed_cmd->in_match + /* Or is no range necessary? */ + || (!sed_cmd->beg_line && !sed_cmd->end_line + && !sed_cmd->beg_match && !sed_cmd->end_match) + /* Or did we match the start of a numerical range? */ + || (sed_cmd->beg_line > 0 && (sed_cmd->beg_line == linenum)) + /* Or does this line match our begin address regex? */ + || (sed_cmd->beg_match && + !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0)) + /* Or did we match last line of input? */ + || (sed_cmd->beg_line == -1 && next_line == NULL); + + /* Snapshot the value */ + + matched = sed_cmd->in_match; + + /* Is this line the end of the current match? */ + + if (matched) { + sed_cmd->in_match = !( + /* has the ending line come, or is this a single address command? */ + (sed_cmd->end_line ? + sed_cmd->end_line == -1 ? + !next_line + : (sed_cmd->end_line <= linenum) + : !sed_cmd->end_match + ) + /* or does this line matches our last address regex */ + || (sed_cmd->end_match && old_matched + && (regexec(sed_cmd->end_match, + pattern_space, 0, NULL, 0) == 0)) + ); + } + + /* Skip blocks of commands we didn't match. */ + if (sed_cmd->cmd == '{') { + if (sed_cmd->invert ? matched : !matched) + while (sed_cmd && sed_cmd->cmd != '}') + sed_cmd = sed_cmd->next; + if (!sed_cmd) bb_error_msg_and_die("unterminated {"); + continue; + } + + /* Okay, so did this line match? */ + if (sed_cmd->invert ? !matched : matched) { + /* Update last used regex in case a blank substitute BRE is found */ + if (sed_cmd->beg_match) { + bbg.previous_regex_ptr = sed_cmd->beg_match; + } + + /* actual sedding */ + switch (sed_cmd->cmd) { + + /* Print line number */ + case '=': + fprintf(bbg.nonstdout, "%d\n", linenum); + break; + + /* Write the current pattern space up to the first newline */ + case 'P': + { + char *tmp = strchr(pattern_space, '\n'); + + if (tmp) { + *tmp = '\0'; + sed_puts(pattern_space, 1); + *tmp = '\n'; + break; + } + /* Fall Through */ + } + + /* Write the current pattern space to output */ + case 'p': + sed_puts(pattern_space, last_char); + break; + /* Delete up through first newline */ + case 'D': + { + char *tmp = strchr(pattern_space, '\n'); + + if (tmp) { + tmp = xstrdup(tmp+1); + free(pattern_space); + pattern_space = tmp; + goto restart; + } + } + /* discard this line. */ + case 'd': + goto discard_line; + + /* Substitute with regex */ + case 's': + if (!do_subst_command(sed_cmd, &pattern_space)) + break; + substituted |= 1; + + /* handle p option */ + if (sed_cmd->sub_p) + sed_puts(pattern_space, last_char); + /* handle w option */ + if (sed_cmd->file) + sed_cmd->last_char = puts_maybe_newline( + pattern_space, sed_cmd->file, + sed_cmd->last_char, last_char); + break; + + /* Append line to linked list to be printed later */ + case 'a': + append(sed_cmd->string); + break; + + /* Insert text before this line */ + case 'i': + sed_puts(sed_cmd->string, 1); + break; + + /* Cut and paste text (replace) */ + case 'c': + /* Only triggers on last line of a matching range. */ + if (!sed_cmd->in_match) + sed_puts(sed_cmd->string, 0); + goto discard_line; + + /* Read file, append contents to output */ + case 'r': + { + FILE *rfile; + + rfile = fopen(sed_cmd->string, "r"); + if (rfile) { + char *line; + + while ((line = xmalloc_getline(rfile)) + != NULL) + append(line); + xprint_and_close_file(rfile); + } + + break; + } + + /* Write pattern space to file. */ + case 'w': + sed_cmd->last_char = puts_maybe_newline( + pattern_space, sed_cmd->file, + sed_cmd->last_char, last_char); + break; + + /* Read next line from input */ + case 'n': + if (!bbg.be_quiet) + sed_puts(pattern_space, last_char); + if (next_line) { + free(pattern_space); + pattern_space = next_line; + last_char = next_last_char; + next_line = get_next_line(&next_last_char); + linenum++; + break; + } + /* fall through */ + + /* Quit. End of script, end of input. */ + case 'q': + /* Exit the outer while loop */ + free(next_line); + next_line = NULL; + goto discard_commands; + + /* Append the next line to the current line */ + case 'N': + { + int len; + /* If no next line, jump to end of script and exit. */ + if (next_line == NULL) { + /* Jump to end of script and exit */ + free(next_line); + next_line = NULL; + goto discard_line; + /* append next_line, read new next_line. */ + } + len = strlen(pattern_space); + pattern_space = realloc(pattern_space, len + strlen(next_line) + 2); + pattern_space[len] = '\n'; + strcpy(pattern_space + len+1, next_line); + last_char = next_last_char; + next_line = get_next_line(&next_last_char); + linenum++; + break; + } + + /* Test/branch if substitution occurred */ + case 't': + if (!substituted) break; + substituted = 0; + /* Fall through */ + /* Test/branch if substitution didn't occur */ + case 'T': + if (substituted) break; + /* Fall through */ + /* Branch to label */ + case 'b': + if (!sed_cmd->string) goto discard_commands; + else sed_cmd = branch_to(sed_cmd->string); + break; + /* Transliterate characters */ + case 'y': + { + int i, j; + + for (i = 0; pattern_space[i]; i++) { + for (j = 0; sed_cmd->string[j]; j += 2) { + if (pattern_space[i] == sed_cmd->string[j]) { + pattern_space[i] = sed_cmd->string[j + 1]; + break; + } + } + } + + break; + } + case 'g': /* Replace pattern space with hold space */ + free(pattern_space); + pattern_space = xstrdup(bbg.hold_space ? bbg.hold_space : ""); + break; + case 'G': /* Append newline and hold space to pattern space */ + { + int pattern_space_size = 2; + int hold_space_size = 0; + + if (pattern_space) + pattern_space_size += strlen(pattern_space); + if (bbg.hold_space) + hold_space_size = strlen(bbg.hold_space); + pattern_space = xrealloc(pattern_space, + pattern_space_size + hold_space_size); + if (pattern_space_size == 2) + pattern_space[0] = 0; + strcat(pattern_space, "\n"); + if (bbg.hold_space) + strcat(pattern_space, bbg.hold_space); + last_char = '\n'; + + break; + } + case 'h': /* Replace hold space with pattern space */ + free(bbg.hold_space); + bbg.hold_space = xstrdup(pattern_space); + break; + case 'H': /* Append newline and pattern space to hold space */ + { + int hold_space_size = 2; + int pattern_space_size = 0; + + if (bbg.hold_space) + hold_space_size += strlen(bbg.hold_space); + if (pattern_space) + pattern_space_size = strlen(pattern_space); + bbg.hold_space = xrealloc(bbg.hold_space, + hold_space_size + pattern_space_size); + + if (hold_space_size == 2) + *bbg.hold_space = 0; + strcat(bbg.hold_space, "\n"); + if (pattern_space) + strcat(bbg.hold_space, pattern_space); + + break; + } + case 'x': /* Exchange hold and pattern space */ + { + char *tmp = pattern_space; + pattern_space = bbg.hold_space ? : xzalloc(1); + last_char = '\n'; + bbg.hold_space = tmp; + break; + } + } + } + } + + /* + * exit point from sedding... + */ +discard_commands: + /* we will print the line unless we were told to be quiet ('-n') + or if the line was suppressed (ala 'd'elete) */ + if (!bbg.be_quiet) sed_puts(pattern_space, last_char); + + /* Delete and such jump here. */ +discard_line: + flush_append(); + free(pattern_space); + + goto again; +} + +/* It is possible to have a command line argument with embedded + newlines. This counts as multiple command lines. */ + +static void add_cmd_block(char *cmdstr) +{ + int go = 1; + char *temp = xstrdup(cmdstr), *temp2 = temp; + + while (go) { + int len = strcspn(temp2, "\n"); + if (!temp2[len]) go = 0; + else temp2[len] = 0; + add_cmd(temp2); + temp2 += len+1; + } + free(temp); +} + +static void add_cmds_link(llist_t *opt_e) +{ + if (!opt_e) return; + add_cmds_link(opt_e->link); + add_cmd_block(opt_e->data); + free(opt_e); +} + +static void add_files_link(llist_t *opt_f) +{ + char *line; + FILE *cmdfile; + if (!opt_f) return; + add_files_link(opt_f->link); + cmdfile = xfopen(opt_f->data, "r"); + while ((line = xmalloc_getline(cmdfile)) != NULL) { + add_cmd(line); + free(line); + } + xprint_and_close_file(cmdfile); + free(opt_f); +} + +int sed_main(int argc, char **argv) +{ + enum { + OPT_in_place = 1 << 0, + }; + unsigned opt; + llist_t *opt_e, *opt_f; + int status = EXIT_SUCCESS; + + bbg.sed_cmd_tail = &bbg.sed_cmd_head; + + /* destroy command strings on exit */ + if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff); + + /* Lie to autoconf when it starts asking stupid questions. */ + if (argc == 2 && !strcmp(argv[1], "--version")) { + puts("This is not GNU sed version 4.0"); + return 0; + } + + /* do normal option parsing */ + opt_e = opt_f = NULL; + opt_complementary = "e::f::" /* can occur multiple times */ + "nn"; /* count -n */ + opt = getopt32(argc, argv, "irne:f:", &opt_e, &opt_f, + &bbg.be_quiet); /* counter for -n */ + argc -= optind; + argv += optind; + if (opt & OPT_in_place) { // -i + atexit(cleanup_outname); + } + if (opt & 0x2) bbg.regex_type |= REG_EXTENDED; // -r + //if (opt & 0x4) bbg.be_quiet++; // -n + if (opt & 0x8) { // -e + /* getopt32 reverses order of arguments, handle it */ + add_cmds_link(opt_e); + } + if (opt & 0x10) { // -f + /* getopt32 reverses order of arguments, handle it */ + add_files_link(opt_f); + } + /* if we didn't get a pattern from -e or -f, use argv[0] */ + if (!(opt & 0x18)) { + if (!argc) + bb_show_usage(); + add_cmd_block(*argv++); + argc--; + } + /* Flush any unfinished commands. */ + add_cmd(""); + + /* By default, we write to stdout */ + bbg.nonstdout = stdout; + + /* argv[0..(argc-1)] should be names of file to process. If no + * files were specified or '-' was specified, take input from stdin. + * Otherwise, we process all the files specified. */ + if (argv[0] == NULL) { + if (opt & OPT_in_place) + bb_error_msg_and_die(bb_msg_requires_arg, "-i"); + add_input_file(stdin); + process_files(); + } else { + int i; + FILE *file; + + for (i = 0; i < argc; i++) { + struct stat statbuf; + int nonstdoutfd; + + if (argv[i][0] == '-' && !argv[i][1] + && !(opt & OPT_in_place) + ) { + add_input_file(stdin); + process_files(); + continue; + } + file = fopen_or_warn(argv[i], "r"); + if (!file) { + status = EXIT_FAILURE; + continue; + } + if (!(opt & OPT_in_place)) { + add_input_file(file); + continue; + } + + bbg.outname = xasprintf("%sXXXXXX", argv[i]); + nonstdoutfd = mkstemp(bbg.outname); + if (-1 == nonstdoutfd) + bb_error_msg_and_die("no temp file"); + bbg.nonstdout = fdopen(nonstdoutfd, "w"); + + /* Set permissions of output file */ + + fstat(fileno(file), &statbuf); + fchmod(nonstdoutfd, statbuf.st_mode); + add_input_file(file); + process_files(); + fclose(bbg.nonstdout); + + bbg.nonstdout = stdout; + /* unlink(argv[i]); */ + // FIXME: error check / message? + rename(bbg.outname, argv[i]); + free(bbg.outname); + bbg.outname = 0; + } + if (bbg.input_file_count > bbg.current_input_file) + process_files(); + } + + return status; +} diff --git a/editors/vi.c b/editors/vi.c new file mode 100644 index 000000000..eef895c53 --- /dev/null +++ b/editors/vi.c @@ -0,0 +1,3926 @@ +/* vi: set sw=4 ts=4: */ +/* + * tiny vi.c: A small 'vi' clone + * Copyright (C) 2000, 2001 Sterling Huxley + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* + * Things To Do: + * EXINIT + * $HOME/.exrc and ./.exrc + * add magic to search /foo.*bar + * add :help command + * :map macros + * if mark[] values were line numbers rather than pointers + * it would be easier to change the mark when add/delete lines + * More intelligence in refresh() + * ":r !cmd" and "!cmd" to filter text through an external command + * A true "undo" facility + * An "ex" line oriented mode- maybe using "cmdedit" + */ + + +#include "busybox.h" + +#ifdef CONFIG_LOCALE_SUPPORT +#define Isprint(c) isprint((c)) +#else +#define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') ) +#endif + +#define MAX_SCR_COLS BUFSIZ + +// Misc. non-Ascii keys that report an escape sequence +#define VI_K_UP 128 // cursor key Up +#define VI_K_DOWN 129 // cursor key Down +#define VI_K_RIGHT 130 // Cursor Key Right +#define VI_K_LEFT 131 // cursor key Left +#define VI_K_HOME 132 // Cursor Key Home +#define VI_K_END 133 // Cursor Key End +#define VI_K_INSERT 134 // Cursor Key Insert +#define VI_K_PAGEUP 135 // Cursor Key Page Up +#define VI_K_PAGEDOWN 136 // Cursor Key Page Down +#define VI_K_FUN1 137 // Function Key F1 +#define VI_K_FUN2 138 // Function Key F2 +#define VI_K_FUN3 139 // Function Key F3 +#define VI_K_FUN4 140 // Function Key F4 +#define VI_K_FUN5 141 // Function Key F5 +#define VI_K_FUN6 142 // Function Key F6 +#define VI_K_FUN7 143 // Function Key F7 +#define VI_K_FUN8 144 // Function Key F8 +#define VI_K_FUN9 145 // Function Key F9 +#define VI_K_FUN10 146 // Function Key F10 +#define VI_K_FUN11 147 // Function Key F11 +#define VI_K_FUN12 148 // Function Key F12 + +/* vt102 typical ESC sequence */ +/* terminal standout start/normal ESC sequence */ +static const char SOs[] = "\033[7m"; +static const char SOn[] = "\033[0m"; +/* terminal bell sequence */ +static const char bell[] = "\007"; +/* Clear-end-of-line and Clear-end-of-screen ESC sequence */ +static const char Ceol[] = "\033[0K"; +static const char Ceos [] = "\033[0J"; +/* Cursor motion arbitrary destination ESC sequence */ +static const char CMrc[] = "\033[%d;%dH"; +/* Cursor motion up and down ESC sequence */ +static const char CMup[] = "\033[A"; +static const char CMdown[] = "\n"; + + +enum { + YANKONLY = FALSE, + YANKDEL = TRUE, + FORWARD = 1, // code depends on "1" for array index + BACK = -1, // code depends on "-1" for array index + LIMITED = 0, // how much of text[] in char_search + FULL = 1, // how much of text[] in char_search + + S_BEFORE_WS = 1, // used in skip_thing() for moving "dot" + S_TO_WS = 2, // used in skip_thing() for moving "dot" + S_OVER_WS = 3, // used in skip_thing() for moving "dot" + S_END_PUNCT = 4, // used in skip_thing() for moving "dot" + S_END_ALNUM = 5 // used in skip_thing() for moving "dot" +}; + +typedef unsigned char Byte; + +static int vi_setops; +#define VI_AUTOINDENT 1 +#define VI_SHOWMATCH 2 +#define VI_IGNORECASE 4 +#define VI_ERR_METHOD 8 +#define autoindent (vi_setops & VI_AUTOINDENT) +#define showmatch (vi_setops & VI_SHOWMATCH ) +#define ignorecase (vi_setops & VI_IGNORECASE) +/* indicate error with beep or flash */ +#define err_method (vi_setops & VI_ERR_METHOD) + + +static int editing; // >0 while we are editing a file +static int cmd_mode; // 0=command 1=insert 2=replace +static int file_modified; // buffer contents changed +static int last_file_modified = -1; +static int fn_start; // index of first cmd line file name +static int save_argc; // how many file names on cmd line +static int cmdcnt; // repetition count +static fd_set rfds; // use select() for small sleeps +static struct timeval tv; // use select() for small sleeps +static int rows, columns; // the terminal screen is this size +static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset +static Byte *status_buffer; // mesages to the user +#define STATUS_BUFFER_LEN 200 +static int have_status_msg; // is default edit status needed? +static int last_status_cksum; // hash of current status line +static Byte *cfn; // previous, current, and next file name +static Byte *text, *end; // pointers to the user data in memory +static Byte *screen; // pointer to the virtual screen buffer +static int screensize; // and its size +static Byte *screenbegin; // index into text[], of top line on the screen +static Byte *dot; // where all the action takes place +static int tabstop; +static struct termios term_orig, term_vi; // remember what the cooked mode was +static Byte erase_char; // the users erase character +static Byte last_input_char; // last char read from user +static Byte last_forward_char; // last char searched for with 'f' + +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR +static int last_row; // where the cursor was last moved to +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS +static jmp_buf restart; // catch_sig() +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ +#if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME) +static int my_pid; +#endif +#ifdef CONFIG_FEATURE_VI_DOT_CMD +static int adding2q; // are we currently adding user input to q +static Byte *last_modifying_cmd; // last modifying cmd for "." +static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read" +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ +#if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK) +static Byte *modifying_cmds; // cmds that modify text[] +#endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_READONLY +static int vi_readonly, readonly; +#endif /* CONFIG_FEATURE_VI_READONLY */ +#ifdef CONFIG_FEATURE_VI_YANKMARK +static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27 +static int YDreg, Ureg; // default delete register and orig line for "U" +static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context '' +static Byte *context_start, *context_end; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_SEARCH +static Byte *last_search_pattern; // last pattern from a '/' or '?' search +#endif /* CONFIG_FEATURE_VI_SEARCH */ + + +static void edit_file(Byte *); // edit one file +static void do_cmd(Byte); // execute a command +static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot +static Byte *begin_line(Byte *); // return pointer to cur line B-o-l +static Byte *end_line(Byte *); // return pointer to cur line E-o-l +static Byte *prev_line(Byte *); // return pointer to prev line B-o-l +static Byte *next_line(Byte *); // return pointer to next line B-o-l +static Byte *end_screen(void); // get pointer to last char on screen +static int count_lines(Byte *, Byte *); // count line from start to stop +static Byte *find_line(int); // find begining of line #li +static Byte *move_to_col(Byte *, int); // move "p" to column l +static int isblnk(Byte); // is the char a blank or tab +static void dot_left(void); // move dot left- dont leave line +static void dot_right(void); // move dot right- dont leave line +static void dot_begin(void); // move dot to B-o-l +static void dot_end(void); // move dot to E-o-l +static void dot_next(void); // move dot to next line B-o-l +static void dot_prev(void); // move dot to prev line B-o-l +static void dot_scroll(int, int); // move the screen up or down +static void dot_skip_over_ws(void); // move dot pat WS +static void dot_delete(void); // delete the char at 'dot' +static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end" +static Byte *new_screen(int, int); // malloc virtual screen memory +static Byte *new_text(int); // malloc memory for text[] buffer +static Byte *char_insert(Byte *, Byte); // insert the char c at 'p' +static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p' +static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object +static int st_test(Byte *, int, int, Byte *); // helper for skip_thing() +static Byte *skip_thing(Byte *, int, int, int); // skip some object +static Byte *find_pair(Byte *, Byte); // find matching pair () [] {} +static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole +static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole +static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete +static void show_help(void); // display some help info +static void rawmode(void); // set "raw" mode on tty +static void cookmode(void); // return to "cooked" mode on tty +static int mysleep(int); // sleep for 'h' 1/100 seconds +static Byte readit(void); // read (maybe cursor) key from stdin +static Byte get_one_char(void); // read 1 char from stdin +static int file_size(const Byte *); // what is the byte size of "fn" +static int file_insert(Byte *, Byte *, int); +static int file_write(Byte *, Byte *, Byte *); +static void place_cursor(int, int, int); +static void screen_erase(void); +static void clear_to_eol(void); +static void clear_to_eos(void); +static void standout_start(void); // send "start reverse video" sequence +static void standout_end(void); // send "end reverse video" sequence +static void flash(int); // flash the terminal screen +static void show_status_line(void); // put a message on the bottom line +static void psb(const char *, ...); // Print Status Buf +static void psbs(const char *, ...); // Print Status Buf in standout mode +static void ni(Byte *); // display messages +static int format_edit_status(void); // format file status on status line +static void redraw(int); // force a full screen refresh +static void format_line(Byte*, Byte*, int); +static void refresh(int); // update the terminal from screen[] + +static void Indicate_Error(void); // use flash or beep to indicate error +#define indicate_error(c) Indicate_Error() +static void Hit_Return(void); + +#ifdef CONFIG_FEATURE_VI_SEARCH +static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p +static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase" +#endif /* CONFIG_FEATURE_VI_SEARCH */ +#ifdef CONFIG_FEATURE_VI_COLON +static Byte *get_one_address(Byte *, int *); // get colon addr, if present +static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present +static void colon(Byte *); // execute the "colon" mode cmds +#endif /* CONFIG_FEATURE_VI_COLON */ +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS +static void winch_sig(int); // catch window size changes +static void suspend_sig(int); // catch ctrl-Z +static void catch_sig(int); // catch ctrl-C and alarm time-outs +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ +#ifdef CONFIG_FEATURE_VI_DOT_CMD +static void start_new_cmd_q(Byte); // new queue for command +static void end_cmd_q(void); // stop saving input chars +#else /* CONFIG_FEATURE_VI_DOT_CMD */ +#define end_cmd_q() +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ +#ifdef CONFIG_FEATURE_VI_SETOPTS +static void showmatching(Byte *); // show the matching pair () [] {} +#endif /* CONFIG_FEATURE_VI_SETOPTS */ +#if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME) +static Byte *string_insert(Byte *, Byte *); // insert the string at 'p' +#endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_SEARCH || CONFIG_FEATURE_VI_CRASHME */ +#ifdef CONFIG_FEATURE_VI_YANKMARK +static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register +static Byte what_reg(void); // what is letter of current YDreg +static void check_context(Byte); // remember context for '' command +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_CRASHME +static void crash_dummy(); +static void crash_test(); +static int crashme = 0; +#endif /* CONFIG_FEATURE_VI_CRASHME */ + + +static void write1(const char *out) +{ + fputs(out, stdout); +} + +int vi_main(int argc, char **argv) +{ + int c; + RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN); + +#ifdef CONFIG_FEATURE_VI_YANKMARK + int i; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME) + my_pid = getpid(); +#endif +#ifdef CONFIG_FEATURE_VI_CRASHME + (void) srand((long) my_pid); +#endif /* CONFIG_FEATURE_VI_CRASHME */ + + status_buffer = (Byte *)STATUS_BUFFER; + last_status_cksum = 0; + +#ifdef CONFIG_FEATURE_VI_READONLY + vi_readonly = readonly = FALSE; + if (strncmp(argv[0], "view", 4) == 0) { + readonly = TRUE; + vi_readonly = TRUE; + } +#endif /* CONFIG_FEATURE_VI_READONLY */ + vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD; +#ifdef CONFIG_FEATURE_VI_YANKMARK + for (i = 0; i < 28; i++) { + reg[i] = 0; + } // init the yank regs +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK) + modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[] +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + + // 1- process $HOME/.exrc file + // 2- process EXINIT variable from environment + // 3- process command line args + while ((c = getopt(argc, argv, "hCR")) != -1) { + switch (c) { +#ifdef CONFIG_FEATURE_VI_CRASHME + case 'C': + crashme = 1; + break; +#endif /* CONFIG_FEATURE_VI_CRASHME */ +#ifdef CONFIG_FEATURE_VI_READONLY + case 'R': // Read-only flag + readonly = TRUE; + vi_readonly = TRUE; + break; +#endif /* CONFIG_FEATURE_VI_READONLY */ + //case 'r': // recover flag- ignore- we don't use tmp file + //case 'x': // encryption flag- ignore + //case 'c': // execute command first + //case 'h': // help -- just use default + default: + show_help(); + return 1; + } + } + + // The argv array can be used by the ":next" and ":rewind" commands + // save optind. + fn_start = optind; // remember first file name for :next and :rew + save_argc = argc; + + //----- This is the main file handling loop -------------- + if (optind >= argc) { + editing = 1; // 0= exit, 1= one file, 2= multiple files + edit_file(0); + } else { + for (; optind < argc; optind++) { + editing = 1; // 0=exit, 1=one file, 2+ =many files + free(cfn); + cfn = (Byte *) xstrdup(argv[optind]); + edit_file(cfn); + } + } + //----------------------------------------------------------- + + return 0; +} + +static void edit_file(Byte * fn) +{ + Byte c; + int cnt, size, ch; + +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS + int sig; +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ +#ifdef CONFIG_FEATURE_VI_YANKMARK + static Byte *cur_line; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + + rawmode(); + rows = 24; + columns = 80; + ch= -1; + if (ENABLE_FEATURE_VI_WIN_RESIZE) + get_terminal_width_height(0, &columns, &rows); + new_screen(rows, columns); // get memory for virtual screen + + cnt = file_size(fn); // file size + size = 2 * cnt; // 200% of file size + new_text(size); // get a text[] buffer + screenbegin = dot = end = text; + if (fn != 0) { + ch= file_insert(fn, text, cnt); + } + if (ch < 1) { + (void) char_insert(text, '\n'); // start empty buf with dummy line + } + file_modified = 0; + last_file_modified = -1; +#ifdef CONFIG_FEATURE_VI_YANKMARK + YDreg = 26; // default Yank/Delete reg + Ureg = 27; // hold orig line for "U" cmd + for (cnt = 0; cnt < 28; cnt++) { + mark[cnt] = 0; + } // init the marks + mark[26] = mark[27] = text; // init "previous context" +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + + last_forward_char = last_input_char = '\0'; + crow = 0; + ccol = 0; + +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS + catch_sig(0); + signal(SIGWINCH, winch_sig); + signal(SIGTSTP, suspend_sig); + sig = setjmp(restart); + if (sig != 0) { + screenbegin = dot = text; + } +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ + + editing = 1; + cmd_mode = 0; // 0=command 1=insert 2='R'eplace + cmdcnt = 0; + tabstop = 8; + offset = 0; // no horizontal offset + c = '\0'; +#ifdef CONFIG_FEATURE_VI_DOT_CMD + free(last_modifying_cmd); + free(ioq_start); + ioq = ioq_start = last_modifying_cmd = 0; + adding2q = 0; +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + redraw(FALSE); // dont force every col re-draw + show_status_line(); + + //------This is the main Vi cmd handling loop ----------------------- + while (editing > 0) { +#ifdef CONFIG_FEATURE_VI_CRASHME + if (crashme > 0) { + if ((end - text) > 1) { + crash_dummy(); // generate a random command + } else { + crashme = 0; + dot = + string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string + refresh(FALSE); + } + } +#endif /* CONFIG_FEATURE_VI_CRASHME */ + last_input_char = c = get_one_char(); // get a cmd from user +#ifdef CONFIG_FEATURE_VI_YANKMARK + // save a copy of the current line- for the 'U" command + if (begin_line(dot) != cur_line) { + cur_line = begin_line(dot); + text_yank(begin_line(dot), end_line(dot), Ureg); + } +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_DOT_CMD + // These are commands that change text[]. + // Remember the input for the "." command + if (!adding2q && ioq_start == 0 + && strchr((char *) modifying_cmds, c) != NULL) { + start_new_cmd_q(c); + } +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + do_cmd(c); // execute the user command + // + // poll to see if there is input already waiting. if we are + // not able to display output fast enough to keep up, skip + // the display update until we catch up with input. + if (mysleep(0) == 0) { + // no input pending- so update output + refresh(FALSE); + show_status_line(); + } +#ifdef CONFIG_FEATURE_VI_CRASHME + if (crashme > 0) + crash_test(); // test editor variables +#endif /* CONFIG_FEATURE_VI_CRASHME */ + } + //------------------------------------------------------------------- + + place_cursor(rows, 0, FALSE); // go to bottom of screen + clear_to_eol(); // Erase to end of line + cookmode(); +} + +//----- The Colon commands ------------------------------------- +#ifdef CONFIG_FEATURE_VI_COLON +static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present +{ + int st; + Byte *q; + +#ifdef CONFIG_FEATURE_VI_YANKMARK + Byte c; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_SEARCH + Byte *pat, buf[BUFSIZ]; +#endif /* CONFIG_FEATURE_VI_SEARCH */ + + *addr = -1; // assume no addr + if (*p == '.') { // the current line + p++; + q = begin_line(dot); + *addr = count_lines(text, q); +#ifdef CONFIG_FEATURE_VI_YANKMARK + } else if (*p == '\'') { // is this a mark addr + p++; + c = tolower(*p); + p++; + if (c >= 'a' && c <= 'z') { + // we have a mark + c = c - 'a'; + q = mark[(int) c]; + if (q != NULL) { // is mark valid + *addr = count_lines(text, q); // count lines + } + } +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_SEARCH + } else if (*p == '/') { // a search pattern + q = buf; + for (p++; *p; p++) { + if (*p == '/') + break; + *q++ = *p; + *q = '\0'; + } + pat = (Byte *) xstrdup((char *) buf); // save copy of pattern + if (*p == '/') + p++; + q = char_search(dot, pat, FORWARD, FULL); + if (q != NULL) { + *addr = count_lines(text, q); + } + free(pat); +#endif /* CONFIG_FEATURE_VI_SEARCH */ + } else if (*p == '$') { // the last line in file + p++; + q = begin_line(end - 1); + *addr = count_lines(text, q); + } else if (isdigit(*p)) { // specific line number + sscanf((char *) p, "%d%n", addr, &st); + p += st; + } else { // I don't reconise this + // unrecognised address- assume -1 + *addr = -1; + } + return p; +} + +static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present +{ + //----- get the address' i.e., 1,3 'a,'b ----- + // get FIRST addr, if present + while (isblnk(*p)) + p++; // skip over leading spaces + if (*p == '%') { // alias for 1,$ + p++; + *b = 1; + *e = count_lines(text, end-1); + goto ga0; + } + p = get_one_address(p, b); + while (isblnk(*p)) + p++; + if (*p == ',') { // is there a address separator + p++; + while (isblnk(*p)) + p++; + // get SECOND addr, if present + p = get_one_address(p, e); + } +ga0: + while (isblnk(*p)) + p++; // skip over trailing spaces + return p; +} + +#ifdef CONFIG_FEATURE_VI_SETOPTS +static void setops(const Byte *args, const char *opname, int flg_no, + const char *short_opname, int opt) +{ + const char *a = (char *) args + flg_no; + int l = strlen(opname) - 1; /* opname have + ' ' */ + + if (strncasecmp(a, opname, l) == 0 || + strncasecmp(a, short_opname, 2) == 0) { + if(flg_no) + vi_setops &= ~opt; + else + vi_setops |= opt; + } +} +#endif + +static void colon(Byte * buf) +{ + Byte c, *orig_buf, *buf1, *q, *r; + Byte *fn, cmd[BUFSIZ], args[BUFSIZ]; + int i, l, li, ch, b, e; + int useforce = FALSE, forced = FALSE; + struct stat st_buf; + + // :3154 // if (-e line 3154) goto it else stay put + // :4,33w! foo // write a portion of buffer to file "foo" + // :w // write all of buffer to current file + // :q // quit + // :q! // quit- dont care about modified file + // :'a,'z!sort -u // filter block through sort + // :'f // goto mark "f" + // :'fl // list literal the mark "f" line + // :.r bar // read file "bar" into buffer before dot + // :/123/,/abc/d // delete lines from "123" line to "abc" line + // :/xyz/ // goto the "xyz" line + // :s/find/replace/ // substitute pattern "find" with "replace" + // :! // run then return + // + + if (strlen((char *) buf) <= 0) + goto vc1; + if (*buf == ':') + buf++; // move past the ':' + + li = ch = i = 0; + b = e = -1; + q = text; // assume 1,$ for the range + r = end - 1; + li = count_lines(text, end - 1); + fn = cfn; // default to current file + memset(cmd, '\0', BUFSIZ); // clear cmd[] + memset(args, '\0', BUFSIZ); // clear args[] + + // look for optional address(es) :. :1 :1,9 :'q,'a :% + buf = get_address(buf, &b, &e); + + // remember orig command line + orig_buf = buf; + + // get the COMMAND into cmd[] + buf1 = cmd; + while (*buf != '\0') { + if (isspace(*buf)) + break; + *buf1++ = *buf++; + } + // get any ARGuments + while (isblnk(*buf)) + buf++; + strcpy((char *) args, (char *) buf); + buf1 = (Byte*)last_char_is((char *)cmd, '!'); + if (buf1) { + useforce = TRUE; + *buf1 = '\0'; // get rid of ! + } + if (b >= 0) { + // if there is only one addr, then the addr + // is the line number of the single line the + // user wants. So, reset the end + // pointer to point at end of the "b" line + q = find_line(b); // what line is #b + r = end_line(q); + li = 1; + } + if (e >= 0) { + // we were given two addrs. change the + // end pointer to the addr given by user. + r = find_line(e); // what line is #e + r = end_line(r); + li = e - b + 1; + } + // ------------ now look for the command ------------ + i = strlen((char *) cmd); + if (i == 0) { // :123CR goto line #123 + if (b >= 0) { + dot = find_line(b); // what line is #b + dot_skip_over_ws(); + } + } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd + // :!ls run the + (void) alarm(0); // wait for input- no alarms + place_cursor(rows - 1, 0, FALSE); // go to Status line + clear_to_eol(); // clear the line + cookmode(); + system((char*)(orig_buf+1)); // run the cmd + rawmode(); + Hit_Return(); // let user see results + (void) alarm(3); // done waiting for input + } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address + if (b < 0) { // no addr given- use defaults + b = e = count_lines(text, dot); + } + psb("%d", b); + } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume .,. for the range + r = end_line(dot); + } + dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines + dot_skip_over_ws(); + } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file + int sr; + sr= 0; + // don't edit, if the current file has been modified + if (file_modified && ! useforce) { + psbs("No write since last change (:edit! overrides)"); + goto vc1; + } + if (strlen((char*)args) > 0) { + // the user supplied a file name + fn= args; + } else if (cfn != 0 && strlen((char*)cfn) > 0) { + // no user supplied name- use the current filename + fn= cfn; + goto vc5; + } else { + // no user file name, no current name- punt + psbs("No current filename"); + goto vc1; + } + + // see if file exists- if not, its just a new file request + if ((sr=stat((char*)fn, &st_buf)) < 0) { + // This is just a request for a new file creation. + // The file_insert below will fail but we get + // an empty buffer with a file name. Then the "write" + // command can do the create. + } else { + if ((st_buf.st_mode & (S_IFREG)) == 0) { + // This is not a regular file + psbs("\"%s\" is not a regular file", fn); + goto vc1; + } + if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) { + // dont have any read permissions + psbs("\"%s\" is not readable", fn); + goto vc1; + } + } + + // There is a read-able regular file + // make this the current file + q = (Byte *) xstrdup((char *) fn); // save the cfn + free(cfn); // free the old name + cfn = q; // remember new cfn + + vc5: + // delete all the contents of text[] + new_text(2 * file_size(fn)); + screenbegin = dot = end = text; + + // insert new file + ch = file_insert(fn, text, file_size(fn)); + + if (ch < 1) { + // start empty buf with dummy line + (void) char_insert(text, '\n'); + ch= 1; + } + file_modified = 0; + last_file_modified = -1; +#ifdef CONFIG_FEATURE_VI_YANKMARK + if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) { + free(reg[Ureg]); // free orig line reg- for 'U' + reg[Ureg]= 0; + } + if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) { + free(reg[YDreg]); // free default yank/delete register + reg[YDreg]= 0; + } + for (li = 0; li < 28; li++) { + mark[li] = 0; + } // init the marks +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + // how many lines in text[]? + li = count_lines(text, end - 1); + psb("\"%s\"%s" +#ifdef CONFIG_FEATURE_VI_READONLY + "%s" +#endif /* CONFIG_FEATURE_VI_READONLY */ + " %dL, %dC", cfn, + (sr < 0 ? " [New file]" : ""), +#ifdef CONFIG_FEATURE_VI_READONLY + ((vi_readonly || readonly) ? " [Read only]" : ""), +#endif /* CONFIG_FEATURE_VI_READONLY */ + li, ch); + } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this + if (b != -1 || e != -1) { + ni((Byte *) "No address allowed on this command"); + goto vc1; + } + if (strlen((char *) args) > 0) { + // user wants a new filename + free(cfn); + cfn = (Byte *) xstrdup((char *) args); + } else { + // user wants file status info + last_status_cksum = 0; // force status update + } + } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available + // print out values of all features + place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen + clear_to_eol(); // clear the line + cookmode(); + show_help(); + rawmode(); + Hit_Return(); + } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume .,. for the range + r = end_line(dot); + } + place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen + clear_to_eol(); // clear the line + puts("\r"); + for (; q <= r; q++) { + int c_is_no_print; + + c = *q; + c_is_no_print = c > 127 && !Isprint(c); + if (c_is_no_print) { + c = '.'; + standout_start(); + } + if (c == '\n') { + write1("$\r"); + } else if (c < ' ' || c == 127) { + putchar('^'); + if(c == 127) + c = '?'; + else + c += '@'; + } + putchar(c); + if (c_is_no_print) + standout_end(); + } +#ifdef CONFIG_FEATURE_VI_SET + vc2: +#endif /* CONFIG_FEATURE_VI_SET */ + Hit_Return(); + } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit + (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file + if (useforce) { + // force end of argv list + if (*cmd == 'q') { + optind = save_argc; + } + editing = 0; + goto vc1; + } + // don't exit if the file been modified + if (file_modified) { + psbs("No write since last change (:%s! overrides)", + (*cmd == 'q' ? "quit" : "next")); + goto vc1; + } + // are there other file to edit + if (*cmd == 'q' && optind < save_argc - 1) { + psbs("%d more file to edit", (save_argc - optind - 1)); + goto vc1; + } + if (*cmd == 'n' && optind >= save_argc - 1) { + psbs("No more files to edit"); + goto vc1; + } + editing = 0; + } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[] + fn = args; + if (strlen((char *) fn) <= 0) { + psbs("No filename given"); + goto vc1; + } + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume "dot" + } + // read after current line- unless user said ":0r foo" + if (b != 0) + q = next_line(q); +#ifdef CONFIG_FEATURE_VI_READONLY + l= readonly; // remember current files' status +#endif + ch = file_insert(fn, q, file_size(fn)); +#ifdef CONFIG_FEATURE_VI_READONLY + readonly= l; +#endif + if (ch < 0) + goto vc1; // nothing was inserted + // how many lines in text[]? + li = count_lines(q, q + ch - 1); + psb("\"%s\"" +#ifdef CONFIG_FEATURE_VI_READONLY + "%s" +#endif /* CONFIG_FEATURE_VI_READONLY */ + " %dL, %dC", fn, +#ifdef CONFIG_FEATURE_VI_READONLY + ((vi_readonly || readonly) ? " [Read only]" : ""), +#endif /* CONFIG_FEATURE_VI_READONLY */ + li, ch); + if (ch > 0) { + // if the insert is before "dot" then we need to update + if (q <= dot) + dot += ch; + file_modified++; + } + } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args + if (file_modified && ! useforce) { + psbs("No write since last change (:rewind! overrides)"); + } else { + // reset the filenames to edit + optind = fn_start - 1; + editing = 0; + } +#ifdef CONFIG_FEATURE_VI_SET + } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features + i = 0; // offset into args + if (strlen((char *) args) == 0) { + // print out values of all options + place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen + clear_to_eol(); // clear the line + printf("----------------------------------------\r\n"); +#ifdef CONFIG_FEATURE_VI_SETOPTS + if (!autoindent) + printf("no"); + printf("autoindent "); + if (!err_method) + printf("no"); + printf("flash "); + if (!ignorecase) + printf("no"); + printf("ignorecase "); + if (!showmatch) + printf("no"); + printf("showmatch "); + printf("tabstop=%d ", tabstop); +#endif /* CONFIG_FEATURE_VI_SETOPTS */ + printf("\r\n"); + goto vc2; + } + if (strncasecmp((char *) args, "no", 2) == 0) + i = 2; // ":set noautoindent" +#ifdef CONFIG_FEATURE_VI_SETOPTS + setops(args, "autoindent ", i, "ai", VI_AUTOINDENT); + setops(args, "flash ", i, "fl", VI_ERR_METHOD); + setops(args, "ignorecase ", i, "ic", VI_IGNORECASE); + setops(args, "showmatch ", i, "ic", VI_SHOWMATCH); + if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) { + sscanf(strchr((char *) args + i, '='), "=%d", &ch); + if (ch > 0 && ch < columns - 1) + tabstop = ch; + } +#endif /* CONFIG_FEATURE_VI_SETOPTS */ +#endif /* CONFIG_FEATURE_VI_SET */ +#ifdef CONFIG_FEATURE_VI_SEARCH + } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern + Byte *ls, *F, *R; + int gflag; + + // F points to the "find" pattern + // R points to the "replace" pattern + // replace the cmd line delimiters "/" with NULLs + gflag = 0; // global replace flag + c = orig_buf[1]; // what is the delimiter + F = orig_buf + 2; // start of "find" + R = (Byte *) strchr((char *) F, c); // middle delimiter + if (!R) goto colon_s_fail; + *R++ = '\0'; // terminate "find" + buf1 = (Byte *) strchr((char *) R, c); + if (!buf1) goto colon_s_fail; + *buf1++ = '\0'; // terminate "replace" + if (*buf1 == 'g') { // :s/foo/bar/g + buf1++; + gflag++; // turn on gflag + } + q = begin_line(q); + if (b < 0) { // maybe :s/foo/bar/ + q = begin_line(dot); // start with cur line + b = count_lines(text, q); // cur line number + } + if (e < 0) + e = b; // maybe :.s/foo/bar/ + for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0 + ls = q; // orig line start + vc4: + buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find" + if (buf1 != NULL) { + // we found the "find" pattern- delete it + (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1); + // inset the "replace" patern + (void) string_insert(buf1, R); // insert the string + // check for "global" :s/foo/bar/g + if (gflag == 1) { + if ((buf1 + strlen((char *) R)) < end_line(ls)) { + q = buf1 + strlen((char *) R); + goto vc4; // don't let q move past cur line + } + } + } + q = next_line(ls); + } +#endif /* CONFIG_FEATURE_VI_SEARCH */ + } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version + psb("%s", BB_VER " " BB_BT); + } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file + || strncasecmp((char *) cmd, "wq", i) == 0 + || strncasecmp((char *) cmd, "wn", i) == 0 + || strncasecmp((char *) cmd, "x", i) == 0) { + // is there a file name to write to? + if (strlen((char *) args) > 0) { + fn = args; + } +#ifdef CONFIG_FEATURE_VI_READONLY + if ((vi_readonly || readonly) && ! useforce) { + psbs("\"%s\" File is read only", fn); + goto vc3; + } +#endif /* CONFIG_FEATURE_VI_READONLY */ + // how many lines in text[]? + li = count_lines(q, r); + ch = r - q + 1; + // see if file exists- if not, its just a new file request + if (useforce) { + // if "fn" is not write-able, chmod u+w + // sprintf(syscmd, "chmod u+w %s", fn); + // system(syscmd); + forced = TRUE; + } + l = file_write(fn, q, r); + if (useforce && forced) { + // chmod u-w + // sprintf(syscmd, "chmod u-w %s", fn); + // system(syscmd); + forced = FALSE; + } + if (l < 0) { + if (l == -1) + psbs("Write error: %s", strerror(errno)); + } else { + psb("\"%s\" %dL, %dC", fn, li, l); + if (q == text && r == end - 1 && l == ch) { + file_modified = 0; + last_file_modified = -1; + } + if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' || + cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N') + && l == ch) { + editing = 0; + } + } +#ifdef CONFIG_FEATURE_VI_READONLY + vc3:; +#endif /* CONFIG_FEATURE_VI_READONLY */ +#ifdef CONFIG_FEATURE_VI_YANKMARK + } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume .,. for the range + r = end_line(dot); + } + text_yank(q, r, YDreg); + li = count_lines(q, r); + psb("Yank %d lines (%d chars) into [%c]", + li, strlen((char *) reg[YDreg]), what_reg()); +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + } else { + // cmd unknown + ni((Byte *) cmd); + } + vc1: + dot = bound_dot(dot); // make sure "dot" is valid + return; +#ifdef CONFIG_FEATURE_VI_SEARCH +colon_s_fail: + psb(":s expression missing delimiters"); +#endif +} + +#endif /* CONFIG_FEATURE_VI_COLON */ + +static void Hit_Return(void) +{ + char c; + + standout_start(); // start reverse video + write1("[Hit return to continue]"); + standout_end(); // end reverse video + while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */ + ; + redraw(TRUE); // force redraw all +} + +//----- Synchronize the cursor to Dot -------------------------- +static void sync_cursor(Byte * d, int *row, int *col) +{ + Byte *beg_cur; // begin and end of "d" line + Byte *end_scr; // begin and end of screen + Byte *tp; + int cnt, ro, co; + + beg_cur = begin_line(d); // first char of cur line + + end_scr = end_screen(); // last char of screen + + if (beg_cur < screenbegin) { + // "d" is before top line on screen + // how many lines do we have to move + cnt = count_lines(beg_cur, screenbegin); + sc1: + screenbegin = beg_cur; + if (cnt > (rows - 1) / 2) { + // we moved too many lines. put "dot" in middle of screen + for (cnt = 0; cnt < (rows - 1) / 2; cnt++) { + screenbegin = prev_line(screenbegin); + } + } + } else if (beg_cur > end_scr) { + // "d" is after bottom line on screen + // how many lines do we have to move + cnt = count_lines(end_scr, beg_cur); + if (cnt > (rows - 1) / 2) + goto sc1; // too many lines + for (ro = 0; ro < cnt - 1; ro++) { + // move screen begin the same amount + screenbegin = next_line(screenbegin); + // now, move the end of screen + end_scr = next_line(end_scr); + end_scr = end_line(end_scr); + } + } + // "d" is on screen- find out which row + tp = screenbegin; + for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row + if (tp == beg_cur) + break; + tp = next_line(tp); + } + + // find out what col "d" is on + co = 0; + do { // drive "co" to correct column + if (*tp == '\n' || *tp == '\0') + break; + if (*tp == '\t') { + // 7 - (co % 8 ) + co += ((tabstop - 1) - (co % tabstop)); + } else if (*tp < ' ' || *tp == 127) { + co++; // display as ^X, use 2 columns + } + } while (tp++ < d && ++co); + + // "co" is the column where "dot" is. + // The screen has "columns" columns. + // The currently displayed columns are 0+offset -- columns+ofset + // |-------------------------------------------------------------| + // ^ ^ ^ + // offset | |------- columns ----------------| + // + // If "co" is already in this range then we do not have to adjust offset + // but, we do have to subtract the "offset" bias from "co". + // If "co" is outside this range then we have to change "offset". + // If the first char of a line is a tab the cursor will try to stay + // in column 7, but we have to set offset to 0. + + if (co < 0 + offset) { + offset = co; + } + if (co >= columns + offset) { + offset = co - columns + 1; + } + // if the first char of the line is a tab, and "dot" is sitting on it + // force offset to 0. + if (d == beg_cur && *d == '\t') { + offset = 0; + } + co -= offset; + + *row = ro; + *col = co; +} + +//----- Text Movement Routines --------------------------------- +static Byte *begin_line(Byte * p) // return pointer to first char cur line +{ + while (p > text && p[-1] != '\n') + p--; // go to cur line B-o-l + return p; +} + +static Byte *end_line(Byte * p) // return pointer to NL of cur line line +{ + while (p < end - 1 && *p != '\n') + p++; // go to cur line E-o-l + return p; +} + +static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line +{ + while (p < end - 1 && *p != '\n') + p++; // go to cur line E-o-l + // Try to stay off of the Newline + if (*p == '\n' && (p - begin_line(p)) > 0) + p--; + return p; +} + +static Byte *prev_line(Byte * p) // return pointer first char prev line +{ + p = begin_line(p); // goto begining of cur line + if (p[-1] == '\n' && p > text) + p--; // step to prev line + p = begin_line(p); // goto begining of prev line + return p; +} + +static Byte *next_line(Byte * p) // return pointer first char next line +{ + p = end_line(p); + if (*p == '\n' && p < end - 1) + p++; // step to next line + return p; +} + +//----- Text Information Routines ------------------------------ +static Byte *end_screen(void) +{ + Byte *q; + int cnt; + + // find new bottom line + q = screenbegin; + for (cnt = 0; cnt < rows - 2; cnt++) + q = next_line(q); + q = end_line(q); + return q; +} + +static int count_lines(Byte * start, Byte * stop) // count line from start to stop +{ + Byte *q; + int cnt; + + if (stop < start) { // start and stop are backwards- reverse them + q = start; + start = stop; + stop = q; + } + cnt = 0; + stop = end_line(stop); // get to end of this line + for (q = start; q <= stop && q <= end - 1; q++) { + if (*q == '\n') + cnt++; + } + return cnt; +} + +static Byte *find_line(int li) // find begining of line #li +{ + Byte *q; + + for (q = text; li > 1; li--) { + q = next_line(q); + } + return q; +} + +//----- Dot Movement Routines ---------------------------------- +static void dot_left(void) +{ + if (dot > text && dot[-1] != '\n') + dot--; +} + +static void dot_right(void) +{ + if (dot < end - 1 && *dot != '\n') + dot++; +} + +static void dot_begin(void) +{ + dot = begin_line(dot); // return pointer to first char cur line +} + +static void dot_end(void) +{ + dot = end_line(dot); // return pointer to last char cur line +} + +static Byte *move_to_col(Byte * p, int l) +{ + int co; + + p = begin_line(p); + co = 0; + do { + if (*p == '\n' || *p == '\0') + break; + if (*p == '\t') { + // 7 - (co % 8 ) + co += ((tabstop - 1) - (co % tabstop)); + } else if (*p < ' ' || *p == 127) { + co++; // display as ^X, use 2 columns + } + } while (++co <= l && p++ < end); + return p; +} + +static void dot_next(void) +{ + dot = next_line(dot); +} + +static void dot_prev(void) +{ + dot = prev_line(dot); +} + +static void dot_scroll(int cnt, int dir) +{ + Byte *q; + + for (; cnt > 0; cnt--) { + if (dir < 0) { + // scroll Backwards + // ctrl-Y scroll up one line + screenbegin = prev_line(screenbegin); + } else { + // scroll Forwards + // ctrl-E scroll down one line + screenbegin = next_line(screenbegin); + } + } + // make sure "dot" stays on the screen so we dont scroll off + if (dot < screenbegin) + dot = screenbegin; + q = end_screen(); // find new bottom line + if (dot > q) + dot = begin_line(q); // is dot is below bottom line? + dot_skip_over_ws(); +} + +static void dot_skip_over_ws(void) +{ + // skip WS + while (isspace(*dot) && *dot != '\n' && dot < end - 1) + dot++; +} + +static void dot_delete(void) // delete the char at 'dot' +{ + (void) text_hole_delete(dot, dot); +} + +static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end" +{ + if (p >= end && end > text) { + p = end - 1; + indicate_error('1'); + } + if (p < text) { + p = text; + indicate_error('2'); + } + return p; +} + +//----- Helper Utility Routines -------------------------------- + +//---------------------------------------------------------------- +//----- Char Routines -------------------------------------------- +/* Chars that are part of a word- + * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz + * Chars that are Not part of a word (stoppers) + * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~ + * Chars that are WhiteSpace + * TAB NEWLINE VT FF RETURN SPACE + * DO NOT COUNT NEWLINE AS WHITESPACE + */ + +static Byte *new_screen(int ro, int co) +{ + int li; + + free(screen); + screensize = ro * co + 8; + screen = (Byte *) xmalloc(screensize); + // initialize the new screen. assume this will be a empty file. + screen_erase(); + // non-existent text[] lines start with a tilde (~). + for (li = 1; li < ro - 1; li++) { + screen[(li * co) + 0] = '~'; + } + return screen; +} + +static Byte *new_text(int size) +{ + if (size < 10240) + size = 10240; // have a minimum size for new files + free(text); + text = (Byte *) xmalloc(size + 8); + memset(text, '\0', size); // clear new text[] + //text += 4; // leave some room for "oops" + return text; +} + +#ifdef CONFIG_FEATURE_VI_SEARCH +static int mycmp(Byte * s1, Byte * s2, int len) +{ + int i; + + i = strncmp((char *) s1, (char *) s2, len); +#ifdef CONFIG_FEATURE_VI_SETOPTS + if (ignorecase) { + i = strncasecmp((char *) s1, (char *) s2, len); + } +#endif /* CONFIG_FEATURE_VI_SETOPTS */ + return i; +} + +static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p +{ +#ifndef REGEX_SEARCH + Byte *start, *stop; + int len; + + len = strlen((char *) pat); + if (dir == FORWARD) { + stop = end - 1; // assume range is p - end-1 + if (range == LIMITED) + stop = next_line(p); // range is to next line + for (start = p; start < stop; start++) { + if (mycmp(start, pat, len) == 0) { + return start; + } + } + } else if (dir == BACK) { + stop = text; // assume range is text - p + if (range == LIMITED) + stop = prev_line(p); // range is to prev line + for (start = p - len; start >= stop; start--) { + if (mycmp(start, pat, len) == 0) { + return start; + } + } + } + // pattern not found + return NULL; +#else /*REGEX_SEARCH */ + char *q; + struct re_pattern_buffer preg; + int i; + int size, range; + + re_syntax_options = RE_SYNTAX_POSIX_EXTENDED; + preg.translate = 0; + preg.fastmap = 0; + preg.buffer = 0; + preg.allocated = 0; + + // assume a LIMITED forward search + q = next_line(p); + q = end_line(q); + q = end - 1; + if (dir == BACK) { + q = prev_line(p); + q = text; + } + // count the number of chars to search over, forward or backward + size = q - p; + if (size < 0) + size = p - q; + // RANGE could be negative if we are searching backwards + range = q - p; + + q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg); + if (q != 0) { + // The pattern was not compiled + psbs("bad search pattern: \"%s\": %s", pat, q); + i = 0; // return p if pattern not compiled + goto cs1; + } + + q = p; + if (range < 0) { + q = p - size; + if (q < text) + q = text; + } + // search for the compiled pattern, preg, in p[] + // range < 0- search backward + // range > 0- search forward + // 0 < start < size + // re_search() < 0 not found or error + // re_search() > 0 index of found pattern + // struct pattern char int int int struct reg + // re_search (*pattern_buffer, *string, size, start, range, *regs) + i = re_search(&preg, q, size, 0, range, 0); + if (i == -1) { + p = 0; + i = 0; // return NULL if pattern not found + } + cs1: + if (dir == FORWARD) { + p = p + i; + } else { + p = p - i; + } + return p; +#endif /*REGEX_SEARCH */ +} +#endif /* CONFIG_FEATURE_VI_SEARCH */ + +static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p' +{ + if (c == 22) { // Is this an ctrl-V? + p = stupid_insert(p, '^'); // use ^ to indicate literal next + p--; // backup onto ^ + refresh(FALSE); // show the ^ + c = get_one_char(); + *p = c; + p++; + file_modified++; // has the file been modified + } else if (c == 27) { // Is this an ESC? + cmd_mode = 0; + cmdcnt = 0; + end_cmd_q(); // stop adding to q + last_status_cksum = 0; // force status update + if ((p[-1] != '\n') && (dot>text)) { + p--; + } + } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS + // 123456789 + if ((p[-1] != '\n') && (dot>text)) { + p--; + p = text_hole_delete(p, p); // shrink buffer 1 char + } + } else { + // insert a char into text[] + Byte *sp; // "save p" + + if (c == 13) + c = '\n'; // translate \r to \n + sp = p; // remember addr of insert + p = stupid_insert(p, c); // insert the char +#ifdef CONFIG_FEATURE_VI_SETOPTS + if (showmatch && strchr(")]}", *sp) != NULL) { + showmatching(sp); + } + if (autoindent && c == '\n') { // auto indent the new line + Byte *q; + + q = prev_line(p); // use prev line as templet + for (; isblnk(*q); q++) { + p = stupid_insert(p, *q); // insert the char + } + } +#endif /* CONFIG_FEATURE_VI_SETOPTS */ + } + return p; +} + +static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p' +{ + p = text_hole_make(p, 1); + if (p != 0) { + *p = c; + file_modified++; // has the file been modified + p++; + } + return p; +} + +static Byte find_range(Byte ** start, Byte ** stop, Byte c) +{ + Byte *save_dot, *p, *q; + int cnt; + + save_dot = dot; + p = q = dot; + + if (strchr("cdy><", c)) { + // these cmds operate on whole lines + p = q = begin_line(p); + for (cnt = 1; cnt < cmdcnt; cnt++) { + q = next_line(q); + } + q = end_line(q); + } else if (strchr("^%$0bBeEft", c)) { + // These cmds operate on char positions + do_cmd(c); // execute movement cmd + q = dot; + } else if (strchr("wW", c)) { + do_cmd(c); // execute movement cmd + // if we are at the next word's first char + // step back one char + // but check the possibilities when it is true + if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0])) + || (ispunct(dot[-1]) && !ispunct(dot[0])) + || (isalnum(dot[-1]) && !isalnum(dot[0])))) + dot--; // move back off of next word + if (dot > text && *dot == '\n') + dot--; // stay off NL + q = dot; + } else if (strchr("H-k{", c)) { + // these operate on multi-lines backwards + q = end_line(dot); // find NL + do_cmd(c); // execute movement cmd + dot_begin(); + p = dot; + } else if (strchr("L+j}\r\n", c)) { + // these operate on multi-lines forwards + p = begin_line(dot); + do_cmd(c); // execute movement cmd + dot_end(); // find NL + q = dot; + } else { + c = 27; // error- return an ESC char + //break; + } + *start = p; + *stop = q; + if (q < p) { + *start = q; + *stop = p; + } + dot = save_dot; + return c; +} + +static int st_test(Byte * p, int type, int dir, Byte * tested) +{ + Byte c, c0, ci; + int test, inc; + + inc = dir; + c = c0 = p[0]; + ci = p[inc]; + test = 0; + + if (type == S_BEFORE_WS) { + c = ci; + test = ((!isspace(c)) || c == '\n'); + } + if (type == S_TO_WS) { + c = c0; + test = ((!isspace(c)) || c == '\n'); + } + if (type == S_OVER_WS) { + c = c0; + test = ((isspace(c))); + } + if (type == S_END_PUNCT) { + c = ci; + test = ((ispunct(c))); + } + if (type == S_END_ALNUM) { + c = ci; + test = ((isalnum(c)) || c == '_'); + } + *tested = c; + return test; +} + +static Byte *skip_thing(Byte * p, int linecnt, int dir, int type) +{ + Byte c; + + while (st_test(p, type, dir, &c)) { + // make sure we limit search to correct number of lines + if (c == '\n' && --linecnt < 1) + break; + if (dir >= 0 && p >= end - 1) + break; + if (dir < 0 && p <= text) + break; + p += dir; // move to next char + } + return p; +} + +// find matching char of pair () [] {} +static Byte *find_pair(Byte * p, Byte c) +{ + Byte match, *q; + int dir, level; + + match = ')'; + level = 1; + dir = 1; // assume forward + switch (c) { + case '(': + match = ')'; + break; + case '[': + match = ']'; + break; + case '{': + match = '}'; + break; + case ')': + match = '('; + dir = -1; + break; + case ']': + match = '['; + dir = -1; + break; + case '}': + match = '{'; + dir = -1; + break; + } + for (q = p + dir; text <= q && q < end; q += dir) { + // look for match, count levels of pairs (( )) + if (*q == c) + level++; // increase pair levels + if (*q == match) + level--; // reduce pair level + if (level == 0) + break; // found matching pair + } + if (level != 0) + q = NULL; // indicate no match + return q; +} + +#ifdef CONFIG_FEATURE_VI_SETOPTS +// show the matching char of a pair, () [] {} +static void showmatching(Byte * p) +{ + Byte *q, *save_dot; + + // we found half of a pair + q = find_pair(p, *p); // get loc of matching char + if (q == NULL) { + indicate_error('3'); // no matching char + } else { + // "q" now points to matching pair + save_dot = dot; // remember where we are + dot = q; // go to new loc + refresh(FALSE); // let the user see it + (void) mysleep(40); // give user some time + dot = save_dot; // go back to old loc + refresh(FALSE); + } +} +#endif /* CONFIG_FEATURE_VI_SETOPTS */ + +// open a hole in text[] +static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole +{ + Byte *src, *dest; + int cnt; + + if (size <= 0) + goto thm0; + src = p; + dest = p + size; + cnt = end - src; // the rest of buffer + if (memmove(dest, src, cnt) != dest) { + psbs("can't create room for new characters"); + } + memset(p, ' ', size); // clear new hole + end = end + size; // adjust the new END + file_modified++; // has the file been modified + thm0: + return p; +} + +// close a hole in text[] +static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive +{ + Byte *src, *dest; + int cnt, hole_size; + + // move forwards, from beginning + // assume p <= q + src = q + 1; + dest = p; + if (q < p) { // they are backward- swap them + src = p + 1; + dest = q; + } + hole_size = q - p + 1; + cnt = end - src; + if (src < text || src > end) + goto thd0; + if (dest < text || dest >= end) + goto thd0; + if (src >= end) + goto thd_atend; // just delete the end of the buffer + if (memmove(dest, src, cnt) != dest) { + psbs("can't delete the character"); + } + thd_atend: + end = end - hole_size; // adjust the new END + if (dest >= end) + dest = end - 1; // make sure dest in below end-1 + if (end <= text) + dest = end = text; // keep pointers valid + file_modified++; // has the file been modified + thd0: + return dest; +} + +// copy text into register, then delete text. +// if dist <= 0, do not include, or go past, a NewLine +// +static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf) +{ + Byte *p; + + // make sure start <= stop + if (start > stop) { + // they are backwards, reverse them + p = start; + start = stop; + stop = p; + } + if (dist <= 0) { + // we cannot cross NL boundaries + p = start; + if (*p == '\n') + return p; + // dont go past a NewLine + for (; p + 1 <= stop; p++) { + if (p[1] == '\n') { + stop = p; // "stop" just before NewLine + break; + } + } + } + p = start; +#ifdef CONFIG_FEATURE_VI_YANKMARK + text_yank(start, stop, YDreg); +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + if (yf == YANKDEL) { + p = text_hole_delete(start, stop); + } // delete lines + return p; +} + +static void show_help(void) +{ + puts("These features are available:" +#ifdef CONFIG_FEATURE_VI_SEARCH + "\n\tPattern searches with / and ?" +#endif /* CONFIG_FEATURE_VI_SEARCH */ +#ifdef CONFIG_FEATURE_VI_DOT_CMD + "\n\tLast command repeat with \'.\'" +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ +#ifdef CONFIG_FEATURE_VI_YANKMARK + "\n\tLine marking with 'x" + "\n\tNamed buffers with \"x" +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_READONLY + "\n\tReadonly if vi is called as \"view\"" + "\n\tReadonly with -R command line arg" +#endif /* CONFIG_FEATURE_VI_READONLY */ +#ifdef CONFIG_FEATURE_VI_SET + "\n\tSome colon mode commands with \':\'" +#endif /* CONFIG_FEATURE_VI_SET */ +#ifdef CONFIG_FEATURE_VI_SETOPTS + "\n\tSettable options with \":set\"" +#endif /* CONFIG_FEATURE_VI_SETOPTS */ +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS + "\n\tSignal catching- ^C" + "\n\tJob suspend and resume with ^Z" +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ +#ifdef CONFIG_FEATURE_VI_WIN_RESIZE + "\n\tAdapt to window re-sizes" +#endif /* CONFIG_FEATURE_VI_WIN_RESIZE */ + ); +} + +static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable +{ + Byte c, b[2]; + + b[1] = '\0'; + strcpy((char *) buf, ""); // init buf + if (strlen((char *) s) <= 0) + s = (Byte *) "(NULL)"; + for (; *s > '\0'; s++) { + int c_is_no_print; + + c = *s; + c_is_no_print = c > 127 && !Isprint(c); + if (c_is_no_print) { + strcat((char *) buf, SOn); + c = '.'; + } + if (c < ' ' || c == 127) { + strcat((char *) buf, "^"); + if(c == 127) + c = '?'; + else + c += '@'; + } + b[0] = c; + strcat((char *) buf, (char *) b); + if (c_is_no_print) + strcat((char *) buf, SOs); + if (*s == '\n') { + strcat((char *) buf, "$"); + } + } +} + +#ifdef CONFIG_FEATURE_VI_DOT_CMD +static void start_new_cmd_q(Byte c) +{ + // release old cmd + free(last_modifying_cmd); + // get buffer for new cmd + last_modifying_cmd = (Byte *) xmalloc(BUFSIZ); + memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue + // if there is a current cmd count put it in the buffer first + if (cmdcnt > 0) + sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c); + else // just save char c onto queue + last_modifying_cmd[0] = c; + adding2q = 1; +} + +static void end_cmd_q(void) +{ +#ifdef CONFIG_FEATURE_VI_YANKMARK + YDreg = 26; // go back to default Yank/Delete reg +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + adding2q = 0; + return; +} +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + +#if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME) +static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p' +{ + int cnt, i; + + i = strlen((char *) s); + p = text_hole_make(p, i); + strncpy((char *) p, (char *) s, i); + for (cnt = 0; *s != '\0'; s++) { + if (*s == '\n') + cnt++; + } +#ifdef CONFIG_FEATURE_VI_YANKMARK + psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg()); +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + return p; +} +#endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */ + +#ifdef CONFIG_FEATURE_VI_YANKMARK +static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register +{ + Byte *t; + int cnt; + + if (q < p) { // they are backwards- reverse them + t = q; + q = p; + p = t; + } + cnt = q - p + 1; + t = reg[dest]; + free(t); // if already a yank register, free it + t = (Byte *) xmalloc(cnt + 1); // get a new register + memset(t, '\0', cnt + 1); // clear new text[] + strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer + reg[dest] = t; + return p; +} + +static Byte what_reg(void) +{ + Byte c; + + c = 'D'; // default to D-reg + if (0 <= YDreg && YDreg <= 25) + c = 'a' + (Byte) YDreg; + if (YDreg == 26) + c = 'D'; + if (YDreg == 27) + c = 'U'; + return c; +} + +static void check_context(Byte cmd) +{ + // A context is defined to be "modifying text" + // Any modifying command establishes a new context. + + if (dot < context_start || dot > context_end) { + if (strchr((char *) modifying_cmds, cmd) != NULL) { + // we are trying to modify text[]- make this the current context + mark[27] = mark[26]; // move cur to prev + mark[26] = dot; // move local to cur + context_start = prev_line(prev_line(dot)); + context_end = next_line(next_line(dot)); + //loiter= start_loiter= now; + } + } + return; +} + +static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context +{ + Byte *tmp; + + // the current context is in mark[26] + // the previous context is in mark[27] + // only swap context if other context is valid + if (text <= mark[27] && mark[27] <= end - 1) { + tmp = mark[27]; + mark[27] = mark[26]; + mark[26] = tmp; + p = mark[26]; // where we are going- previous context + context_start = prev_line(prev_line(prev_line(p))); + context_end = next_line(next_line(next_line(p))); + } + return p; +} +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + +static int isblnk(Byte c) // is the char a blank or tab +{ + return (c == ' ' || c == '\t'); +} + +//----- Set terminal attributes -------------------------------- +static void rawmode(void) +{ + tcgetattr(0, &term_orig); + term_vi = term_orig; + term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's + term_vi.c_iflag &= (~IXON & ~ICRNL); + term_vi.c_oflag &= (~ONLCR); + term_vi.c_cc[VMIN] = 1; + term_vi.c_cc[VTIME] = 0; + erase_char = term_vi.c_cc[VERASE]; + tcsetattr(0, TCSANOW, &term_vi); +} + +static void cookmode(void) +{ + fflush(stdout); + tcsetattr(0, TCSANOW, &term_orig); +} + +//----- Come here when we get a window resize signal --------- +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS +static void winch_sig(int sig ATTRIBUTE_UNUSED) +{ + signal(SIGWINCH, winch_sig); + if (ENABLE_FEATURE_VI_WIN_RESIZE) + get_terminal_width_height(0, &columns, &rows); + new_screen(rows, columns); // get memory for virtual screen + redraw(TRUE); // re-draw the screen +} + +//----- Come here when we get a continue signal ------------------- +static void cont_sig(int sig ATTRIBUTE_UNUSED) +{ + rawmode(); // terminal to "raw" + last_status_cksum = 0; // force status update + redraw(TRUE); // re-draw the screen + + signal(SIGTSTP, suspend_sig); + signal(SIGCONT, SIG_DFL); + kill(my_pid, SIGCONT); +} + +//----- Come here when we get a Suspend signal ------------------- +static void suspend_sig(int sig ATTRIBUTE_UNUSED) +{ + place_cursor(rows - 1, 0, FALSE); // go to bottom of screen + clear_to_eol(); // Erase to end of line + cookmode(); // terminal to "cooked" + + signal(SIGCONT, cont_sig); + signal(SIGTSTP, SIG_DFL); + kill(my_pid, SIGTSTP); +} + +//----- Come here when we get a signal --------------------------- +static void catch_sig(int sig) +{ + signal(SIGINT, catch_sig); + if(sig) + longjmp(restart, sig); +} +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ + +static int mysleep(int hund) // sleep for 'h' 1/100 seconds +{ + // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000 + fflush(stdout); + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 0; + tv.tv_usec = hund * 10000; + select(1, &rfds, NULL, NULL, &tv); + return FD_ISSET(0, &rfds); +} + +#define readbuffer bb_common_bufsiz1 + +static int readed_for_parse; + +//----- IO Routines -------------------------------------------- +static Byte readit(void) // read (maybe cursor) key from stdin +{ + Byte c; + int n; + struct esc_cmds { + const char *seq; + Byte val; + }; + + static const struct esc_cmds esccmds[] = { + {"OA", (Byte) VI_K_UP}, // cursor key Up + {"OB", (Byte) VI_K_DOWN}, // cursor key Down + {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right + {"OD", (Byte) VI_K_LEFT}, // cursor key Left + {"OH", (Byte) VI_K_HOME}, // Cursor Key Home + {"OF", (Byte) VI_K_END}, // Cursor Key End + {"[A", (Byte) VI_K_UP}, // cursor key Up + {"[B", (Byte) VI_K_DOWN}, // cursor key Down + {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right + {"[D", (Byte) VI_K_LEFT}, // cursor key Left + {"[H", (Byte) VI_K_HOME}, // Cursor Key Home + {"[F", (Byte) VI_K_END}, // Cursor Key End + {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home + {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert + {"[4~", (Byte) VI_K_END}, // Cursor Key End + {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up + {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down + {"OP", (Byte) VI_K_FUN1}, // Function Key F1 + {"OQ", (Byte) VI_K_FUN2}, // Function Key F2 + {"OR", (Byte) VI_K_FUN3}, // Function Key F3 + {"OS", (Byte) VI_K_FUN4}, // Function Key F4 + {"[15~", (Byte) VI_K_FUN5}, // Function Key F5 + {"[17~", (Byte) VI_K_FUN6}, // Function Key F6 + {"[18~", (Byte) VI_K_FUN7}, // Function Key F7 + {"[19~", (Byte) VI_K_FUN8}, // Function Key F8 + {"[20~", (Byte) VI_K_FUN9}, // Function Key F9 + {"[21~", (Byte) VI_K_FUN10}, // Function Key F10 + {"[23~", (Byte) VI_K_FUN11}, // Function Key F11 + {"[24~", (Byte) VI_K_FUN12}, // Function Key F12 + {"[11~", (Byte) VI_K_FUN1}, // Function Key F1 + {"[12~", (Byte) VI_K_FUN2}, // Function Key F2 + {"[13~", (Byte) VI_K_FUN3}, // Function Key F3 + {"[14~", (Byte) VI_K_FUN4}, // Function Key F4 + }; + +#define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds)) + + (void) alarm(0); // turn alarm OFF while we wait for input + fflush(stdout); + n = readed_for_parse; + // get input from User- are there already input chars in Q? + if (n <= 0) { + ri0: + // the Q is empty, wait for a typed char + n = read(0, readbuffer, BUFSIZ - 1); + if (n < 0) { + if (errno == EINTR) + goto ri0; // interrupted sys call + if (errno == EBADF) + editing = 0; + if (errno == EFAULT) + editing = 0; + if (errno == EINVAL) + editing = 0; + if (errno == EIO) + editing = 0; + errno = 0; + } + if(n <= 0) + return 0; // error + if (readbuffer[0] == 27) { + // This is an ESC char. Is this Esc sequence? + // Could be bare Esc key. See if there are any + // more chars to read after the ESC. This would + // be a Function or Cursor Key sequence. + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000 + + // keep reading while there are input chars and room in buffer + while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) { + // read the rest of the ESC string + int r = read(0, (void *) (readbuffer + n), BUFSIZ - n); + if (r > 0) { + n += r; + } + } + } + readed_for_parse = n; + } + c = readbuffer[0]; + if(c == 27 && n > 1) { + // Maybe cursor or function key? + const struct esc_cmds *eindex; + + for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) { + int cnt = strlen(eindex->seq); + + if(n <= cnt) + continue; + if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt)) + continue; + // is a Cursor key- put derived value back into Q + c = eindex->val; + // for squeeze out the ESC sequence + n = cnt + 1; + break; + } + if(eindex == &esccmds[ESCCMDS_COUNT]) { + /* defined ESC sequence not found, set only one ESC */ + n = 1; + } + } else { + n = 1; + } + // remove key sequence from Q + readed_for_parse -= n; + memmove(readbuffer, readbuffer + n, BUFSIZ - n); + (void) alarm(3); // we are done waiting for input, turn alarm ON + return c; +} + +//----- IO Routines -------------------------------------------- +static Byte get_one_char(void) +{ + static Byte c; + +#ifdef CONFIG_FEATURE_VI_DOT_CMD + // ! adding2q && ioq == 0 read() + // ! adding2q && ioq != 0 *ioq + // adding2q *last_modifying_cmd= read() + if (!adding2q) { + // we are not adding to the q. + // but, we may be reading from a q + if (ioq == 0) { + // there is no current q, read from STDIN + c = readit(); // get the users input + } else { + // there is a queue to get chars from first + c = *ioq++; + if (c == '\0') { + // the end of the q, read from STDIN + free(ioq_start); + ioq_start = ioq = 0; + c = readit(); // get the users input + } + } + } else { + // adding STDIN chars to q + c = readit(); // get the users input + if (last_modifying_cmd != 0) { + int len = strlen((char *) last_modifying_cmd); + if (len + 1 >= BUFSIZ) { + psbs("last_modifying_cmd overrun"); + } else { + // add new char to q + last_modifying_cmd[len] = c; + } + } + } +#else /* CONFIG_FEATURE_VI_DOT_CMD */ + c = readit(); // get the users input +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + return c; // return the char, where ever it came from +} + +static Byte *get_input_line(Byte * prompt) // get input line- use "status line" +{ + Byte buf[BUFSIZ]; + Byte c; + int i; + static Byte *obufp = NULL; + + strcpy((char *) buf, (char *) prompt); + last_status_cksum = 0; // force status update + place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen + clear_to_eol(); // clear the line + write1((char *) prompt); // write out the :, /, or ? prompt + + for (i = strlen((char *) buf); i < BUFSIZ;) { + c = get_one_char(); // read user input + if (c == '\n' || c == '\r' || c == 27) + break; // is this end of input + if (c == erase_char || c == 8 || c == 127) { + // user wants to erase prev char + i--; // backup to prev char + buf[i] = '\0'; // erase the char + buf[i + 1] = '\0'; // null terminate buffer + write1("\b \b"); // erase char on screen + if (i <= 0) { // user backs up before b-o-l, exit + break; + } + } else { + buf[i] = c; // save char in buffer + buf[i + 1] = '\0'; // make sure buffer is null terminated + putchar(c); // echo the char back to user + i++; + } + } + refresh(FALSE); + free(obufp); + obufp = (Byte *) xstrdup((char *) buf); + return obufp; +} + +static int file_size(const Byte * fn) // what is the byte size of "fn" +{ + struct stat st_buf; + int cnt, sr; + + if (fn == 0 || strlen((char *)fn) <= 0) + return -1; + cnt = -1; + sr = stat((char *) fn, &st_buf); // see if file exists + if (sr >= 0) { + cnt = (int) st_buf.st_size; + } + return cnt; +} + +static int file_insert(Byte * fn, Byte * p, int size) +{ + int fd, cnt; + + cnt = -1; +#ifdef CONFIG_FEATURE_VI_READONLY + readonly = FALSE; +#endif /* CONFIG_FEATURE_VI_READONLY */ + if (fn == 0 || strlen((char*) fn) <= 0) { + psbs("No filename given"); + goto fi0; + } + if (size == 0) { + // OK- this is just a no-op + cnt = 0; + goto fi0; + } + if (size < 0) { + psbs("Trying to insert a negative number (%d) of characters", size); + goto fi0; + } + if (p < text || p > end) { + psbs("Trying to insert file outside of memory"); + goto fi0; + } + + // see if we can open the file +#ifdef CONFIG_FEATURE_VI_READONLY + if (vi_readonly) goto fi1; // do not try write-mode +#endif + fd = open((char *) fn, O_RDWR); // assume read & write + if (fd < 0) { + // could not open for writing- maybe file is read only +#ifdef CONFIG_FEATURE_VI_READONLY + fi1: +#endif + fd = open((char *) fn, O_RDONLY); // try read-only + if (fd < 0) { + psbs("\"%s\" %s", fn, "cannot open file"); + goto fi0; + } +#ifdef CONFIG_FEATURE_VI_READONLY + // got the file- read-only + readonly = TRUE; +#endif /* CONFIG_FEATURE_VI_READONLY */ + } + p = text_hole_make(p, size); + cnt = read(fd, p, size); + close(fd); + if (cnt < 0) { + cnt = -1; + p = text_hole_delete(p, p + size - 1); // un-do buffer insert + psbs("cannot read file \"%s\"", fn); + } else if (cnt < size) { + // There was a partial read, shrink unused space text[] + p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert + psbs("cannot read all of file \"%s\"", fn); + } + if (cnt >= size) + file_modified++; + fi0: + return cnt; +} + +static int file_write(Byte * fn, Byte * first, Byte * last) +{ + int fd, cnt, charcnt; + + if (fn == 0) { + psbs("No current filename"); + return -2; + } + charcnt = 0; + // FIXIT- use the correct umask() + fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664); + if (fd < 0) + return -1; + cnt = last - first + 1; + charcnt = write(fd, first, cnt); + if (charcnt == cnt) { + // good write + //file_modified= FALSE; // the file has not been modified + } else { + charcnt = 0; + } + close(fd); + return charcnt; +} + +//----- Terminal Drawing --------------------------------------- +// The terminal is made up of 'rows' line of 'columns' columns. +// classically this would be 24 x 80. +// screen coordinates +// 0,0 ... 0,79 +// 1,0 ... 1,79 +// . ... . +// . ... . +// 22,0 ... 22,79 +// 23,0 ... 23,79 status line +// + +//----- Move the cursor to row x col (count from 0, not 1) ------- +static void place_cursor(int row, int col, int opti) +{ + char cm1[BUFSIZ]; + char *cm; +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + char cm2[BUFSIZ]; + Byte *screenp; + // char cm3[BUFSIZ]; + int Rrow= last_row; +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + + memset(cm1, '\0', BUFSIZ - 1); // clear the buffer + + if (row < 0) row = 0; + if (row >= rows) row = rows - 1; + if (col < 0) col = 0; + if (col >= columns) col = columns - 1; + + //----- 1. Try the standard terminal ESC sequence + sprintf((char *) cm1, CMrc, row + 1, col + 1); + cm= cm1; + if (! opti) goto pc0; + +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + //----- find the minimum # of chars to move cursor ------------- + //----- 2. Try moving with discreet chars (Newline, [back]space, ...) + memset(cm2, '\0', BUFSIZ - 1); // clear the buffer + + // move to the correct row + while (row < Rrow) { + // the cursor has to move up + strcat(cm2, CMup); + Rrow--; + } + while (row > Rrow) { + // the cursor has to move down + strcat(cm2, CMdown); + Rrow++; + } + + // now move to the correct column + strcat(cm2, "\r"); // start at col 0 + // just send out orignal source char to get to correct place + screenp = &screen[row * columns]; // start of screen line + strncat(cm2, (char* )screenp, col); + + //----- 3. Try some other way of moving cursor + //--------------------------------------------- + + // pick the shortest cursor motion to send out + cm= cm1; + if (strlen(cm2) < strlen(cm)) { + cm= cm2; + } /* else if (strlen(cm3) < strlen(cm)) { + cm= cm3; + } */ +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + pc0: + write1(cm); // move the cursor +} + +//----- Erase from cursor to end of line ----------------------- +static void clear_to_eol(void) +{ + write1(Ceol); // Erase from cursor to end of line +} + +//----- Erase from cursor to end of screen ----------------------- +static void clear_to_eos(void) +{ + write1(Ceos); // Erase from cursor to end of screen +} + +//----- Start standout mode ------------------------------------ +static void standout_start(void) // send "start reverse video" sequence +{ + write1(SOs); // Start reverse video mode +} + +//----- End standout mode -------------------------------------- +static void standout_end(void) // send "end reverse video" sequence +{ + write1(SOn); // End reverse video mode +} + +//----- Flash the screen -------------------------------------- +static void flash(int h) +{ + standout_start(); // send "start reverse video" sequence + redraw(TRUE); + (void) mysleep(h); + standout_end(); // send "end reverse video" sequence + redraw(TRUE); +} + +static void Indicate_Error(void) +{ +#ifdef CONFIG_FEATURE_VI_CRASHME + if (crashme > 0) + return; // generate a random command +#endif /* CONFIG_FEATURE_VI_CRASHME */ + if (!err_method) { + write1(bell); // send out a bell character + } else { + flash(10); + } +} + +//----- Screen[] Routines -------------------------------------- +//----- Erase the Screen[] memory ------------------------------ +static void screen_erase(void) +{ + memset(screen, ' ', screensize); // clear new screen +} + +static int bufsum(unsigned char *buf, int count) +{ + int sum = 0; + unsigned char *e = buf + count; + while (buf < e) + sum += *buf++; + return sum; +} + +//----- Draw the status line at bottom of the screen ------------- +static void show_status_line(void) +{ + int cnt = 0, cksum = 0; + + // either we already have an error or status message, or we + // create one. + if (!have_status_msg) { + cnt = format_edit_status(); + cksum = bufsum(status_buffer, cnt); + } + if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) { + last_status_cksum= cksum; // remember if we have seen this line + place_cursor(rows - 1, 0, FALSE); // put cursor on status line + write1((char*)status_buffer); + clear_to_eol(); + if (have_status_msg) { + if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) > + (columns - 1) ) { + have_status_msg = 0; + Hit_Return(); + } + have_status_msg = 0; + } + place_cursor(crow, ccol, FALSE); // put cursor back in correct place + } + fflush(stdout); +} + +//----- format the status buffer, the bottom line of screen ------ +// format status buffer, with STANDOUT mode +static void psbs(const char *format, ...) +{ + va_list args; + + va_start(args, format); + strcpy((char *) status_buffer, SOs); // Terminal standout mode on + vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args); + strcat((char *) status_buffer, SOn); // Terminal standout mode off + va_end(args); + + have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2; + + return; +} + +// format status buffer +static void psb(const char *format, ...) +{ + va_list args; + + va_start(args, format); + vsprintf((char *) status_buffer, format, args); + va_end(args); + + have_status_msg = 1; + + return; +} + +static void ni(Byte * s) // display messages +{ + Byte buf[BUFSIZ]; + + print_literal(buf, s); + psbs("\'%s\' is not implemented", buf); +} + +static int format_edit_status(void) // show file status on status line +{ + int cur, percent, ret, trunc_at; + static int tot; + + // file_modified is now a counter rather than a flag. this + // helps reduce the amount of line counting we need to do. + // (this will cause a mis-reporting of modified status + // once every MAXINT editing operations.) + + // it would be nice to do a similar optimization here -- if + // we haven't done a motion that could have changed which line + // we're on, then we shouldn't have to do this count_lines() + cur = count_lines(text, dot); + + // reduce counting -- the total lines can't have + // changed if we haven't done any edits. + if (file_modified != last_file_modified) { + tot = cur + count_lines(dot, end - 1) - 1; + last_file_modified = file_modified; + } + + // current line percent + // ------------- ~~ ---------- + // total lines 100 + if (tot > 0) { + percent = (100 * cur) / tot; + } else { + cur = tot = 0; + percent = 100; + } + + trunc_at = columns < STATUS_BUFFER_LEN-1 ? + columns : STATUS_BUFFER_LEN-1; + + ret = snprintf((char *) status_buffer, trunc_at+1, +#ifdef CONFIG_FEATURE_VI_READONLY + "%c %s%s%s %d/%d %d%%", +#else + "%c %s%s %d/%d %d%%", +#endif + (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'), + (cfn != 0 ? (char *) cfn : "No file"), +#ifdef CONFIG_FEATURE_VI_READONLY + ((vi_readonly || readonly) ? " [Read-only]" : ""), +#endif + (file_modified ? " [modified]" : ""), + cur, tot, percent); + + if (ret >= 0 && ret < trunc_at) + return ret; /* it all fit */ + + return trunc_at; /* had to truncate */ +} + +//----- Force refresh of all Lines ----------------------------- +static void redraw(int full_screen) +{ + place_cursor(0, 0, FALSE); // put cursor in correct place + clear_to_eos(); // tel terminal to erase display + screen_erase(); // erase the internal screen buffer + last_status_cksum = 0; // force status update + refresh(full_screen); // this will redraw the entire display + show_status_line(); +} + +//----- Format a text[] line into a buffer --------------------- +static void format_line(Byte *dest, Byte *src, int li) +{ + int co; + Byte c; + + for (co= 0; co < MAX_SCR_COLS; co++) { + c= ' '; // assume blank + if (li > 0 && co == 0) { + c = '~'; // not first line, assume Tilde + } + // are there chars in text[] and have we gone past the end + if (text < end && src < end) { + c = *src++; + } + if (c == '\n') + break; + if (c > 127 && !Isprint(c)) { + c = '.'; + } + if (c < ' ' || c == 127) { + if (c == '\t') { + c = ' '; + // co % 8 != 7 + for (; (co % tabstop) != (tabstop - 1); co++) { + dest[co] = c; + } + } else { + dest[co++] = '^'; + if(c == 127) + c = '?'; + else + c += '@'; // make it visible + } + } + // the co++ is done here so that the column will + // not be overwritten when we blank-out the rest of line + dest[co] = c; + if (src >= end) + break; + } +} + +//----- Refresh the changed screen lines ----------------------- +// Copy the source line from text[] into the buffer and note +// if the current screenline is different from the new buffer. +// If they differ then that line needs redrawing on the terminal. +// +static void refresh(int full_screen) +{ + static int old_offset; + int li, changed; + Byte buf[MAX_SCR_COLS]; + Byte *tp, *sp; // pointer into text[] and screen[] +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + int last_li= -2; // last line that changed- for optimizing cursor movement +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + + if (ENABLE_FEATURE_VI_WIN_RESIZE) + get_terminal_width_height(0, &columns, &rows); + sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot") + tp = screenbegin; // index into text[] of top line + + // compare text[] to screen[] and mark screen[] lines that need updating + for (li = 0; li < rows - 1; li++) { + int cs, ce; // column start & end + memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer + buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer + // format current text line into buf + format_line(buf, tp, li); + + // skip to the end of the current text[] line + while (tp < end && *tp++ != '\n') /*no-op*/ ; + + // see if there are any changes between vitual screen and buf + changed = FALSE; // assume no change + cs= 0; + ce= columns-1; + sp = &screen[li * columns]; // start of screen line + if (full_screen) { + // force re-draw of every single column from 0 - columns-1 + goto re0; + } + // compare newly formatted buffer with virtual screen + // look forward for first difference between buf and screen + for ( ; cs <= ce; cs++) { + if (buf[cs + offset] != sp[cs]) { + changed = TRUE; // mark for redraw + break; + } + } + + // look backward for last difference between buf and screen + for ( ; ce >= cs; ce--) { + if (buf[ce + offset] != sp[ce]) { + changed = TRUE; // mark for redraw + break; + } + } + // now, cs is index of first diff, and ce is index of last diff + + // if horz offset has changed, force a redraw + if (offset != old_offset) { + re0: + changed = TRUE; + } + + // make a sanity check of columns indexes + if (cs < 0) cs= 0; + if (ce > columns-1) ce= columns-1; + if (cs > ce) { cs= 0; ce= columns-1; } + // is there a change between vitual screen and buf + if (changed) { + // copy changed part of buffer to virtual screen + memmove(sp+cs, buf+(cs+offset), ce-cs+1); + + // move cursor to column of first change + if (offset != old_offset) { + // opti_cur_move is still too stupid + // to handle offsets correctly + place_cursor(li, cs, FALSE); + } else { +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + // if this just the next line + // try to optimize cursor movement + // otherwise, use standard ESC sequence + place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE); + last_li= li; +#else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + place_cursor(li, cs, FALSE); // use standard ESC sequence +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + } + + // write line out to terminal + { + int nic = ce-cs+1; + char *out = (char*)sp+cs; + + while(nic-- > 0) { + putchar(*out); + out++; + } + } +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + last_row = li; +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + } + } + +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE); + last_row = crow; +#else + place_cursor(crow, ccol, FALSE); +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + + if (offset != old_offset) + old_offset = offset; +} + +//--------------------------------------------------------------------- +//----- the Ascii Chart ----------------------------------------------- +// +// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel +// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si +// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb +// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us +// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 ' +// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f / +// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7 +// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ? +// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G +// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O +// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W +// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _ +// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g +// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o +// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w +// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del +//--------------------------------------------------------------------- + +//----- Execute a Vi Command ----------------------------------- +static void do_cmd(Byte c) +{ + Byte c1, *p, *q, *msg, buf[9], *save_dot; + int cnt, i, j, dir, yf; + + c1 = c; // quiet the compiler + cnt = yf = dir = 0; // quiet the compiler + p = q = save_dot = msg = buf; // quiet the compiler + memset(buf, '\0', 9); // clear buf + + show_status_line(); + + /* if this is a cursor key, skip these checks */ + switch (c) { + case VI_K_UP: + case VI_K_DOWN: + case VI_K_LEFT: + case VI_K_RIGHT: + case VI_K_HOME: + case VI_K_END: + case VI_K_PAGEUP: + case VI_K_PAGEDOWN: + goto key_cmd_mode; + } + + if (cmd_mode == 2) { + // flip-flop Insert/Replace mode + if (c == VI_K_INSERT) goto dc_i; + // we are 'R'eplacing the current *dot with new char + if (*dot == '\n') { + // don't Replace past E-o-l + cmd_mode = 1; // convert to insert + } else { + if (1 <= c || Isprint(c)) { + if (c != 27) + dot = yank_delete(dot, dot, 0, YANKDEL); // delete char + dot = char_insert(dot, c); // insert new char + } + goto dc1; + } + } + if (cmd_mode == 1) { + // hitting "Insert" twice means "R" replace mode + if (c == VI_K_INSERT) goto dc5; + // insert the char c at "dot" + if (1 <= c || Isprint(c)) { + dot = char_insert(dot, c); + } + goto dc1; + } + +key_cmd_mode: + switch (c) { + //case 0x01: // soh + //case 0x09: // ht + //case 0x0b: // vt + //case 0x0e: // so + //case 0x0f: // si + //case 0x10: // dle + //case 0x11: // dc1 + //case 0x13: // dc3 +#ifdef CONFIG_FEATURE_VI_CRASHME + case 0x14: // dc4 ctrl-T + crashme = (crashme == 0) ? 1 : 0; + break; +#endif /* CONFIG_FEATURE_VI_CRASHME */ + //case 0x16: // syn + //case 0x17: // etb + //case 0x18: // can + //case 0x1c: // fs + //case 0x1d: // gs + //case 0x1e: // rs + //case 0x1f: // us + //case '!': // !- + //case '#': // #- + //case '&': // &- + //case '(': // (- + //case ')': // )- + //case '*': // *- + //case ',': // ,- + //case '=': // =- + //case '@': // @- + //case 'F': // F- + //case 'K': // K- + //case 'Q': // Q- + //case 'S': // S- + //case 'T': // T- + //case 'V': // V- + //case '[': // [- + //case '\\': // \- + //case ']': // ]- + //case '_': // _- + //case '`': // `- + //case 'g': // g- + //case 'u': // u- FIXME- there is no undo + //case 'v': // v- + default: // unrecognised command + buf[0] = c; + buf[1] = '\0'; + if (c < ' ') { + buf[0] = '^'; + buf[1] = c + '@'; + buf[2] = '\0'; + } + ni((Byte *) buf); + end_cmd_q(); // stop adding to q + case 0x00: // nul- ignore + break; + case 2: // ctrl-B scroll up full screen + case VI_K_PAGEUP: // Cursor Key Page Up + dot_scroll(rows - 2, -1); + break; +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS + case 0x03: // ctrl-C interrupt + longjmp(restart, 1); + break; + case 26: // ctrl-Z suspend + suspend_sig(SIGTSTP); + break; +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ + case 4: // ctrl-D scroll down half screen + dot_scroll((rows - 2) / 2, 1); + break; + case 5: // ctrl-E scroll down one line + dot_scroll(1, 1); + break; + case 6: // ctrl-F scroll down full screen + case VI_K_PAGEDOWN: // Cursor Key Page Down + dot_scroll(rows - 2, 1); + break; + case 7: // ctrl-G show current status + last_status_cksum = 0; // force status update + break; + case 'h': // h- move left + case VI_K_LEFT: // cursor key Left + case 8: // ctrl-H- move left (This may be ERASE char) + case 127: // DEL- move left (This may be ERASE char) + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_left(); + break; + case 10: // Newline ^J + case 'j': // j- goto next line, same col + case VI_K_DOWN: // cursor key Down + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_next(); // go to next B-o-l + dot = move_to_col(dot, ccol + offset); // try stay in same col + break; + case 12: // ctrl-L force redraw whole screen + case 18: // ctrl-R force redraw + place_cursor(0, 0, FALSE); // put cursor in correct place + clear_to_eos(); // tel terminal to erase display + (void) mysleep(10); + screen_erase(); // erase the internal screen buffer + last_status_cksum = 0; // force status update + refresh(TRUE); // this will redraw the entire display + break; + case 13: // Carriage Return ^M + case '+': // +- goto next line + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_next(); + dot_skip_over_ws(); + break; + case 21: // ctrl-U scroll up half screen + dot_scroll((rows - 2) / 2, -1); + break; + case 25: // ctrl-Y scroll up one line + dot_scroll(1, -1); + break; + case 27: // esc + if (cmd_mode == 0) + indicate_error(c); + cmd_mode = 0; // stop insrting + end_cmd_q(); + last_status_cksum = 0; // force status update + break; + case ' ': // move right + case 'l': // move right + case VI_K_RIGHT: // Cursor Key Right + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_right(); + break; +#ifdef CONFIG_FEATURE_VI_YANKMARK + case '"': // "- name a register to use for Delete/Yank + c1 = get_one_char(); + c1 = tolower(c1); + if (islower(c1)) { + YDreg = c1 - 'a'; + } else { + indicate_error(c); + } + break; + case '\'': // '- goto a specific mark + c1 = get_one_char(); + c1 = tolower(c1); + if (islower(c1)) { + c1 = c1 - 'a'; + // get the b-o-l + q = mark[(int) c1]; + if (text <= q && q < end) { + dot = q; + dot_begin(); // go to B-o-l + dot_skip_over_ws(); + } + } else if (c1 == '\'') { // goto previous context + dot = swap_context(dot); // swap current and previous context + dot_begin(); // go to B-o-l + dot_skip_over_ws(); + } else { + indicate_error(c); + } + break; + case 'm': // m- Mark a line + // this is really stupid. If there are any inserts or deletes + // between text[0] and dot then this mark will not point to the + // correct location! It could be off by many lines! + // Well..., at least its quick and dirty. + c1 = get_one_char(); + c1 = tolower(c1); + if (islower(c1)) { + c1 = c1 - 'a'; + // remember the line + mark[(int) c1] = dot; + } else { + indicate_error(c); + } + break; + case 'P': // P- Put register before + case 'p': // p- put register after + p = reg[YDreg]; + if (p == 0) { + psbs("Nothing in register %c", what_reg()); + break; + } + // are we putting whole lines or strings + if (strchr((char *) p, '\n') != NULL) { + if (c == 'P') { + dot_begin(); // putting lines- Put above + } + if (c == 'p') { + // are we putting after very last line? + if (end_line(dot) == (end - 1)) { + dot = end; // force dot to end of text[] + } else { + dot_next(); // next line, then put before + } + } + } else { + if (c == 'p') + dot_right(); // move to right, can move to NL + } + dot = string_insert(dot, p); // insert the string + end_cmd_q(); // stop adding to q + break; + case 'U': // U- Undo; replace current line with original version + if (reg[Ureg] != 0) { + p = begin_line(dot); + q = end_line(dot); + p = text_hole_delete(p, q); // delete cur line + p = string_insert(p, reg[Ureg]); // insert orig line + dot = p; + dot_skip_over_ws(); + } + break; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + case '$': // $- goto end of line + case VI_K_END: // Cursor Key End + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot = end_line(dot); + break; + case '%': // %- find matching char of pair () [] {} + for (q = dot; q < end && *q != '\n'; q++) { + if (strchr("()[]{}", *q) != NULL) { + // we found half of a pair + p = find_pair(q, *q); + if (p == NULL) { + indicate_error(c); + } else { + dot = p; + } + break; + } + } + if (*q == '\n') + indicate_error(c); + break; + case 'f': // f- forward to a user specified char + last_forward_char = get_one_char(); // get the search char + // + // dont separate these two commands. 'f' depends on ';' + // + //**** fall thru to ... ';' + case ';': // ;- look at rest of line for last forward char + if (cmdcnt-- > 1) { + do_cmd(';'); + } // repeat cnt + if (last_forward_char == 0) break; + q = dot + 1; + while (q < end - 1 && *q != '\n' && *q != last_forward_char) { + q++; + } + if (*q == last_forward_char) + dot = q; + break; + case '-': // -- goto prev line + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_prev(); + dot_skip_over_ws(); + break; +#ifdef CONFIG_FEATURE_VI_DOT_CMD + case '.': // .- repeat the last modifying command + // Stuff the last_modifying_cmd back into stdin + // and let it be re-executed. + if (last_modifying_cmd != 0) { + ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd); + } + break; +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ +#ifdef CONFIG_FEATURE_VI_SEARCH + case '?': // /- search for a pattern + case '/': // /- search for a pattern + buf[0] = c; + buf[1] = '\0'; + q = get_input_line(buf); // get input line- use "status line" + if (strlen((char *) q) == 1) + goto dc3; // if no pat re-use old pat + if (strlen((char *) q) > 1) { // new pat- save it and find + // there is a new pat + free(last_search_pattern); + last_search_pattern = (Byte *) xstrdup((char *) q); + goto dc3; // now find the pattern + } + // user changed mind and erased the "/"- do nothing + break; + case 'N': // N- backward search for last pattern + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dir = BACK; // assume BACKWARD search + p = dot - 1; + if (last_search_pattern[0] == '?') { + dir = FORWARD; + p = dot + 1; + } + goto dc4; // now search for pattern + break; + case 'n': // n- repeat search for last pattern + // search rest of text[] starting at next char + // if search fails return orignal "p" not the "p+1" address + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dc3: + if (last_search_pattern == 0) { + msg = (Byte *) "No previous regular expression"; + goto dc2; + } + if (last_search_pattern[0] == '/') { + dir = FORWARD; // assume FORWARD search + p = dot + 1; + } + if (last_search_pattern[0] == '?') { + dir = BACK; + p = dot - 1; + } + dc4: + q = char_search(p, last_search_pattern + 1, dir, FULL); + if (q != NULL) { + dot = q; // good search, update "dot" + msg = (Byte *) ""; + goto dc2; + } + // no pattern found between "dot" and "end"- continue at top + p = text; + if (dir == BACK) { + p = end - 1; + } + q = char_search(p, last_search_pattern + 1, dir, FULL); + if (q != NULL) { // found something + dot = q; // found new pattern- goto it + msg = (Byte *) "search hit BOTTOM, continuing at TOP"; + if (dir == BACK) { + msg = (Byte *) "search hit TOP, continuing at BOTTOM"; + } + } else { + msg = (Byte *) "Pattern not found"; + } + dc2: + if (*msg) psbs("%s", msg); + break; + case '{': // {- move backward paragraph + q = char_search(dot, (Byte *) "\n\n", BACK, FULL); + if (q != NULL) { // found blank line + dot = next_line(q); // move to next blank line + } + break; + case '}': // }- move forward paragraph + q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL); + if (q != NULL) { // found blank line + dot = next_line(q); // move to next blank line + } + break; +#endif /* CONFIG_FEATURE_VI_SEARCH */ + case '0': // 0- goto begining of line + case '1': // 1- + case '2': // 2- + case '3': // 3- + case '4': // 4- + case '5': // 5- + case '6': // 6- + case '7': // 7- + case '8': // 8- + case '9': // 9- + if (c == '0' && cmdcnt < 1) { + dot_begin(); // this was a standalone zero + } else { + cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number + } + break; + case ':': // :- the colon mode commands + p = get_input_line((Byte *) ":"); // get input line- use "status line" +#ifdef CONFIG_FEATURE_VI_COLON + colon(p); // execute the command +#else /* CONFIG_FEATURE_VI_COLON */ + if (*p == ':') + p++; // move past the ':' + cnt = strlen((char *) p); + if (cnt <= 0) + break; + if (strncasecmp((char *) p, "quit", cnt) == 0 || + strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines + if (file_modified && p[1] != '!') { + psbs("No write since last change (:quit! overrides)"); + } else { + editing = 0; + } + } else if (strncasecmp((char *) p, "write", cnt) == 0 + || strncasecmp((char *) p, "wq", cnt) == 0 + || strncasecmp((char *) p, "wn", cnt) == 0 + || strncasecmp((char *) p, "x", cnt) == 0) { + cnt = file_write(cfn, text, end - 1); + if (cnt < 0) { + if (cnt == -1) + psbs("Write error: %s", strerror(errno)); + } else { + file_modified = 0; + last_file_modified = -1; + psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt); + if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' || + p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') { + editing = 0; + } + } + } else if (strncasecmp((char *) p, "file", cnt) == 0 ) { + last_status_cksum = 0; // force status update + } else if (sscanf((char *) p, "%d", &j) > 0) { + dot = find_line(j); // go to line # j + dot_skip_over_ws(); + } else { // unrecognised cmd + ni((Byte *) p); + } +#endif /* CONFIG_FEATURE_VI_COLON */ + break; + case '<': // <- Left shift something + case '>': // >- Right shift something + cnt = count_lines(text, dot); // remember what line we are on + c1 = get_one_char(); // get the type of thing to delete + find_range(&p, &q, c1); + (void) yank_delete(p, q, 1, YANKONLY); // save copy before change + p = begin_line(p); + q = end_line(q); + i = count_lines(p, q); // # of lines we are shifting + for ( ; i > 0; i--, p = next_line(p)) { + if (c == '<') { + // shift left- remove tab or 8 spaces + if (*p == '\t') { + // shrink buffer 1 char + (void) text_hole_delete(p, p); + } else if (*p == ' ') { + // we should be calculating columns, not just SPACE + for (j = 0; *p == ' ' && j < tabstop; j++) { + (void) text_hole_delete(p, p); + } + } + } else if (c == '>') { + // shift right -- add tab or 8 spaces + (void) char_insert(p, '\t'); + } + } + dot = find_line(cnt); // what line were we on + dot_skip_over_ws(); + end_cmd_q(); // stop adding to q + break; + case 'A': // A- append at e-o-l + dot_end(); // go to e-o-l + //**** fall thru to ... 'a' + case 'a': // a- append after current char + if (*dot != '\n') + dot++; + goto dc_i; + break; + case 'B': // B- back a blank-delimited Word + case 'E': // E- end of a blank-delimited word + case 'W': // W- forward a blank-delimited word + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dir = FORWARD; + if (c == 'B') + dir = BACK; + if (c == 'W' || isspace(dot[dir])) { + dot = skip_thing(dot, 1, dir, S_TO_WS); + dot = skip_thing(dot, 2, dir, S_OVER_WS); + } + if (c != 'W') + dot = skip_thing(dot, 1, dir, S_BEFORE_WS); + break; + case 'C': // C- Change to e-o-l + case 'D': // D- delete to e-o-l + save_dot = dot; + dot = dollar_line(dot); // move to before NL + // copy text into a register and delete + dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l + if (c == 'C') + goto dc_i; // start inserting +#ifdef CONFIG_FEATURE_VI_DOT_CMD + if (c == 'D') + end_cmd_q(); // stop adding to q +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + break; + case 'G': // G- goto to a line number (default= E-O-F) + dot = end - 1; // assume E-O-F + if (cmdcnt > 0) { + dot = find_line(cmdcnt); // what line is #cmdcnt + } + dot_skip_over_ws(); + break; + case 'H': // H- goto top line on screen + dot = screenbegin; + if (cmdcnt > (rows - 1)) { + cmdcnt = (rows - 1); + } + if (cmdcnt-- > 1) { + do_cmd('+'); + } // repeat cnt + dot_skip_over_ws(); + break; + case 'I': // I- insert before first non-blank + dot_begin(); // 0 + dot_skip_over_ws(); + //**** fall thru to ... 'i' + case 'i': // i- insert before current char + case VI_K_INSERT: // Cursor Key Insert + dc_i: + cmd_mode = 1; // start insrting + break; + case 'J': // J- join current and next lines together + if (cmdcnt-- > 2) { + do_cmd(c); + } // repeat cnt + dot_end(); // move to NL + if (dot < end - 1) { // make sure not last char in text[] + *dot++ = ' '; // replace NL with space + file_modified++; + while (isblnk(*dot)) { // delete leading WS + dot_delete(); + } + } + end_cmd_q(); // stop adding to q + break; + case 'L': // L- goto bottom line on screen + dot = end_screen(); + if (cmdcnt > (rows - 1)) { + cmdcnt = (rows - 1); + } + if (cmdcnt-- > 1) { + do_cmd('-'); + } // repeat cnt + dot_begin(); + dot_skip_over_ws(); + break; + case 'M': // M- goto middle line on screen + dot = screenbegin; + for (cnt = 0; cnt < (rows-1) / 2; cnt++) + dot = next_line(dot); + break; + case 'O': // O- open a empty line above + // 0i\n ESC -i + p = begin_line(dot); + if (p[-1] == '\n') { + dot_prev(); + case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." + dot_end(); + dot = char_insert(dot, '\n'); + } else { + dot_begin(); // 0 + dot = char_insert(dot, '\n'); // i\n ESC + dot_prev(); // - + } + goto dc_i; + break; + case 'R': // R- continuous Replace char + dc5: + cmd_mode = 2; + break; + case 'X': // X- delete char before dot + case 'x': // x- delete the current char + case 's': // s- substitute the current char + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dir = 0; + if (c == 'X') + dir = -1; + if (dot[dir] != '\n') { + if (c == 'X') + dot--; // delete prev char + dot = yank_delete(dot, dot, 0, YANKDEL); // delete char + } + if (c == 's') + goto dc_i; // start insrting + end_cmd_q(); // stop adding to q + break; + case 'Z': // Z- if modified, {write}; exit + // ZZ means to save file (if necessary), then exit + c1 = get_one_char(); + if (c1 != 'Z') { + indicate_error(c); + break; + } + if (file_modified) { +#ifdef CONFIG_FEATURE_VI_READONLY + if (vi_readonly || readonly) { + psbs("\"%s\" File is read only", cfn); + break; + } +#endif /* CONFIG_FEATURE_VI_READONLY */ + cnt = file_write(cfn, text, end - 1); + if (cnt < 0) { + if (cnt == -1) + psbs("Write error: %s", strerror(errno)); + } else if (cnt == (end - 1 - text + 1)) { + editing = 0; + } + } else { + editing = 0; + } + break; + case '^': // ^- move to first non-blank on line + dot_begin(); + dot_skip_over_ws(); + break; + case 'b': // b- back a word + case 'e': // e- end of word + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dir = FORWARD; + if (c == 'b') + dir = BACK; + if ((dot + dir) < text || (dot + dir) > end - 1) + break; + dot += dir; + if (isspace(*dot)) { + dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS); + } + if (isalnum(*dot) || *dot == '_') { + dot = skip_thing(dot, 1, dir, S_END_ALNUM); + } else if (ispunct(*dot)) { + dot = skip_thing(dot, 1, dir, S_END_PUNCT); + } + break; + case 'c': // c- change something + case 'd': // d- delete something +#ifdef CONFIG_FEATURE_VI_YANKMARK + case 'y': // y- yank something + case 'Y': // Y- Yank a line +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + yf = YANKDEL; // assume either "c" or "d" +#ifdef CONFIG_FEATURE_VI_YANKMARK + if (c == 'y' || c == 'Y') + yf = YANKONLY; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + c1 = 'y'; + if (c != 'Y') + c1 = get_one_char(); // get the type of thing to delete + find_range(&p, &q, c1); + if (c1 == 27) { // ESC- user changed mind and wants out + c = c1 = 27; // Escape- do nothing + } else if (strchr("wW", c1)) { + if (c == 'c') { + // don't include trailing WS as part of word + while (isblnk(*q)) { + if (q <= text || q[-1] == '\n') + break; + q--; + } + } + dot = yank_delete(p, q, 0, yf); // delete word + } else if (strchr("^0bBeEft$", c1)) { + // single line copy text into a register and delete + dot = yank_delete(p, q, 0, yf); // delete word + } else if (strchr("cdykjHL%+-{}\r\n", c1)) { + // multiple line copy text into a register and delete + dot = yank_delete(p, q, 1, yf); // delete lines + if (c == 'c') { + dot = char_insert(dot, '\n'); + // on the last line of file don't move to prev line + if (dot != (end-1)) { + dot_prev(); + } + } else if (c == 'd') { + dot_begin(); + dot_skip_over_ws(); + } + } else { + // could not recognize object + c = c1 = 27; // error- + indicate_error(c); + } + if (c1 != 27) { + // if CHANGING, not deleting, start inserting after the delete + if (c == 'c') { + strcpy((char *) buf, "Change"); + goto dc_i; // start inserting + } + if (c == 'd') { + strcpy((char *) buf, "Delete"); + } +#ifdef CONFIG_FEATURE_VI_YANKMARK + if (c == 'y' || c == 'Y') { + strcpy((char *) buf, "Yank"); + } + p = reg[YDreg]; + q = p + strlen((char *) p); + for (cnt = 0; p <= q; p++) { + if (*p == '\n') + cnt++; + } + psb("%s %d lines (%d chars) using [%c]", + buf, cnt, strlen((char *) reg[YDreg]), what_reg()); +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + end_cmd_q(); // stop adding to q + } + break; + case 'k': // k- goto prev line, same col + case VI_K_UP: // cursor key Up + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_prev(); + dot = move_to_col(dot, ccol + offset); // try stay in same col + break; + case 'r': // r- replace the current char with user input + c1 = get_one_char(); // get the replacement char + if (*dot != '\n') { + *dot = c1; + file_modified++; // has the file been modified + } + end_cmd_q(); // stop adding to q + break; + case 't': // t- move to char prior to next x + last_forward_char = get_one_char(); + do_cmd(';'); + if (*dot == last_forward_char) + dot_left(); + last_forward_char= 0; + break; + case 'w': // w- forward a word + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + if (isalnum(*dot) || *dot == '_') { // we are on ALNUM + dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM); + } else if (ispunct(*dot)) { // we are on PUNCT + dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT); + } + if (dot < end - 1) + dot++; // move over word + if (isspace(*dot)) { + dot = skip_thing(dot, 2, FORWARD, S_OVER_WS); + } + break; + case 'z': // z- + c1 = get_one_char(); // get the replacement char + cnt = 0; + if (c1 == '.') + cnt = (rows - 2) / 2; // put dot at center + if (c1 == '-') + cnt = rows - 2; // put dot at bottom + screenbegin = begin_line(dot); // start dot at top + dot_scroll(cnt, -1); + break; + case '|': // |- move to column "cmdcnt" + dot = move_to_col(dot, cmdcnt - 1); // try to move to column + break; + case '~': // ~- flip the case of letters a-z -> A-Z + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + if (islower(*dot)) { + *dot = toupper(*dot); + file_modified++; // has the file been modified + } else if (isupper(*dot)) { + *dot = tolower(*dot); + file_modified++; // has the file been modified + } + dot_right(); + end_cmd_q(); // stop adding to q + break; + //----- The Cursor and Function Keys ----------------------------- + case VI_K_HOME: // Cursor Key Home + dot_begin(); + break; + // The Fn keys could point to do_macro which could translate them + case VI_K_FUN1: // Function Key F1 + case VI_K_FUN2: // Function Key F2 + case VI_K_FUN3: // Function Key F3 + case VI_K_FUN4: // Function Key F4 + case VI_K_FUN5: // Function Key F5 + case VI_K_FUN6: // Function Key F6 + case VI_K_FUN7: // Function Key F7 + case VI_K_FUN8: // Function Key F8 + case VI_K_FUN9: // Function Key F9 + case VI_K_FUN10: // Function Key F10 + case VI_K_FUN11: // Function Key F11 + case VI_K_FUN12: // Function Key F12 + break; + } + + dc1: + // if text[] just became empty, add back an empty line + if (end == text) { + (void) char_insert(text, '\n'); // start empty buf with dummy line + dot = text; + } + // it is OK for dot to exactly equal to end, otherwise check dot validity + if (dot != end) { + dot = bound_dot(dot); // make sure "dot" is valid + } +#ifdef CONFIG_FEATURE_VI_YANKMARK + check_context(c); // update the current context +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + + if (!isdigit(c)) + cmdcnt = 0; // cmd was not a number, reset cmdcnt + cnt = dot - begin_line(dot); + // Try to stay off of the Newline + if (*dot == '\n' && cnt > 0 && cmd_mode == 0) + dot--; +} + +#ifdef CONFIG_FEATURE_VI_CRASHME +static int totalcmds = 0; +static int Mp = 85; // Movement command Probability +static int Np = 90; // Non-movement command Probability +static int Dp = 96; // Delete command Probability +static int Ip = 97; // Insert command Probability +static int Yp = 98; // Yank command Probability +static int Pp = 99; // Put command Probability +static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0; +char chars[20] = "\t012345 abcdABCD-=.$"; +char *words[20] = { "this", "is", "a", "test", + "broadcast", "the", "emergency", "of", + "system", "quick", "brown", "fox", + "jumped", "over", "lazy", "dogs", + "back", "January", "Febuary", "March" +}; +char *lines[20] = { + "You should have received a copy of the GNU General Public License\n", + "char c, cm, *cmd, *cmd1;\n", + "generate a command by percentages\n", + "Numbers may be typed as a prefix to some commands.\n", + "Quit, discarding changes!\n", + "Forced write, if permission originally not valid.\n", + "In general, any ex or ed command (such as substitute or delete).\n", + "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n", + "Please get w/ me and I will go over it with you.\n", + "The following is a list of scheduled, committed changes.\n", + "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n", + "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n", + "Any question about transactions please contact Sterling Huxley.\n", + "I will try to get back to you by Friday, December 31.\n", + "This Change will be implemented on Friday.\n", + "Let me know if you have problems accessing this;\n", + "Sterling Huxley recently added you to the access list.\n", + "Would you like to go to lunch?\n", + "The last command will be automatically run.\n", + "This is too much english for a computer geek.\n", +}; +char *multilines[20] = { + "You should have received a copy of the GNU General Public License\n", + "char c, cm, *cmd, *cmd1;\n", + "generate a command by percentages\n", + "Numbers may be typed as a prefix to some commands.\n", + "Quit, discarding changes!\n", + "Forced write, if permission originally not valid.\n", + "In general, any ex or ed command (such as substitute or delete).\n", + "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n", + "Please get w/ me and I will go over it with you.\n", + "The following is a list of scheduled, committed changes.\n", + "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n", + "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n", + "Any question about transactions please contact Sterling Huxley.\n", + "I will try to get back to you by Friday, December 31.\n", + "This Change will be implemented on Friday.\n", + "Let me know if you have problems accessing this;\n", + "Sterling Huxley recently added you to the access list.\n", + "Would you like to go to lunch?\n", + "The last command will be automatically run.\n", + "This is too much english for a computer geek.\n", +}; + +// create a random command to execute +static void crash_dummy() +{ + static int sleeptime; // how long to pause between commands + char c, cm, *cmd, *cmd1; + int i, cnt, thing, rbi, startrbi, percent; + + // "dot" movement commands + cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL"; + + // is there already a command running? + if (readed_for_parse > 0) + goto cd1; + cd0: + startrbi = rbi = 0; + sleeptime = 0; // how long to pause between commands + memset(readbuffer, '\0', BUFSIZ); // clear the read buffer + // generate a command by percentages + percent = (int) lrand48() % 100; // get a number from 0-99 + if (percent < Mp) { // Movement commands + // available commands + cmd = cmd1; + M++; + } else if (percent < Np) { // non-movement commands + cmd = "mz<>\'\""; // available commands + N++; + } else if (percent < Dp) { // Delete commands + cmd = "dx"; // available commands + D++; + } else if (percent < Ip) { // Inset commands + cmd = "iIaAsrJ"; // available commands + I++; + } else if (percent < Yp) { // Yank commands + cmd = "yY"; // available commands + Y++; + } else if (percent < Pp) { // Put commands + cmd = "pP"; // available commands + P++; + } else { + // We do not know how to handle this command, try again + U++; + goto cd0; + } + // randomly pick one of the available cmds from "cmd[]" + i = (int) lrand48() % strlen(cmd); + cm = cmd[i]; + if (strchr(":\024", cm)) + goto cd0; // dont allow colon or ctrl-T commands + readbuffer[rbi++] = cm; // put cmd into input buffer + + // now we have the command- + // there are 1, 2, and multi char commands + // find out which and generate the rest of command as necessary + if (strchr("dmryz<>\'\"", cm)) { // 2-char commands + cmd1 = " \n\r0$^-+wWeEbBhjklHL"; + if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[] + cmd1 = "abcdefghijklmnopqrstuvwxyz"; + } + thing = (int) lrand48() % strlen(cmd1); // pick a movement command + c = cmd1[thing]; + readbuffer[rbi++] = c; // add movement to input buffer + } + if (strchr("iIaAsc", cm)) { // multi-char commands + if (cm == 'c') { + // change some thing + thing = (int) lrand48() % strlen(cmd1); // pick a movement command + c = cmd1[thing]; + readbuffer[rbi++] = c; // add movement to input buffer + } + thing = (int) lrand48() % 4; // what thing to insert + cnt = (int) lrand48() % 10; // how many to insert + for (i = 0; i < cnt; i++) { + if (thing == 0) { // insert chars + readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))]; + } else if (thing == 1) { // insert words + strcat((char *) readbuffer, words[(int) lrand48() % 20]); + strcat((char *) readbuffer, " "); + sleeptime = 0; // how fast to type + } else if (thing == 2) { // insert lines + strcat((char *) readbuffer, lines[(int) lrand48() % 20]); + sleeptime = 0; // how fast to type + } else { // insert multi-lines + strcat((char *) readbuffer, multilines[(int) lrand48() % 20]); + sleeptime = 0; // how fast to type + } + } + strcat((char *) readbuffer, "\033"); + } + readed_for_parse = strlen(readbuffer); + cd1: + totalcmds++; + if (sleeptime > 0) + (void) mysleep(sleeptime); // sleep 1/100 sec +} + +// test to see if there are any errors +static void crash_test() +{ + static time_t oldtim; + time_t tim; + char d[2], msg[BUFSIZ]; + + msg[0] = '\0'; + if (end < text) { + strcat((char *) msg, "end textend) { + strcat((char *) msg, "end>textend "); + } + if (dot < text) { + strcat((char *) msg, "dot end) { + strcat((char *) msg, "dot>end "); + } + if (screenbegin < text) { + strcat((char *) msg, "screenbegin end - 1) { + strcat((char *) msg, "screenbegin>end-1 "); + } + + if (strlen(msg) > 0) { + alarm(0); + printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s", + totalcmds, last_input_char, msg, SOs, SOn); + fflush(stdout); + while (read(0, d, 1) > 0) { + if (d[0] == '\n' || d[0] == '\r') + break; + } + alarm(3); + } + tim = (time_t) time((time_t *) 0); + if (tim >= (oldtim + 3)) { + sprintf((char *) status_buffer, + "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d", + totalcmds, M, N, I, D, Y, P, U, end - text + 1); + oldtim = tim; + } + return; +} +#endif /* CONFIG_FEATURE_VI_CRASHME */ diff --git a/examples/bootfloppy/bootfloppy.txt b/examples/bootfloppy/bootfloppy.txt new file mode 100644 index 000000000..b514e6305 --- /dev/null +++ b/examples/bootfloppy/bootfloppy.txt @@ -0,0 +1,180 @@ +Building a Busybox Boot Floppy +============================== + +This document describes how to buid a boot floppy using the following +components: + + - Linux Kernel (http://www.kernel.org) + - uClibc: C library (http://www.uclibc.org/) + - Busybox: Unix utilities (http://busybox.net/) + - Syslinux: bootloader (http://syslinux.zytor.com) + +It is based heavily on a paper presented by Erik Andersen at the 2001 Embedded +Systems Conference. + + + +Building The Software Components +-------------------------------- + +Detailed instructions on how to build Busybox, uClibc, or a working Linux +kernel are beyond the scope of this document. The following guidelines will +help though: + + - Stock Busybox from CVS or a tarball will work with no modifications to + any files. Just extract and go. + - Ditto uClibc. + - Your Linux kernel must include support for initrd or else the floppy + won't be able to mount it's root file system. + +If you require further information on building Busybox uClibc or Linux, please +refer to the web pages and documentation for those individual programs. + + + +Making a Root File System +------------------------- + +The following steps will create a root file system. + + - Create an empty file that you can format as a filesystem: + + dd if=/dev/zero of=rootfs bs=1k count=4000 + + - Set up the rootfs file we just created to be used as a loop device (may not + be necessary) + + losetup /dev/loop0 rootfs + + - Format the rootfs file with a filesystem: + + mkfs.ext2 -F -i 2000 rootfs + + - Mount the file on a mountpoint so we can place files in it: + + mkdir loop + mount -o loop rootfs loop/ + + (you will probably need to be root to do this) + + - Copy on the C library, the dynamic linking library, and other necessary + libraries. For this example, we copy the following files from the uClibc + tree: + + mkdir loop/lib + (chdir to uClibc directory) + cp -a libc.so* uClibc*.so \ + ld.so-1/d-link/ld-linux-uclibc.so* \ + ld.so-1/libdl/libdl.so* \ + crypt/libcrypt.so* \ + (path to)loop/lib + + - Install the Busybox binary and accompanying symlinks: + + (chdir to busybox directory) + make PREFIX=(path to)loop/ install + + - Make device files in /dev: + + This can be done by running the 'mkdevs.sh' script. If you want the gory + details, you can read the script. + + - Make necessary files in /etc: + + For this, just cp -a the etc/ directory onto rootfs. Again, if you want + all the details, you can just look at the files in the dir. + + - Unmount the rootfs from the mountpoint: + + umount loop + + - Compress it: + + gzip -9 rootfs + + +Making a SYSLINUX boot floppy +----------------------------- + +The following steps will create the boot floppy. + +Note: You will need to have the mtools package installed beforehand. + + - Insert a floppy in the drive and format it with an MSDOS filesystem: + + mformat a: + + (if the system doesn't know what device 'a:' is, look at /etc/mtools.conf) + + - Run syslinux on the floppy: + + syslinux -s /dev/fd0 + + (the -s stands for "safe, slow, and stupid" and should work better with + buggy BIOSes; it can be omitted) + + - Put on a syslinux.cfg file: + + mcopy syslinux.cfg a: + + (more on syslinux.cfg below) + + - Copy the root file system you made onto the MSDOS formatted floppy + + mcopy rootfs.gz a: + + - Build a linux kernel and copy it onto the disk with the filename 'linux' + + mcopy bzImage a:linux + + +Sample syslinux.cfg +~~~~~~~~~~~~~~~~~~~ + +The following simple syslinux.cfg file should work. You can tweak it if you +like. + +----begin-syslinux.cfg--------------- +DEFAULT linux +APPEND initrd=rootfs.gz root=/dev/ram0 +TIMEOUT 10 +PROMPT 1 +----end-syslinux.cfg--------------- + +Some changes you could make to syslinux.cfg: + + - This value is the number seconds it will wait before booting. You can set + the timeout to 0 (or omit) to boot instantly, or you can set it as high as + 10 to wait awhile. + + - PROMPT can be set to 0 to disable the 'boot:' prompt. + + - you can add this line to display the contents of a file as a welcome + message: + + DISPLAY display.txt + + + +Additional Resources +-------------------- + +Other useful information on making a Linux bootfloppy is available at the +following URLs: + +http://www.linuxdoc.org/HOWTO/Bootdisk-HOWTO/index.html +http://www.linux-embedded.com/howto/Embedded-Linux-Howto.html +http://linux-embedded.org/howto/LFS-HOWTO.html +http://linux-embedded.org/pmhowto.html +http://recycle.lbl.gov/~ldoolitt/embedded/ (Larry Doolittle's stuff) + + + +Possible TODOs +-------------- + +The following features that we might want to add later: + + - support for additional filesystems besides ext2, i.e. minix + - different libc, static vs dynamic loading + - maybe using an alternate bootloader diff --git a/examples/bootfloppy/display.txt b/examples/bootfloppy/display.txt new file mode 100644 index 000000000..399d326d9 --- /dev/null +++ b/examples/bootfloppy/display.txt @@ -0,0 +1,4 @@ + +This boot floppy is made with Busybox, uClibc, and the Linux kernel. +Hit RETURN to boot or enter boot parameters at the prompt below. + diff --git a/examples/bootfloppy/etc/fstab b/examples/bootfloppy/etc/fstab new file mode 100644 index 000000000..ef14ca2cc --- /dev/null +++ b/examples/bootfloppy/etc/fstab @@ -0,0 +1,2 @@ +proc /proc proc defaults 0 0 + diff --git a/examples/bootfloppy/etc/init.d/rcS b/examples/bootfloppy/etc/init.d/rcS new file mode 100755 index 000000000..4f29b923f --- /dev/null +++ b/examples/bootfloppy/etc/init.d/rcS @@ -0,0 +1,3 @@ +#! /bin/sh + +/bin/mount -a diff --git a/examples/bootfloppy/etc/inittab b/examples/bootfloppy/etc/inittab new file mode 100644 index 000000000..eb3e979ce --- /dev/null +++ b/examples/bootfloppy/etc/inittab @@ -0,0 +1,5 @@ +::sysinit:/etc/init.d/rcS +::respawn:-/bin/sh +tty2::askfirst:-/bin/sh +::ctrlaltdel:/bin/umount -a -r + diff --git a/examples/bootfloppy/etc/profile b/examples/bootfloppy/etc/profile new file mode 100644 index 000000000..8a7c77d78 --- /dev/null +++ b/examples/bootfloppy/etc/profile @@ -0,0 +1,8 @@ +# /etc/profile: system-wide .profile file for the Bourne shells + +echo +echo -n "Processing /etc/profile... " +# no-op +echo "Done" +echo + diff --git a/examples/bootfloppy/mkdevs.sh b/examples/bootfloppy/mkdevs.sh new file mode 100755 index 000000000..03a1a8550 --- /dev/null +++ b/examples/bootfloppy/mkdevs.sh @@ -0,0 +1,62 @@ +#!/bin/sh +# +# makedev.sh - creates device files for a busybox boot floppy image + + +# we do our work in the dev/ directory +if [ -z "$1" ]; then + echo "usage: `basename $0` path/to/dev/dir" + exit 1 +fi + +cd $1 + + +# miscellaneous one-of-a-kind stuff +mknod console c 5 1 +mknod full c 1 7 +mknod kmem c 1 2 +mknod mem c 1 1 +mknod null c 1 3 +mknod port c 1 4 +mknod random c 1 8 +mknod urandom c 1 9 +mknod zero c 1 5 +ln -s /proc/kcore core + +# IDE HD devs +# note: not going to bother creating all concievable partitions; you can do +# that yourself as you need 'em. +mknod hda b 3 0 +mknod hdb b 3 64 +mknod hdc b 22 0 +mknod hdd b 22 64 + +# loop devs +for i in `seq 0 7`; do + mknod loop$i b 7 $i +done + +# ram devs +for i in `seq 0 9`; do + mknod ram$i b 1 $i +done +ln -s ram1 ram + +# ttys +mknod tty c 5 0 +for i in `seq 0 9`; do + mknod tty$i c 4 $i +done + +# virtual console screen devs +for i in `seq 0 9`; do + mknod vcs$i b 7 $i +done +ln -s vcs0 vcs + +# virtual console screen w/ attributes devs +for i in `seq 0 9`; do + mknod vcsa$i b 7 $i +done +ln -s vcsa0 vcsa diff --git a/examples/bootfloppy/mkrootfs.sh b/examples/bootfloppy/mkrootfs.sh new file mode 100755 index 000000000..e79ed418e --- /dev/null +++ b/examples/bootfloppy/mkrootfs.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# +# mkrootfs.sh - creates a root file system +# + +# TODO: need to add checks here to verify that busybox, uClibc and bzImage +# exist + + +# command-line settable variables +BUSYBOX_DIR=.. +UCLIBC_DIR=../../uClibc +TARGET_DIR=./loop +FSSIZE=4000 +CLEANUP=1 +MKFS='mkfs.ext2 -F' + +# don't-touch variables +BASE_DIR=`pwd` + + +while getopts 'b:u:s:t:Cm' opt +do + case $opt in + b) BUSYBOX_DIR=$OPTARG ;; + u) UCLIBC_DIR=$OPTARG ;; + t) TARGET_DIR=$OPTARG ;; + s) FSSIZE=$OPTARG ;; + C) CLEANUP=0 ;; + m) MKFS='mkfs.minix' ;; + *) + echo "usage: `basename $0` [-bu]" + echo " -b DIR path to busybox direcory (default ..)" + echo " -u DIR path to uClibc direcory (default ../../uClibc)" + echo " -t DIR path to target direcory (default ./loop)" + echo " -s SIZE size of root filesystem in Kbytes (default 4000)" + echo " -C don't perform cleanup (umount target dir, gzip rootfs, etc.)" + echo " (this allows you to 'chroot loop/ /bin/sh' to test it)" + echo " -m use minix filesystem (default is ext2)" + exit 1 + ;; + esac +done + + + + +# clean up from any previous work +mount | grep -q loop +[ $? -eq 0 ] && umount $TARGET_DIR +[ -d $TARGET_DIR ] && rm -rf $TARGET_DIR/ +[ -f rootfs ] && rm -f rootfs +[ -f rootfs.gz ] && rm -f rootfs.gz + + +# prepare root file system and mount as loopback +dd if=/dev/zero of=rootfs bs=1k count=$FSSIZE +$MKFS -i 2000 rootfs +mkdir $TARGET_DIR +mount -o loop,exec rootfs $TARGET_DIR # must be root + + +# install uClibc +mkdir -p $TARGET_DIR/lib +cd $UCLIBC_DIR +make INSTALL_DIR= +cp -a libc.so* $BASE_DIR/$TARGET_DIR/lib +cp -a uClibc*.so $BASE_DIR/$TARGET_DIR/lib +cp -a ld.so-1/d-link/ld-linux-uclibc.so* $BASE_DIR/$TARGET_DIR/lib +cp -a ld.so-1/libdl/libdl.so* $BASE_DIR/$TARGET_DIR/lib +cp -a crypt/libcrypt.so* $BASE_DIR/$TARGET_DIR/lib +cd $BASE_DIR + + +# install busybox and components +cd $BUSYBOX_DIR +make distclean +make CC=$BASE_DIR/$UCLIBC_DIR/extra/gcc-uClibc/i386-uclibc-gcc +make PREFIX=$BASE_DIR/$TARGET_DIR install +cd $BASE_DIR + + +# make files in /dev +mkdir $TARGET_DIR/dev +./mkdevs.sh $TARGET_DIR/dev + + +# make files in /etc +cp -a etc $TARGET_DIR +ln -s /proc/mounts $TARGET_DIR/etc/mtab + + +# other miscellaneous setup +mkdir $TARGET_DIR/initrd +mkdir $TARGET_DIR/proc + + +# Done. Maybe do cleanup. +if [ $CLEANUP -eq 1 ] +then + umount $TARGET_DIR + rmdir $TARGET_DIR + gzip -9 rootfs +fi + diff --git a/examples/bootfloppy/mksyslinux.sh b/examples/bootfloppy/mksyslinux.sh new file mode 100755 index 000000000..e25417353 --- /dev/null +++ b/examples/bootfloppy/mksyslinux.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# +# Formats a floppy to use Syslinux + +dummy="" + + +# need to have mtools installed +if [ -z `which mformat` -o -z `which mcopy` ]; then + echo "You must have the mtools package installed to run this script" + exit 1 +fi + + +# need an arg for the location of the kernel +if [ -z "$1" ]; then + echo "usage: `basename $0` path/to/linux/kernel" + exit 1 +fi + + +# need to have a root file system built +if [ ! -f rootfs.gz ]; then + echo "You need to have a rootfs built first." + echo "Hit RETURN to make one now or Control-C to quit." + read dummy + ./mkrootfs.sh +fi + + +# prepare the floppy +echo "Please insert a blank floppy in the drive and press RETURN to format" +echo "(WARNING: All data will be erased! Hit Control-C to abort)" +read dummy + +echo "Formatting the floppy..." +mformat a: +echo "Making it bootable with Syslinux..." +syslinux -s /dev/fd0 +echo "Copying Syslinux configuration files..." +mcopy syslinux.cfg display.txt a: +echo "Copying root filesystem file..." +mcopy rootfs.gz a: +# XXX: maybe check for "no space on device" errors here +echo "Copying linux kernel..." +mcopy $1 a:linux +# XXX: maybe check for "no space on device" errors here too +echo "Finished: boot floppy created" diff --git a/examples/bootfloppy/quickstart.txt b/examples/bootfloppy/quickstart.txt new file mode 100644 index 000000000..0d8090824 --- /dev/null +++ b/examples/bootfloppy/quickstart.txt @@ -0,0 +1,15 @@ +Quickstart on making the Busybox boot-floppy: + + 1) Download Busybox and uClibc from CVS or tarballs. Make sure they share a + common parent directory. (i.e. busybox/ and uclibc/ are both right off of + /tmp, or wherever.) + + 2) Build a Linux kernel. Make sure you include support for initrd. + + 3) Put a floppy in the drive. Make sure it is a floppy you don't care about + because the contents will be overwritten. + + 4) As root, type ./mksyslinux.sh path/to/linux/kernel from this directory. + Wait patiently while the magic happens. + + 5) Boot up on the floppy. diff --git a/examples/bootfloppy/syslinux.cfg b/examples/bootfloppy/syslinux.cfg new file mode 100644 index 000000000..fa2677ca8 --- /dev/null +++ b/examples/bootfloppy/syslinux.cfg @@ -0,0 +1,7 @@ +display display.txt +default linux +timeout 10 +prompt 1 +label linux + kernel linux + append initrd=rootfs.gz root=/dev/ram0 diff --git a/examples/busybox.spec b/examples/busybox.spec new file mode 100644 index 000000000..3986436c1 --- /dev/null +++ b/examples/busybox.spec @@ -0,0 +1,44 @@ +%define name busybox +%define epoch 0 +%define version 0.61.pre +%define release %(date -I | sed -e 's/-/_/g') +%define serial 1 + +Name: %{name} +#Epoch: %{epoch} +Version: %{version} +Release: %{release} +Serial: %{serial} +Copyright: GPL +Group: System/Utilities +Summary: BusyBox is a tiny suite of Unix utilities in a multi-call binary. +URL: http://busybox.net/ +Source: ftp://busybox.net/busybox/%{name}-%{version}.tar.gz +Buildroot: /var/tmp/%{name}-%{version} +Packager : Erik Andersen + +%Description +BusyBox combines tiny versions of many common UNIX utilities into a single +small executable. It provides minimalist replacements for most of the utilities +you usually find in fileutils, shellutils, findutils, textutils, grep, gzip, +tar, etc. BusyBox provides a fairly complete POSIX environment for any small +or emdedded system. The utilities in BusyBox generally have fewer options then +their full featured GNU cousins; however, the options that are provided behave +very much like their GNU counterparts. + +%Prep +%setup -q -n %{name}-%{version} + +%Build +make + +%Install +rm -rf $RPM_BUILD_ROOT +make PREFIX=$RPM_BUILD_ROOT install + +%Clean +rm -rf $RPM_BUILD_ROOT + +%Files +%defattr(-,root,root) +/ diff --git a/examples/depmod.pl b/examples/depmod.pl new file mode 100755 index 000000000..b2bf54713 --- /dev/null +++ b/examples/depmod.pl @@ -0,0 +1,289 @@ +#!/usr/bin/perl -w +# vi: set sw=4 ts=4: +# Copyright (c) 2001 David Schleef +# Copyright (c) 2001 Erik Andersen +# Copyright (c) 2001 Stuart Hughes +# Copyright (c) 2002 Steven J. Hill +# Copyright (c) 2006 Freescale Semiconductor, Inc +# +# History: +# March 2006: Stuart Hughes . +# Significant updates, including implementing the '-F' option +# and adding support for 2.6 kernels. + +# This program is free software; you can redistribute it and/or modify it +# under the same terms as Perl itself. +use Getopt::Long; +use File::Find; +use strict; + +# Set up some default values +my $kdir=""; +my $basedir=""; +my $kernel=""; +my $kernelsyms=""; +my $stdout=0; +my $verbose=0; +my $help=0; +my $nm = $ENV{'NM'} || "nm"; + +# more globals +my (@liblist) = (); +my $exp = {}; +my $dep = {}; +my $mod = {}; + +my $usage = < | -F } [options]... + Where: + -h --help : Show this help screen + -b --basedir : Modules base directory (e.g /lib/modules/<2.x.y>) + -k --kernel : Kernel binary for the target (e.g. vmlinux) + -F --kernelsyms : Kernel symbol file (e.g. System.map) + -n --stdout : Write to stdout instead of /modules.dep + -v --verbose : Print out lots of debugging stuff +TXT + +# get command-line options +GetOptions( + "help|h" => \$help, + "basedir|b=s" => \$basedir, + "kernel|k=s" => \$kernel, + "kernelsyms|F=s" => \$kernelsyms, + "stdout|n" => \$stdout, + "verbose|v" => \$verbose, +); + +die $usage if $help; +die $usage unless $basedir && ( $kernel || $kernelsyms ); +die "can't use both -k and -F\n\n$usage" if $kernel && $kernelsyms; + +# Strip any trailing or multiple slashes from basedir +$basedir =~ s-(/)\1+-/-g; + +# The base directory should contain /lib/modules somewhere +if($basedir !~ m-/lib/modules-) { + warn "WARNING: base directory does not match ..../lib/modules\n"; +} + +# if no kernel version is contained in the basedir, try to find one +if($basedir !~ m-/lib/modules/\d\.\d-) { + opendir(BD, $basedir) or die "can't open basedir $basedir : $!\n"; + foreach ( readdir(BD) ) { + next if /^\.\.?$/; + next unless -d "$basedir/$_"; + warn "dir = $_\n" if $verbose; + if( /^\d\.\d/ ) { + $kdir = $_; + warn("Guessed module directory as $basedir/$kdir\n"); + last; + } + } + closedir(BD); + die "Cannot find a kernel version under $basedir\n" unless $kdir; + $basedir = "$basedir/$kdir"; +} + +# Find the list of .o or .ko files living under $basedir +warn "**** Locating all modules\n" if $verbose; +find sub { + my $file; + if ( -f $_ && ! -d $_ ) { + $file = $File::Find::name; + if ( $file =~ /\.k?o$/ ) { + push(@liblist, $file); + warn "$file\n" if $verbose; + } + } +}, $basedir; +warn "**** Finished locating modules\n" if $verbose; + +foreach my $obj ( @liblist ){ + # turn the input file name into a target tag name + my ($tgtname) = $obj =~ m-(/lib/modules/.*)$-; + + warn "\nMODULE = $tgtname\n" if $verbose; + + # get a list of symbols + my @output=`$nm $obj`; + + build_ref_tables($tgtname, \@output, $exp, $dep); +} + + +# vmlinux is a special name that is only used to resolve symbols +my $tgtname = 'vmlinux'; +my @output = $kernelsyms ? `cat $kernelsyms` : `$nm $kernel`; +warn "\nMODULE = $tgtname\n" if $verbose; +build_ref_tables($tgtname, \@output, $exp, $dep); + +# resolve the dependencies for each module +# reduce dependencies: remove unresolvable and resolved from vmlinux/System.map +# remove duplicates +foreach my $module (keys %$dep) { + warn "reducing module: $module\n" if $verbose; + $mod->{$module} = {}; + foreach (@{$dep->{$module}}) { + if( $exp->{$_} ) { + warn "resolved symbol $_ in file $exp->{$_}\n" if $verbose; + next if $exp->{$_} =~ /vmlinux/; + $mod->{$module}{$exp->{$_}} = 1; + } else { + warn "unresolved symbol $_ in file $module\n"; + } + } +} + +# figure out where the output should go +if ($stdout == 0) { + open(STDOUT, ">$basedir/modules.dep") + or die "cannot open $basedir/modules.dep: $!"; +} +my $kseries = $basedir =~ m,/2\.6\.[^/]*, ? '2.6' : '2.4'; + +foreach my $module ( keys %$mod ) { + if($kseries eq '2.4') { + print "$module:\t"; + my @sorted = sort bydep keys %{$mod->{$module}}; + print join(" \\\n\t",@sorted); + print "\n\n"; + } else { + print "$module: "; + my @sorted = sort bydep keys %{$mod->{$module}}; + print join(" ",@sorted); + print "\n"; + } +} + + +sub build_ref_tables +{ + my ($name, $sym_ar, $exp, $dep) = @_; + + my $ksymtab = grep m/ __ksymtab/, @$sym_ar; + + # gather the exported symbols + if($ksymtab){ + # explicitly exported + foreach ( @$sym_ar ) { + / __ksymtab_(.*)$/ and do { + warn "sym = $1\n" if $verbose; + $exp->{$1} = $name; + }; + } + } else { + # exporting all symbols + foreach ( @$sym_ar ) { + / [ABCDGRST] (.*)$/ and do { + warn "syma = $1\n" if $verbose; + $exp->{$1} = $name; + }; + } + } + + # this takes makes sure modules with no dependencies get listed + push @{$dep->{$name}}, 'printk' unless $name eq 'vmlinux'; + + # gather the unresolved symbols + foreach ( @$sym_ar ) { + !/ __this_module/ && / U (.*)$/ and do { + warn "und = $1\n" if $verbose; + push @{$dep->{$name}}, $1; + }; + } +} + +sub bydep +{ + foreach my $f ( keys %{$mod->{$b}} ) { + if($f eq $a) { + return 1; + } + } + return -1; +} + + + +__END__ + +=head1 NAME + +depmod.pl - a cross platform script to generate kernel module +dependency lists (modules.conf) which can then be used by modprobe +on the target platform. + +It supports Linux 2.4 and 2.6 styles of modules.conf (auto-detected) + +=head1 SYNOPSIS + +depmod.pl [OPTION]... [basedir]... + +Example: + + depmod.pl -F linux/System.map -b target/lib/modules/2.6.11 + +=head1 DESCRIPTION + +The purpose of this script is to automagically generate a list of of kernel +module dependencies. This script produces dependency lists that should be +identical to the depmod program from the modutils package. Unlike the depmod +binary, however, depmod.pl is designed to be run on your host system, not +on your target system. + +This script was written by David Schleef to be used in +conjunction with the BusyBox modprobe applet. + +=head1 OPTIONS + +=over 4 + +=item B<-h --help> + +This displays the help message. + +=item B<-b --basedir> + +The base directory uner which the target's modules will be found. This +defaults to the /lib/modules directory. + +If you don't specify the kernel version, this script will search for +one under the specified based directory and use the first thing that +looks like a kernel version. + +=item B<-k --kernel> + +Kernel binary for the target (vmlinux). You must either supply a kernel binary +or a kernel symbol file (using the -F option). + +=item B<-F --kernelsyms> + +Kernel symbol file for the target (System.map). + +=item B<-n --stdout> + +Write to stdout instead of modules.dep +kernel binary for the target (using the -k option). + +=item B<--verbose> + +Verbose (debug) output + +=back + +=head1 COPYRIGHT AND LICENSE + + Copyright (c) 2001 David Schleef + Copyright (c) 2001 Erik Andersen + Copyright (c) 2001 Stuart Hughes + Copyright (c) 2002 Steven J. Hill + Copyright (c) 2006 Freescale Semiconductor, Inc + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 AUTHOR + +David Schleef + +=cut diff --git a/examples/devfsd.conf b/examples/devfsd.conf new file mode 100644 index 000000000..e90e7102b --- /dev/null +++ b/examples/devfsd.conf @@ -0,0 +1,133 @@ +# Sample /etc/devfsd.conf configuration file. +# Richard Gooch 17-FEB-2002 +# +# adapted for busybox devfsd implementation by Tito +# +# Enable full compatibility mode for old device names. You may comment these +# out if you don't use the old device names. Make sure you know what you're +# doing! +REGISTER .* MKOLDCOMPAT +UNREGISTER .* RMOLDCOMPAT + +# You may comment out the above and uncomment the following if you've +# configured your system to use the original "new" devfs names or the really +# new names +#REGISTER ^vc/ MKOLDCOMPAT +#UNREGISTER ^vc/ RMOLDCOMPAT +#REGISTER ^pty/ MKOLDCOMPAT +#UNREGISTER ^pty/ RMOLDCOMPAT +#REGISTER ^misc/ MKOLDCOMPAT +#UNREGISTER ^misc/ RMOLDCOMPAT + +# You may comment these out if you don't use the original "new" names +REGISTER .* MKNEWCOMPAT +UNREGISTER .* RMNEWCOMPAT + +# Enable module autoloading. You may comment this out if you don't use +# autoloading +# Supported by busybox when CONFIG_DEVFSD_MODLOAD is set. +# This actually doesn't work with busybox modutils but needs +# the real modutils' modprobe +LOOKUP .* MODLOAD + +# Uncomment the following if you want to set the group to "tty" for the +# pseudo-tty devices. This is necessary so that mesg(1) can later be used to +# enable/disable talk requests and wall(1) messages. +REGISTER ^pty/s.* PERMISSIONS -1.tty 0600 +#REGISTER ^pts/.* PERMISSIONS -1.tty 0600 + +# Restoring /dev/log on startup would trigger the minilogd/initlog deadlock +# (minilogd falsely assuming syslogd has been started). +REGISTER ^log$ IGNORE +CREATE ^log$ IGNORE +CHANGE ^log$ IGNORE +DELETE ^log$ IGNORE + +# +# Uncomment this if you want permissions to be saved and restored +# Do not do this for pseudo-terminal devices +REGISTER ^pt[sy] IGNORE +CREATE ^pt[sy] IGNORE +CHANGE ^pt[sy] IGNORE +DELETE ^pt[sy] IGNORE +REGISTER .* COPY /lib/dev-state/$devname $devpath +CREATE .* COPY $devpath /lib/dev-state/$devname +CHANGE .* COPY $devpath /lib/dev-state/$devname +#DELETE .* CFUNCTION GLOBAL unlink /lib/dev-state/$devname +# Busybox +DELETE .* EXECUTE /bin/rm -f /lib/dev-state/$devname + +RESTORE /lib/dev-state + +# +# Uncomment this if you want the old /dev/cdrom symlink +#REGISTER ^cdroms/cdrom0$ CFUNCTION GLOBAL mksymlink $devname cdrom +#UNREGISTER ^cdroms/cdrom0$ CFUNCTION GLOBAL unlink cdrom +# busybox +REGISTER ^cdroms/cdrom0$ EXECUTE /bin/ln -sf $devname cdrom +UNREGISTER ^cdroms/cdrom0$ EXECUTE /bin/rm -f cdrom + +#REGISTER ^v4l/video0$ CFUNCTION GLOBAL mksymlink v4l/video0 video +#UNREGISTER ^v4l/video0$ CFUNCTION GLOBAL unlink video +#REGISTER ^radio0$ CFUNCTION GLOBAL mksymlink radio0 radio +#UNREGISTER ^radio0$ CFUNCTION GLOBAL unlink radio +# Busybox +REGISTER ^v4l/video0$ EXECUTE /bin/ln -sf v4l/video0 video +UNREGISTER ^v4l/video0$ EXECUTE /bin/rm -f video +REGISTER ^radio0$ EXECUTE /bin/ln -sf radio0 radio +UNREGISTER ^radio0$ EXECUTE /bin/rm -f radio + +# ALSA stuff +#LOOKUP snd MODLOAD ACTION snd + +# Uncomment this to let PAM manage devfs +# Not supported by busybox +#REGISTER .* CFUNCTION /lib/security/pam_console_apply_devfsd.so pam_console_apply_single $devpath + +# Uncomment this to manage USB mouse +# Not supported by busybox +#REGISTER ^input/mouse0$ CFUNCTION GLOBAL mksymlink $devname usbmouse +#UNREGISTER ^input/mouse0$ CFUNCTION GLOBAL unlink usbmouse +# Busybox +#REGISTER ^input/mouse0$ EXECUTE /bin/ln -sf $devname usbmouse +#UNREGISTER ^input/mouse0$ EXECUTE /bin/rm -f usbmouse +# Not supported by busybox +#REGISTER ^input/mice$ CFUNCTION GLOBAL mksymlink $devname usbmouse +#UNREGISTER ^input/mice$ CFUNCTION GLOBAL unlink usbmouse +# Busybox +REGISTER ^input/mice$ EXECUTE /bin/ln -sf $devname usbmouse +UNREGISTER ^input/mice$ EXECUTE /bin/rm -f usbmouse + +# If you have removable media and want to force media revalidation when looking +# up new or old compatibility names, uncomment the following lines +# SCSI NEWCOMPAT /dev/sd/* names +LOOKUP ^(sd/c[0-9]+b[0-9]+t[0-9]+u[0-9]+)p[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 +# SCSI OLDCOMPAT /dev/sd?? names +LOOKUP ^(sd[a-z]+)[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 +# IDE NEWCOMPAT /dev/ide/hd/* names +LOOKUP ^(ide/hd/c[0-9]+b[0-9]+t[0-9]+u[0-9]+)p[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 +# IDE OLDCOMPAT /dev/hd?? names +LOOKUP ^(hd[a-z])[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 +# IDE-SCSI NEWCOMPAT /dev/sd/* names +#LOOKUP ^(sd/c[0-9]+b[0-9]+t[0-9]+u[0-9]+)p[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 +#SCSI OLDCOMPAT /dev/scd? names +LOOKUP ^(scd+)[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 + + +REGISTER ^dvb/card[0-9]+/[^/]+$ PERMISSIONS root.video 0660 +# Not supported by busybox +#REGISTER ^dvb/card([0-9]+)/([^/0-9]*)[0-9]+$ CFUNCTION GLOBAL mksymlink /dev/$devname ost/\2\1 +#UNREGISTER ^dvb/card([0-9]+)/([^/0-9]*)[0-9]+$ CFUNCTION GLOBAL unlink ost/\2\1 +# Busybox +REGISTER ^dvb/card([0-9]+)/([^/0-9]*)[0-9]+$ EXECUTE /bin/ln -sf /dev/$devname ost/\2\1 +UNREGISTER ^dvb/card([0-9]+)/([^/0-9]*)[0-9]+$ EXECUTE /bin/rm -f ost/\2\1 + +# Include package-generated files from /etc/devfs/conf.d +# Supported by busybox +# INCLUDE /etc/devfs/conf.d/ +INCLUDE /etc/devfs/busybox/ +# Busybox: just for testing +#INCLUDE /etc/devfs/nothing/ +#INCLUDE /etc/devfs/nothing/nothing +#OPTIONAL_INCLUDE /etc/devfs/nothing/ +#OPTIONAL_INCLUDE /etc/devfs/nothing/nothing diff --git a/examples/dnsd.conf b/examples/dnsd.conf new file mode 100644 index 000000000..8af622b02 --- /dev/null +++ b/examples/dnsd.conf @@ -0,0 +1 @@ +thebox 192.168.1.5 diff --git a/examples/inetd.conf b/examples/inetd.conf new file mode 100644 index 000000000..ca7e3d8e1 --- /dev/null +++ b/examples/inetd.conf @@ -0,0 +1,73 @@ +# /etc/inetd.conf: see inetd(8) for further informations. +# +# Internet server configuration database +# +# +# If you want to disable an entry so it isn't touched during +# package updates just comment it out with a single '#' character. +# +# If you make changes to this file, either reboot your machine or +# send the inetd process a HUP signal: +# Do a "ps x" as root and look up the pid of inetd. Then do a +# kill -HUP +# inetd will re-read this file whenever it gets that signal. +# +# +#:INTERNAL: Internal services +# It is generally considered safer to keep these off. +echo stream tcp nowait root internal +echo dgram udp wait root internal +#discard stream tcp nowait root internal +#discard dgram udp wait root internal +daytime stream tcp nowait root internal +daytime dgram udp wait root internal +#chargen stream tcp nowait root internal +#chargen dgram udp wait root internal +time stream tcp nowait root internal +time dgram udp wait root internal + +# These are standard services. +# +#ftp stream tcp nowait root /usr/sbin/tcpd in.ftpd +#telnet stream tcp nowait root /sbin/telnetd /sbin/telnetd +#nntp stream tcp nowait root tcpd in.nntpd +#smtp stream tcp nowait root tcpd sendmail -v +# +# Shell, login, exec and talk are BSD protocols. +# +# If you run an ntalk daemon (such as netkit-ntalk) on the old talk +# port, that is, "talk" as opposed to "ntalk", it won't work and may +# cause certain broken talk clients to malfunction. +# +# The talkd from netkit-ntalk 0.12 and higher, however, can speak the +# old talk protocol and can be used safely. +# +#shell stream tcp nowait root /usr/sbin/tcpd in.rshd -L +#login stream tcp nowait root /usr/sbin/tcpd in.rlogind -L +#exec stream tcp nowait root /usr/sbin/tcpd in.rexecd +#talk dgram udp wait root /usr/sbin/tcpd in.talkd +#ntalk dgram udp wait root /usr/sbin/tcpd in.talkd +# +# Pop et al +# Leave these off unless you're using them. +#pop2 stream tcp nowait root /usr/sbin/tcpd in.pop2d +#pop3 stream tcp nowait root /usr/sbin/tcpd in.pop3d +# +# The Internet UUCP service. +# uucp stream tcp nowait uucp /usr/sbin/tcpd /usr/lib/uucp/uucico -l +# +# Tftp service is provided primarily for booting. Most sites +# run this only on machines acting as "boot servers." If you don't +# need it, don't use it. +# +#tftp dgram udp wait nobody /usr/sbin/tcpd in.tftpd +#bootps dgram udp wait root /usr/sbin/in.bootpd in.bootpd +# +# Finger, systat and netstat give out user information which may be +# valuable to potential "system crackers." Many sites choose to disable +# some or all of these services to improve security. +# +#finger stream tcp nowait nobody /usr/sbin/tcpd in.fingerd -w +#systat stream tcp nowait nobody /usr/sbin/tcpd /bin/ps -auwwx +#netstat stream tcp nowait root /bin/netstat /bin/netstat -a +#ident stream tcp nowait root /usr/sbin/in.identd in.identd diff --git a/examples/inittab b/examples/inittab new file mode 100644 index 000000000..5f2af8724 --- /dev/null +++ b/examples/inittab @@ -0,0 +1,90 @@ +# /etc/inittab init(8) configuration for BusyBox +# +# Copyright (C) 1999-2004 by Erik Andersen +# +# +# Note, BusyBox init doesn't support runlevels. The runlevels field is +# completely ignored by BusyBox init. If you want runlevels, use sysvinit. +# +# +# Format for each entry: ::: +# +# : WARNING: This field has a non-traditional meaning for BusyBox init! +# +# The id field is used by BusyBox init to specify the controlling tty for +# the specified process to run on. The contents of this field are +# appended to "/dev/" and used as-is. There is no need for this field to +# be unique, although if it isn't you may have strange results. If this +# field is left blank, it is completely ignored. Also note that if +# BusyBox detects that a serial console is in use, then all entries +# containing non-empty id fields will be ignored. BusyBox init does +# nothing with utmp. We don't need no stinkin' utmp. +# +# : The runlevels field is completely ignored. +# +# : Valid actions include: sysinit, respawn, askfirst, wait, once, +# restart, ctrlaltdel, and shutdown. +# +# Note: askfirst acts just like respawn, but before running the specified +# process it displays the line "Please press Enter to activate this +# console." and then waits for the user to press enter before starting +# the specified process. +# +# Note: unrecognised actions (like initdefault) will cause init to emit +# an error message, and then go along with its business. +# +# : Specifies the process to be executed and it's command line. +# +# Note: BusyBox init works just fine without an inittab. If no inittab is +# found, it has the following default behavior: +# ::sysinit:/etc/init.d/rcS +# ::askfirst:/bin/sh +# ::ctrlaltdel:/sbin/reboot +# ::shutdown:/sbin/swapoff -a +# ::shutdown:/bin/umount -a -r +# ::restart:/sbin/init +# +# if it detects that /dev/console is _not_ a serial console, it will +# also run: +# tty2::askfirst:/bin/sh +# tty3::askfirst:/bin/sh +# tty4::askfirst:/bin/sh +# +# Boot-time system configuration/initialization script. +# This is run first except when booting in single-user mode. +# +::sysinit:/etc/init.d/rcS + +# /bin/sh invocations on selected ttys +# +# Note below that we prefix the shell commands with a "-" to indicate to the +# shell that it is supposed to be a login shell. Normally this is handled by +# login, but since we are bypassing login in this case, BusyBox lets you do +# this yourself... +# +# Start an "askfirst" shell on the console (whatever that may be) +::askfirst:-/bin/sh +# Start an "askfirst" shell on /dev/tty2-4 +tty2::askfirst:-/bin/sh +tty3::askfirst:-/bin/sh +tty4::askfirst:-/bin/sh + +# /sbin/getty invocations for selected ttys +tty4::respawn:/sbin/getty 38400 tty5 +tty5::respawn:/sbin/getty 38400 tty6 + +# Example of how to put a getty on a serial line (for a terminal) +#::respawn:/sbin/getty -L ttyS0 9600 vt100 +#::respawn:/sbin/getty -L ttyS1 9600 vt100 +# +# Example how to put a getty on a modem line. +#::respawn:/sbin/getty 57600 ttyS2 + +# Stuff to do when restarting the init process +::restart:/sbin/init + +# Stuff to do before rebooting +::ctrlaltdel:/sbin/reboot +::shutdown:/bin/umount -a -r +::shutdown:/sbin/swapoff -a + diff --git a/examples/mk2knr.pl b/examples/mk2knr.pl new file mode 100755 index 000000000..1259b8436 --- /dev/null +++ b/examples/mk2knr.pl @@ -0,0 +1,84 @@ +#!/usr/bin/perl -w +# +# @(#) mk2knr.pl - generates a perl script that converts lexemes to K&R-style +# +# How to use this script: +# - In the busybox directory type 'examples/mk2knr.pl files-to-convert' +# - Review the 'convertme.pl' script generated and remove / edit any of the +# substitutions in there (please especially check for false positives) +# - Type './convertme.pl same-files-as-before' +# - Compile and see if it works +# +# BUGS: This script does not ignore strings inside comments or strings inside +# quotes (it probably should). + +# set this to something else if you want +$convertme = 'convertme.pl'; + +# internal-use variables (don't touch) +$convert = 0; +%converted = (); + +# if no files were specified, print usage +die "usage: $0 file.c | file.h\n" if scalar(@ARGV) == 0; + +# prepare the "convert me" file +open(CM, ">$convertme") or die "convertme.pl $!"; +print CM "#!/usr/bin/perl -p -i\n\n"; + +# process each file passed on the cmd line +while (<>) { + + # if the line says "getopt" in it anywhere, we don't want to muck with it + # because option lists tend to include strings like "cxtzvOf:" which get + # matched by the "check for mixed case" regexps below + next if /getopt/; + + # tokenize the string into just the variables + while (/([a-zA-Z_][a-zA-Z0-9_]*)/g) { + $var = $1; + + # ignore the word "BusyBox" + next if ($var =~ /BusyBox/); + + # this checks for javaStyle or szHungarianNotation + $convert++ if ($var =~ /^[a-z]+[A-Z][a-z]+/); + + # this checks for PascalStyle + $convert++ if ($var =~ /^[A-Z][a-z]+[A-Z][a-z]+/); + + # if we want to add more checks, we can add 'em here, but the above + # checks catch "just enough" and not too much, so prolly not. + + if ($convert) { + $convert = 0; + + # skip ahead if we've already dealt with this one + next if ($converted{$var}); + + # record that we've dealt with this var + $converted{$var} = 1; + + print CM "s/\\b$var\\b/"; # more to come in just a minute + + # change the first letter to lower-case + $var = lcfirst($var); + + # put underscores before all remaining upper-case letters + $var =~ s/([A-Z])/_$1/g; + + # now change the remaining characters to lower-case + $var = lc($var); + + print CM "$var/g;\n"; + } + } +} + +# tidy up and make the $convertme script executable +close(CM); +chmod 0755, $convertme; + +# print a helpful help message +print "Done. Scheduled name changes are in $convertme.\n"; +print "Please review/modify it and then type ./$convertme to do the search & replace.\n"; diff --git a/examples/udhcp/sample.bound b/examples/udhcp/sample.bound new file mode 100755 index 000000000..2a95d8b7d --- /dev/null +++ b/examples/udhcp/sample.bound @@ -0,0 +1,31 @@ +#!/bin/sh +# Sample udhcpc renew script + +RESOLV_CONF="/etc/udhcpc/resolv.conf" + +[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" +[ -n "$subnet" ] && NETMASK="netmask $subnet" + +/sbin/ifconfig $interface $ip $BROADCAST $NETMASK + +if [ -n "$router" ] +then + echo "deleting routers" + while /sbin/route del default gw 0.0.0.0 dev $interface + do : + done + + metric=0 + for i in $router + do + /sbin/route add default gw $i dev $interface metric $((metric++)) + done +fi + +echo -n > $RESOLV_CONF +[ -n "$domain" ] && echo domain $domain >> $RESOLV_CONF +for i in $dns +do + echo adding dns $i + echo nameserver $i >> $RESOLV_CONF +done \ No newline at end of file diff --git a/examples/udhcp/sample.deconfig b/examples/udhcp/sample.deconfig new file mode 100755 index 000000000..b221bcf12 --- /dev/null +++ b/examples/udhcp/sample.deconfig @@ -0,0 +1,4 @@ +#!/bin/sh +# Sample udhcpc deconfig script + +/sbin/ifconfig $interface 0.0.0.0 diff --git a/examples/udhcp/sample.nak b/examples/udhcp/sample.nak new file mode 100755 index 000000000..f4d08e669 --- /dev/null +++ b/examples/udhcp/sample.nak @@ -0,0 +1,4 @@ +#!/bin/sh +# Sample udhcpc nak script + +echo Received a NAK: $message diff --git a/examples/udhcp/sample.renew b/examples/udhcp/sample.renew new file mode 100755 index 000000000..842bafe91 --- /dev/null +++ b/examples/udhcp/sample.renew @@ -0,0 +1,31 @@ +#!/bin/sh +# Sample udhcpc bound script + +RESOLV_CONF="/etc/udhcpc/resolv.conf" + +[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" +[ -n "$subnet" ] && NETMASK="netmask $subnet" + +/sbin/ifconfig $interface $ip $BROADCAST $NETMASK + +if [ -n "$router" ] +then + echo "deleting routers" + while /sbin/route del default gw 0.0.0.0 dev $interface + do : + done + + metric=0 + for i in $router + do + /sbin/route add default gw $i dev $interface metric $((metric++)) + done +fi + +echo -n > $RESOLV_CONF +[ -n "$domain" ] && echo domain $domain >> $RESOLV_CONF +for i in $dns +do + echo adding dns $i + echo nameserver $i >> $RESOLV_CONF +done \ No newline at end of file diff --git a/examples/udhcp/sample.script b/examples/udhcp/sample.script new file mode 100644 index 000000000..9b717ac3c --- /dev/null +++ b/examples/udhcp/sample.script @@ -0,0 +1,7 @@ +#!/bin/sh +# Currently, we only dispatch according to command. However, a more +# elaborate system might dispatch by command and interface or do some +# common initialization first, especially if more dhcp event notifications +# are added. + +exec /usr/share/udhcpc/sample.$1 diff --git a/examples/udhcp/simple.script b/examples/udhcp/simple.script new file mode 100644 index 000000000..98ebc159f --- /dev/null +++ b/examples/udhcp/simple.script @@ -0,0 +1,40 @@ +#!/bin/sh + +# udhcpc script edited by Tim Riker + +[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1 + +RESOLV_CONF="/etc/resolv.conf" +[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" +[ -n "$subnet" ] && NETMASK="netmask $subnet" + +case "$1" in + deconfig) + /sbin/ifconfig $interface 0.0.0.0 + ;; + + renew|bound) + /sbin/ifconfig $interface $ip $BROADCAST $NETMASK + + if [ -n "$router" ] ; then + echo "deleting routers" + while route del default gw 0.0.0.0 dev $interface ; do + : + done + + metric=0 + for i in $router ; do + route add default gw $i dev $interface metric $((metric++)) + done + fi + + echo -n > $RESOLV_CONF + [ -n "$domain" ] && echo search $domain >> $RESOLV_CONF + for i in $dns ; do + echo adding dns $i + echo nameserver $i >> $RESOLV_CONF + done + ;; +esac + +exit 0 diff --git a/examples/udhcp/udhcpd.conf b/examples/udhcp/udhcpd.conf new file mode 100644 index 000000000..f91fddebf --- /dev/null +++ b/examples/udhcp/udhcpd.conf @@ -0,0 +1,123 @@ +# Sample udhcpd configuration file (/etc/udhcpd.conf) + +# The start and end of the IP lease block + +start 192.168.0.20 #default: 192.168.0.20 +end 192.168.0.254 #default: 192.168.0.254 + + +# The interface that udhcpd will use + +interface eth0 #default: eth0 + + +# The maximim number of leases (includes addressesd reserved +# by OFFER's, DECLINE's, and ARP conficts + +#max_leases 254 #default: 254 + + +# If remaining is true (default), udhcpd will store the time +# remaining for each lease in the udhcpd leases file. This is +# for embedded systems that cannot keep time between reboots. +# If you set remaining to no, the absolute time that the lease +# expires at will be stored in the dhcpd.leases file. + +#remaining yes #default: yes + + +# The time period at which udhcpd will write out a dhcpd.leases +# file. If this is 0, udhcpd will never automatically write a +# lease file. (specified in seconds) + +#auto_time 7200 #default: 7200 (2 hours) + + +# The amount of time that an IP will be reserved (leased) for if a +# DHCP decline message is received (seconds). + +#decline_time 3600 #default: 3600 (1 hour) + + +# The amount of time that an IP will be reserved (leased) for if an +# ARP conflct occurs. (seconds + +#conflict_time 3600 #default: 3600 (1 hour) + + +# How long an offered address is reserved (leased) in seconds + +#offer_time 60 #default: 60 (1 minute) + +# If a lease to be given is below this value, the full lease time is +# instead used (seconds). + +#min_lease 60 #defult: 60 + + +# The location of the leases file + +#lease_file /var/lib/misc/udhcpd.leases #defualt: /var/lib/misc/udhcpd.leases + +# The location of the pid file +#pidfile /var/run/udhcpd.pid #default: /var/run/udhcpd.pid + +# Everytime udhcpd writes a leases file, the below script will be called. +# Useful for writing the lease file to flash every few hours. + +#notify_file #default: (no script) + +#notify_file dumpleases # <--- usefull for debugging + +# The following are bootp specific options, setable by udhcpd. + +#siaddr 192.168.0.22 #default: 0.0.0.0 + +#sname zorak #default: (none) + +#boot_file /var/nfs_root #default: (none) + +# The remainer of options are DHCP options and can be specifed with the +# keyword 'opt' or 'option'. If an option can take multiple items, such +# as the dns option, they can be listed on the same line, or multiple +# lines. The only option with a default is 'lease'. + +#Examles +opt dns 192.168.10.2 192.168.10.10 +option subnet 255.255.255.0 +opt router 192.168.10.2 +opt wins 192.168.10.10 +option dns 129.219.13.81 # appened to above DNS servers for a total of 3 +option domain local +option lease 864000 # 10 days of seconds + + +# Currently supported options, for more info, see options.c +#opt subnet +#opt timezone +#opt router +#opt timesvr +#opt namesvr +#opt dns +#opt logsvr +#opt cookiesvr +#opt lprsvr +#opt bootsize +#opt domain +#opt swapsvr +#opt rootpath +#opt ipttl +#opt mtu +#opt broadcast +#opt wins +#opt lease +#opt ntpsrv +#opt tftp +#opt bootfile + + +# Static leases map +#static_lease 00:60:08:11:CE:4E 192.168.0.54 +#static_lease 00:60:08:11:CE:3E 192.168.0.44 + + diff --git a/examples/undeb b/examples/undeb new file mode 100644 index 000000000..37104e9d8 --- /dev/null +++ b/examples/undeb @@ -0,0 +1,53 @@ +#!/bin/sh +# +# This should work with the GNU version of tar and gzip! +# This should work with the bash or ash shell! +# Requires the programs (ar, tar, gzip, and the pager more or less). +# +usage() { +echo "Usage: undeb -c package.deb " +echo " undeb -l package.deb " +echo " undeb -x package.deb /foo/boo " +exit +} + +deb=$2 + +exist() { +if [ "$deb" = "" ]; then +usage +elif [ ! -s "$deb" ]; then +echo "Can't find $deb!" +exit +fi +} + +if [ "$1" = "" ]; then +usage +elif [ "$1" = "-l" ]; then +exist +type more >/dev/null 2>&1 && pager=more +type less >/dev/null 2>&1 && pager=less +[ "$pager" = "" ] && echo "No pager found!" && exit +(ar -p $deb control.tar.gz | tar -xzO *control ; echo -e "\nPress enter to scroll, q to Quit!\n" ; ar -p $deb data.tar.gz | tar -tzv) | $pager +exit +elif [ "$1" = "-c" ]; then +exist +ar -p $deb control.tar.gz | tar -xzO *control +exit +elif [ "$1" = "-x" ]; then +exist +if [ "$3" = "" ]; then +usage +elif [ ! -d "$3" ]; then +echo "No such directory $3!" +exit +fi +ar -p $deb data.tar.gz | tar -xzvpf - -C $3 || exit +echo +echo "Extracted $deb to $3!" +exit +else +usage +fi diff --git a/examples/unrpm b/examples/unrpm new file mode 100644 index 000000000..7fd3676f6 --- /dev/null +++ b/examples/unrpm @@ -0,0 +1,48 @@ +#!/bin/sh +# +# This should work with the GNU version of cpio and gzip! +# This should work with the bash or ash shell! +# Requires the programs (cpio, gzip, and the pager more or less). +# +usage() { +echo "Usage: unrpm -l package.rpm " +echo " unrpm -x package.rpm /foo/boo " +exit +} + +rpm=$2 + +exist() { +if [ "$rpm" = "" ]; then +usage +elif [ ! -s "$rpm" ]; then +echo "Can't find $rpm!" +exit +fi +} + +if [ "$1" = "" ]; then +usage +elif [ "$1" = "-l" ]; then +exist +type more >/dev/null 2>&1 && pager=more +type less >/dev/null 2>&1 && pager=less +[ "$pager" = "" ] && echo "No pager found!" && exit +(echo -e "\nPress enter to scroll, q to Quit!\n" ; rpm2cpio $rpm | cpio -tv --quiet) | $pager +exit +elif [ "$1" = "-x" ]; then +exist +if [ "$3" = "" ]; then +usage +elif [ ! -d "$3" ]; then +echo "No such directory $3!" +exit +fi +rpm2cpio $rpm | (umask 0 ; cd $3 ; cpio -idmuv) || exit +echo +echo "Extracted $rpm to $3!" +exit +else +usage +fi diff --git a/examples/zcip.script b/examples/zcip.script new file mode 100644 index 000000000..988e542a4 --- /dev/null +++ b/examples/zcip.script @@ -0,0 +1,38 @@ +#!/bin/sh + +# only for use as a "zcip" callback script +if [ "x$interface" = x ] +then + exit 1 +fi + +# zcip should start on boot/resume and various media changes +case "$1" in +init) + # for now, zcip requires the link to be already up, + # and it drops links when they go down. that isn't + # the most robust model... + exit 0 + ;; +config) + if [ "x$ip" = x ] + then + exit 1 + fi + # remember $ip for $interface, to use on restart + if [ "x$IP" != x -a -w "$IP.$interface" ] + then + echo $ip > "$IP.$interface" + fi + exec ip address add dev $interface \ + scope link local "$ip/16" broadcast + + ;; +deconfig) + if [ x$ip = x ] + then + exit 1 + fi + exec ip address del dev $interface local $ip + ;; +esac +exit 1 diff --git a/findutils/Config.in b/findutils/Config.in new file mode 100644 index 000000000..5a4476a98 --- /dev/null +++ b/findutils/Config.in @@ -0,0 +1,159 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Finding Utilities" + +config FIND + bool "find" + default n + help + find is used to search your system to find specified files. + +config FEATURE_FIND_PRINT0 + bool "Enable -print0 option" + default y + depends on FIND + help + Causes output names to be separated by a null character + rather than a newline. This allows names that contain + newlines and other whitespace to be more easily + interpreted by other programs. + +config FEATURE_FIND_MTIME + bool "Enable modified time matching (-mtime) option" + default y + depends on FIND + help + Allow searching based on the modification time of + files, in days. + +config FEATURE_FIND_MMIN + bool "Enable modified time matching (-min) option" + default y + depends on FIND + help + Allow searching based on the modification time of + files, in minutes. + +config FEATURE_FIND_PERM + bool "Enable permissions matching (-perm) option" + default y + depends on FIND + help + Enable searching based on file permissions. + +config FEATURE_FIND_TYPE + bool "Enable filetype matching (-type) option" + default y + depends on FIND + help + Enable searching based on file type (file, + directory, socket, device, etc.). + +config FEATURE_FIND_XDEV + bool "Enable stay in filesystem (-xdev) option" + default y + depends on FIND + help + This option will allow find to restrict searches to a single + filesystem. + +config FEATURE_FIND_NEWER + bool "Enable -newer option for comparing file mtimes" + default y + depends on FIND + help + Support the 'find -newer' option for finding any files which have + a modified time that is more recent than the specified FILE. + +config FEATURE_FIND_INUM + bool "Enable inode number matching (-inum) option" + default y + depends on FIND + help + Support the 'find -inum' option for searching by inode number. + +config FEATURE_FIND_EXEC + bool "Enable (-exec) option allowing execution of commands" + default y + depends on FIND + help + Support the 'find -exec' option for executing commands based upon + the files matched. + +config GREP + bool "grep" + default n + help + grep is used to search files for a specified pattern. + +config FEATURE_GREP_EGREP_ALIAS + bool "Support extended regular expressions (egrep & grep -E)" + default y + depends on GREP + help + Enabled support for extended regular expressions. Extended + regular expressions allow for alternation (foo|bar), grouping, + and various repetition operators. + +config FEATURE_GREP_FGREP_ALIAS + bool "Alias fgrep to grep -F" + default y + depends on GREP + help + fgrep sees the search pattern as a normal string rather than + regular expressions. + grep -F is always builtin, this just creates the fgrep alias. + +config FEATURE_GREP_CONTEXT + bool "Enable before and after context flags (-A, -B and -C)" + default y + depends on GREP + help + Print the specified number of leading (-B) and/or trailing (-A) + context surrounding our matching lines. + Print the specified number of context lines (-C). + +config XARGS + bool "xargs" + default n + help + xargs is used to execute a specified command on + every item from standard input. + +config FEATURE_XARGS_SUPPORT_CONFIRMATION + bool "Enable prompt and confirmation option -p" + default n + depends on XARGS + help + Support prompt the user about whether to run each command + line and read a line from the terminal. + +config FEATURE_XARGS_SUPPORT_QUOTES + bool "Enable support single and double quotes and backslash" + default n + depends on XARGS + help + Default xargs unsupport single and double quotes + and backslash for can use aruments with spaces. + +config FEATURE_XARGS_SUPPORT_TERMOPT + bool "Enable support options -x" + default n + depends on XARGS + help + Enable support exit if the size (see the -s or -n option) + is exceeded. + +config FEATURE_XARGS_SUPPORT_ZERO_TERM + bool "Enable null terminated option -0" + default n + depends on XARGS + help + Enable input filenames are terminated by a null character + instead of by whitespace, and the quotes and backslash + are not special. + +endmenu diff --git a/findutils/Kbuild b/findutils/Kbuild new file mode 100644 index 000000000..7b504bacf --- /dev/null +++ b/findutils/Kbuild @@ -0,0 +1,10 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_FIND) += find.o +lib-$(CONFIG_GREP) += grep.o +lib-$(CONFIG_XARGS) += xargs.o diff --git a/findutils/find.c b/findutils/find.c new file mode 100644 index 000000000..bf6b71a83 --- /dev/null +++ b/findutils/find.c @@ -0,0 +1,582 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini find implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Reworked by David Douthitt and + * Matt Kraai . + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +/* findutils-4.1.20: + * + * # find file.txt -exec 'echo {}' '{} {}' ';' + * find: echo file.txt: No such file or directory + * # find file.txt -exec 'echo' '{} {}' '; ' + * find: missing argument to `-exec' + * # find file.txt -exec 'echo {}' '{} {}' ';' junk + * find: paths must precede expression + * # find file.txt -exec 'echo {}' '{} {}' ';' junk ';' + * find: paths must precede expression + * # find file.txt -exec 'echo' '{} {}' ';' + * file.txt file.txt + * (strace: execve("/bin/echo", ["echo", "file.txt file.txt"], [ 30 vars ])) + * # find file.txt -exec 'echo' '{} {}' ';' -print -exec pwd ';' + * file.txt file.txt + * file.txt + * /tmp + * # find -name '*.c' -o -name '*.h' + * [shows files, *.c and *.h intermixed] + * # find file.txt -name '*f*' -o -name '*t*' + * file.txt + * # find file.txt -name '*z*' -o -name '*t*' + * file.txt + * # find file.txt -name '*f*' -o -name '*z*' + * file.txt + * + * # find t z -name '*t*' -print -o -name '*z*' + * t + * # find t z t z -name '*t*' -o -name '*z*' -print + * z + * z + * # find t z t z '(' -name '*t*' -o -name '*z*' ')' -o -print + * (no output) + */ + +#include "busybox.h" +#include + +USE_FEATURE_FIND_XDEV(static dev_t *xdev_dev;) +USE_FEATURE_FIND_XDEV(static int xdev_count;) + +typedef int (*action_fp)(const char *fileName, struct stat *statbuf, void *); + +typedef struct { + action_fp f; +} action; +#define ACTS(name, arg...) typedef struct { action a; arg; } action_##name; +#define ACTF(name) static int func_##name(const char *fileName, struct stat *statbuf, action_##name* ap) + ACTS(print) + ACTS(name, char *pattern;) +USE_FEATURE_FIND_PRINT0(ACTS(print0)) +USE_FEATURE_FIND_TYPE( ACTS(type, int type_mask;)) +USE_FEATURE_FIND_PERM( ACTS(perm, char perm_char; int perm_mask;)) +USE_FEATURE_FIND_MTIME( ACTS(mtime, char mtime_char; int mtime_days;)) +USE_FEATURE_FIND_MMIN( ACTS(mmin, char mmin_char; int mmin_mins;)) +USE_FEATURE_FIND_NEWER( ACTS(newer, time_t newer_mtime;)) +USE_FEATURE_FIND_INUM( ACTS(inum, ino_t inode_num;)) +USE_FEATURE_FIND_EXEC( ACTS(exec, char **exec_argv; int *subst_count; int exec_argc;)) +USE_DESKTOP( ACTS(paren, action ***subexpr;)) +USE_DESKTOP( ACTS(size, off_t size;)) +USE_DESKTOP( ACTS(prune)) + +static action ***actions; +static int need_print = 1; + +static inline int one_char(const char* str, char c) +{ + return (str[0] == c && str[1] == '\0'); +} + + +static int count_subst(const char *str) +{ + int count = 0; + while ((str = strstr(str, "{}"))) { + count++; + str++; + } + return count; +} + + +static char* subst(const char *src, int count, const char* filename) +{ + char *buf, *dst, *end; + int flen = strlen(filename); + /* we replace each '{}' with filename: growth by strlen-2 */ + buf = dst = xmalloc(strlen(src) + count*(flen-2) + 1); + while ((end = strstr(src, "{}"))) { + memcpy(dst, src, end - src); + dst += end - src; + src = end + 2; + memcpy(dst, filename, flen); + dst += flen; + } + strcpy(dst, src); + return buf; +} + + +static int exec_actions(action ***appp, const char *fileName, struct stat *statbuf) +{ + int cur_group; + int cur_action; + int rc = TRUE; + action **app, *ap; + + cur_group = -1; + while ((app = appp[++cur_group])) { + cur_action = -1; + do { + ap = app[++cur_action]; + } while (ap && (rc = ap->f(fileName, statbuf, ap))); + if (!ap) { + /* all actions in group were successful */ + break; + } + } + return rc; +} + + +ACTF(name) +{ + const char *tmp = strrchr(fileName, '/'); + if (tmp == NULL) + tmp = fileName; + else + tmp++; + return fnmatch(ap->pattern, tmp, FNM_PERIOD) == 0; +} +#if ENABLE_FEATURE_FIND_TYPE +ACTF(type) +{ + return ((statbuf->st_mode & S_IFMT) == ap->type_mask); +} +#endif +#if ENABLE_FEATURE_FIND_PERM +ACTF(perm) +{ + return !((isdigit(ap->perm_char) && (statbuf->st_mode & 07777) == ap->perm_mask) + || (ap->perm_char == '-' && (statbuf->st_mode & ap->perm_mask) == ap->perm_mask) + || (ap->perm_char == '+' && (statbuf->st_mode & ap->perm_mask) != 0)); +} +#endif +#if ENABLE_FEATURE_FIND_MTIME +ACTF(mtime) +{ + time_t file_age = time(NULL) - statbuf->st_mtime; + time_t mtime_secs = ap->mtime_days * 24 * 60 * 60; + return !((isdigit(ap->mtime_char) && file_age >= mtime_secs + && file_age < mtime_secs + 24 * 60 * 60) + || (ap->mtime_char == '+' && file_age >= mtime_secs + 24 * 60 * 60) + || (ap->mtime_char == '-' && file_age < mtime_secs)); +} +#endif +#if ENABLE_FEATURE_FIND_MMIN +ACTF(mmin) +{ + time_t file_age = time(NULL) - statbuf->st_mtime; + time_t mmin_secs = ap->mmin_mins * 60; + return !((isdigit(ap->mmin_char) && file_age >= mmin_secs + && file_age < mmin_secs + 60) + || (ap->mmin_char == '+' && file_age >= mmin_secs + 60) + || (ap->mmin_char == '-' && file_age < mmin_secs)); +} +#endif +#if ENABLE_FEATURE_FIND_NEWER +ACTF(newer) +{ + return (ap->newer_mtime >= statbuf->st_mtime); +} +#endif +#if ENABLE_FEATURE_FIND_INUM +ACTF(inum) +{ + return (statbuf->st_ino != ap->inode_num); +} +#endif +#if ENABLE_FEATURE_FIND_EXEC +ACTF(exec) +{ + int i, rc; + char *argv[ap->exec_argc+1]; + for (i = 0; i < ap->exec_argc; i++) + argv[i] = subst(ap->exec_argv[i], ap->subst_count[i], fileName); + argv[i] = NULL; /* terminate the list */ + errno = 0; + rc = wait4pid(spawn(argv)); + if (errno) + bb_perror_msg("%s", argv[0]); + for (i = 0; i < ap->exec_argc; i++) + free(argv[i]); + return rc == 0; /* return 1 if success */ +} +#endif + +#if ENABLE_FEATURE_FIND_PRINT0 +ACTF(print0) +{ + printf("%s%c", fileName, '\0'); + return TRUE; +} +#endif + +ACTF(print) +{ + puts(fileName); + return TRUE; +} + +#if ENABLE_DESKTOP +ACTF(paren) +{ + return exec_actions(ap->subexpr, fileName, statbuf); +} + +/* + * -prune: if -depth is not given, return true and do not descend + * current dir; if -depth is given, return false with no effect. + * Example: + * find dir -name 'asm-*' -prune -o -name '*.[chS]' -print + */ +ACTF(prune) +{ + return SKIP; +} + +ACTF(size) +{ + return statbuf->st_size == ap->size; +} +#endif + + +static int fileAction(const char *fileName, struct stat *statbuf, void* junk, int depth) +{ + int rc; +#ifdef CONFIG_FEATURE_FIND_XDEV + if (S_ISDIR(statbuf->st_mode) && xdev_count) { + int i; + for (i = 0; i < xdev_count; i++) { + if (xdev_dev[i] != statbuf->st_dev) + return SKIP; + } + } +#endif + rc = exec_actions(actions, fileName, statbuf); + /* Had no explicit -print[0] or -exec? then print */ + if (rc && need_print) + puts(fileName); + /* Cannot return 0: our caller, recursive_action(), + * will perror() and skip dirs (if called on dir) */ + return rc == 0 ? TRUE : rc; +} + + +#if ENABLE_FEATURE_FIND_TYPE +static int find_type(char *type) +{ + int mask = 0; + + switch (type[0]) { + case 'b': + mask = S_IFBLK; + break; + case 'c': + mask = S_IFCHR; + break; + case 'd': + mask = S_IFDIR; + break; + case 'p': + mask = S_IFIFO; + break; + case 'f': + mask = S_IFREG; + break; + case 'l': + mask = S_IFLNK; + break; + case 's': + mask = S_IFSOCK; + break; + } + + if (mask == 0 || type[1] != '\0') + bb_error_msg_and_die(bb_msg_invalid_arg, type, "-type"); + + return mask; +} +#endif + +action*** parse_params(char **argv) +{ + action*** appp; + int cur_group = 0; + int cur_action = 0; + + action* alloc_action(int sizeof_struct, action_fp f) + { + action *ap; + appp[cur_group] = xrealloc(appp[cur_group], (cur_action+2) * sizeof(*appp)); + appp[cur_group][cur_action++] = ap = xmalloc(sizeof_struct); + appp[cur_group][cur_action] = NULL; + ap->f = f; + return ap; + } +#define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name) + + appp = xzalloc(2 * sizeof(*appp)); /* appp[0],[1] == NULL */ + +// Actions have side effects and return a true or false value +// We implement: -print, -print0, -exec + +// The rest are tests. + +// Tests and actions are grouped by operators +// ( expr ) Force precedence +// ! expr True if expr is false +// -not expr Same as ! expr +// expr1 [-a[nd]] expr2 And; expr2 is not evaluated if expr1 is false +// expr1 -o[r] expr2 Or; expr2 is not evaluated if expr1 is true +// expr1 , expr2 List; both expr1 and expr2 are always evaluated +// We implement: (), -a, -o + + while (*argv) { + char *arg = argv[0]; + char *arg1 = argv[1]; + /* --- Operators --- */ + if (strcmp(arg, "-a") == 0 + USE_DESKTOP(|| strcmp(arg, "-and") == 0) + ) { + /* no special handling required */ + } + else if (strcmp(arg, "-o") == 0 + USE_DESKTOP(|| strcmp(arg, "-or") == 0) + ) { + /* start new OR group */ + cur_group++; + appp = xrealloc(appp, (cur_group+2) * sizeof(*appp)); + appp[cur_group] = NULL; + appp[cur_group+1] = NULL; + cur_action = 0; + } + + /* --- Tests and actions --- */ + else if (strcmp(arg, "-print") == 0) { + need_print = 0; + (void) ALLOC_ACTION(print); + } +#if ENABLE_FEATURE_FIND_PRINT0 + else if (strcmp(arg, "-print0") == 0) { + need_print = 0; + (void) ALLOC_ACTION(print0); + } +#endif + else if (strcmp(arg, "-name") == 0) { + action_name *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(name); + ap->pattern = arg1; + } +#if ENABLE_FEATURE_FIND_TYPE + else if (strcmp(arg, "-type") == 0) { + action_type *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(type); + ap->type_mask = find_type(arg1); + } +#endif +#if ENABLE_FEATURE_FIND_PERM +/* TODO: + * -perm mode File's permission bits are exactly mode (octal or symbolic). + * Symbolic modes use mode 0 as a point of departure. + * -perm -mode All of the permission bits mode are set for the file. + * -perm +mode Any of the permission bits mode are set for the file. + */ + else if (strcmp(arg, "-perm") == 0) { + action_perm *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(perm); + ap->perm_mask = xstrtol_range(arg1, 8, 0, 07777); + ap->perm_char = arg1[0]; + if (ap->perm_char == '-') + ap->perm_mask = -ap->perm_mask; + } +#endif +#if ENABLE_FEATURE_FIND_MTIME + else if (strcmp(arg, "-mtime") == 0) { + action_mtime *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(mtime); + ap->mtime_days = xatol(arg1); + ap->mtime_char = arg1[0]; + if (ap->mtime_char == '-') + ap->mtime_days = -ap->mtime_days; + } +#endif +#if ENABLE_FEATURE_FIND_MMIN + else if (strcmp(arg, "-mmin") == 0) { + action_mmin *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(mmin); + ap->mmin_mins = xatol(arg1); + ap->mmin_char = arg1[0]; + if (ap->mmin_char == '-') + ap->mmin_mins = -ap->mmin_mins; + } +#endif +#if ENABLE_FEATURE_FIND_NEWER + else if (strcmp(arg, "-newer") == 0) { + action_newer *ap; + struct stat stat_newer; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + xstat(arg1, &stat_newer); + ap = ALLOC_ACTION(newer); + ap->newer_mtime = stat_newer.st_mtime; + } +#endif +#if ENABLE_FEATURE_FIND_INUM + else if (strcmp(arg, "-inum") == 0) { + action_inum *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(inum); + ap->inode_num = xatoul(arg1); + } +#endif +#if ENABLE_FEATURE_FIND_EXEC + else if (strcmp(arg, "-exec") == 0) { + int i; + action_exec *ap; + need_print = 0; + ap = ALLOC_ACTION(exec); + ap->exec_argv = ++argv; /* first arg after -exec */ + ap->exec_argc = 0; + while (1) { + if (!*argv) /* did not see ';' till end */ + bb_error_msg_and_die(bb_msg_requires_arg, arg); + if (one_char(argv[0], ';')) + break; + argv++; + ap->exec_argc++; + } + if (ap->exec_argc == 0) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap->subst_count = xmalloc(ap->exec_argc * sizeof(int)); + i = ap->exec_argc; + while (i--) + ap->subst_count[i] = count_subst(ap->exec_argv[i]); + } +#endif +#if ENABLE_DESKTOP + else if (one_char(arg, '(')) { + action_paren *ap; + char **endarg; + int nested = 1; + + endarg = argv; + while (1) { + if (!*++endarg) + bb_error_msg_and_die("unpaired '('"); + if (one_char(*endarg, '(')) + nested++; + else if (one_char(*endarg, ')') && !--nested) { + *endarg = NULL; + break; + } + } + ap = ALLOC_ACTION(paren); + ap->subexpr = parse_params(argv + 1); + *endarg = ")"; /* restore NULLed parameter */ + argv = endarg; + } + else if (strcmp(arg, "-prune") == 0) { + (void) ALLOC_ACTION(prune); + } + else if (strcmp(arg, "-size") == 0) { + action_size *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(size); + ap->size = XATOOFF(arg1); + } +#endif + else + bb_show_usage(); + argv++; + } + + return appp; +#undef ALLOC_ACTION +} + + +int find_main(int argc, char **argv) +{ + int dereference = FALSE; + char *arg; + char **argp; + int i, firstopt, status = EXIT_SUCCESS; + + for (firstopt = 1; firstopt < argc; firstopt++) { + if (argv[firstopt][0] == '-') + break; +#if ENABLE_DESKTOP + if (one_char(argv[firstopt], '(')) + break; +#endif + } + if (firstopt == 1) { + argv[0] = "."; + argv--; + firstopt++; + } + +// All options always return true. They always take effect, +// rather than being processed only when their place in the +// expression is reached +// We implement: -follow, -xdev + + /* Process options, and replace then with -a */ + /* (-a will be ignored by recursive parser later) */ + argp = &argv[firstopt]; + while ((arg = argp[0])) { + if (strcmp(arg, "-follow") == 0) { + dereference = TRUE; + argp[0] = "-a"; + } +#if ENABLE_FEATURE_FIND_XDEV + else if (strcmp(arg, "-xdev") == 0) { + struct stat stbuf; + if (!xdev_count) { + xdev_count = firstopt - 1; + xdev_dev = xmalloc(xdev_count * sizeof(dev_t)); + for (i = 1; i < firstopt; i++) { + /* not xstat(): shouldn't bomb out on + * "find not_exist exist -xdev" */ + if (stat(argv[i], &stbuf)) stbuf.st_dev = -1L; + xdev_dev[i-1] = stbuf.st_dev; + } + } + argp[0] = "-a"; + } + argp++; + } +#endif + + actions = parse_params(&argv[firstopt]); + + for (i = 1; i < firstopt; i++) { + if (!recursive_action(argv[i], + TRUE, // recurse + dereference, // follow links + FALSE, // depth first + fileAction, // file action + fileAction, // dir action + NULL, // user data + 0)) // depth + status = EXIT_FAILURE; + } + return status; +} diff --git a/findutils/grep.c b/findutils/grep.c new file mode 100644 index 000000000..8bb38f95c --- /dev/null +++ b/findutils/grep.c @@ -0,0 +1,483 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini grep implementation for busybox using libc regex. + * + * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley + * Copyright (C) 1999,2000,2001 by Mark Whitley + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ +/* BB_AUDIT SUSv3 defects - unsupported option -x. */ +/* BB_AUDIT GNU defects - always acts as -a. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/grep.html */ +/* + * 2004,2006 (C) Vladimir Oleynik - + * correction "-e pattern1 -e pattern2" logic and more optimizations. + * precompiled regex + */ +/* + * (C) 2006 Jac Goudsmit added -o option + */ + +#include "busybox.h" +#include "xregex.h" + + +/* options */ +#define GREP_OPTS "lnqvscFiHhe:f:Lor" +#define GREP_OPT_l (1<<0) +#define PRINT_FILES_WITH_MATCHES (option_mask32 & GREP_OPT_l) +#define GREP_OPT_n (1<<1) +#define PRINT_LINE_NUM (option_mask32 & GREP_OPT_n) +#define GREP_OPT_q (1<<2) +#define BE_QUIET (option_mask32 & GREP_OPT_q) +#define GREP_OPT_v (1<<3) +#define GREP_OPT_s (1<<4) +#define SUPPRESS_ERR_MSGS (option_mask32 & GREP_OPT_s) +#define GREP_OPT_c (1<<5) +#define PRINT_MATCH_COUNTS (option_mask32 & GREP_OPT_c) +#define GREP_OPT_F (1<<6) +#define FGREP_FLAG (option_mask32 & GREP_OPT_F) +#define GREP_OPT_i (1<<7) +#define GREP_OPT_H (1<<8) +#define GREP_OPT_h (1<<9) +#define GREP_OPT_e (1<<10) +#define GREP_OPT_f (1<<11) +#define GREP_OPT_L (1<<12) +#define PRINT_FILES_WITHOUT_MATCHES (option_mask32 & GREP_OPT_L) +#define GREP_OPT_o (1<<13) +#define GREP_OPT_r (1<<14) +#if ENABLE_FEATURE_GREP_CONTEXT +# define GREP_OPT_CONTEXT "A:B:C:" +# define GREP_OPT_A (1<<15) +# define GREP_OPT_B (1<<16) +# define GREP_OPT_C (1<<17) +# define GREP_OPT_E (1<<18) +#else +# define GREP_OPT_CONTEXT "" +# define GREP_OPT_A 0 +# define GREP_OPT_B 0 +# define GREP_OPT_C 0 +# define GREP_OPT_E (1<<15) +#endif +#if ENABLE_FEATURE_GREP_EGREP_ALIAS +# define OPT_EGREP "E" +#else +# define OPT_EGREP "" +#endif + +typedef unsigned char byte_t; + +static int reflags; +static byte_t invert_search; +static byte_t print_filename; +static byte_t open_errors; + +#if ENABLE_FEATURE_GREP_CONTEXT +static int lines_before; +static int lines_after; +static char **before_buf; +static int last_line_printed; +#endif /* ENABLE_FEATURE_GREP_CONTEXT */ + +/* globals used internally */ +static llist_t *pattern_head; /* growable list of patterns to match */ +static const char *cur_file; /* the current file we are reading */ + +typedef struct GREP_LIST_DATA { + char *pattern; + regex_t preg; +#define PATTERN_MEM_A 1 +#define COMPILED 2 + int flg_mem_alocated_compiled; +} grep_list_data_t; + +static void print_line(const char *line, int linenum, char decoration) +{ +#if ENABLE_FEATURE_GREP_CONTEXT + /* possibly print the little '--' separator */ + if ((lines_before || lines_after) && last_line_printed && + last_line_printed < linenum - 1) { + puts("--"); + } + last_line_printed = linenum; +#endif + if (print_filename) + printf("%s%c", cur_file, decoration); + if (PRINT_LINE_NUM) + printf("%i%c", linenum, decoration); + /* Emulate weird GNU grep behavior with -ov */ + if ((option_mask32 & (GREP_OPT_v+GREP_OPT_o)) != (GREP_OPT_v+GREP_OPT_o)) + puts(line); +} + + +static int grep_file(FILE *file) +{ + char *line; + byte_t ret; + int linenum = 0; + int nmatches = 0; + regmatch_t regmatch; +#if ENABLE_FEATURE_GREP_CONTEXT + int print_n_lines_after = 0; + int curpos = 0; /* track where we are in the circular 'before' buffer */ + int idx = 0; /* used for iteration through the circular buffer */ +#endif /* ENABLE_FEATURE_GREP_CONTEXT */ + + while ((line = xmalloc_getline(file)) != NULL) { + llist_t *pattern_ptr = pattern_head; + grep_list_data_t * gl; + + linenum++; + ret = 0; + while (pattern_ptr) { + gl = (grep_list_data_t *)pattern_ptr->data; + if (FGREP_FLAG) { + ret = strstr(line, gl->pattern) != NULL; + } else { + /* + * test for a postitive-assertion match (regexec returns success (0) + * and the user did not specify invert search), or a negative-assertion + * match (regexec returns failure (REG_NOMATCH) and the user specified + * invert search) + */ + if (!(gl->flg_mem_alocated_compiled & COMPILED)) { + gl->flg_mem_alocated_compiled |= COMPILED; + xregcomp(&(gl->preg), gl->pattern, reflags); + } + regmatch.rm_so = 0; + regmatch.rm_eo = 0; + ret |= regexec(&(gl->preg), line, 1, ®match, 0) == 0; + } + pattern_ptr = pattern_ptr->link; + } /* while (pattern_ptr) */ + + if (ret ^ invert_search) { + + if (PRINT_FILES_WITH_MATCHES || BE_QUIET) + free(line); + + /* if we found a match but were told to be quiet, stop here */ + if (BE_QUIET || PRINT_FILES_WITHOUT_MATCHES) + return -1; + + /* keep track of matches */ + nmatches++; + + /* if we're just printing filenames, we stop after the first match */ + if (PRINT_FILES_WITH_MATCHES) + break; + + /* print the matched line */ + if (PRINT_MATCH_COUNTS == 0) { +#if ENABLE_FEATURE_GREP_CONTEXT + int prevpos = (curpos == 0) ? lines_before - 1 : curpos - 1; + + /* if we were told to print 'before' lines and there is at least + * one line in the circular buffer, print them */ + if (lines_before && before_buf[prevpos] != NULL) { + int first_buf_entry_line_num = linenum - lines_before; + + /* advance to the first entry in the circular buffer, and + * figure out the line number is of the first line in the + * buffer */ + idx = curpos; + while (before_buf[idx] == NULL) { + idx = (idx + 1) % lines_before; + first_buf_entry_line_num++; + } + + /* now print each line in the buffer, clearing them as we go */ + while (before_buf[idx] != NULL) { + print_line(before_buf[idx], first_buf_entry_line_num, '-'); + free(before_buf[idx]); + before_buf[idx] = NULL; + idx = (idx + 1) % lines_before; + first_buf_entry_line_num++; + } + } + + /* make a note that we need to print 'after' lines */ + print_n_lines_after = lines_after; +#endif + if (option_mask32 & GREP_OPT_o) { + line[regmatch.rm_eo] = '\0'; + print_line(line + regmatch.rm_so, linenum, ':'); + } else { + print_line(line, linenum, ':'); + } + } + } +#if ENABLE_FEATURE_GREP_CONTEXT + else { /* no match */ + /* Add the line to the circular 'before' buffer */ + if (lines_before) { + free(before_buf[curpos]); + before_buf[curpos] = xstrdup(line); + curpos = (curpos + 1) % lines_before; + } + } + + /* if we need to print some context lines after the last match, do so */ + if (print_n_lines_after && (last_line_printed != linenum)) { + print_line(line, linenum, '-'); + print_n_lines_after--; + } +#endif /* ENABLE_FEATURE_GREP_CONTEXT */ + free(line); + } + + /* special-case file post-processing for options where we don't print line + * matches, just filenames and possibly match counts */ + + /* grep -c: print [filename:]count, even if count is zero */ + if (PRINT_MATCH_COUNTS) { + if (print_filename) + printf("%s:", cur_file); + printf("%d\n", nmatches); + } + + /* grep -l: print just the filename, but only if we grepped the line in the file */ + if (PRINT_FILES_WITH_MATCHES && nmatches > 0) { + puts(cur_file); + } + + /* grep -L: print just the filename, but only if we didn't grep the line in the file */ + if (PRINT_FILES_WITHOUT_MATCHES && nmatches == 0) { + puts(cur_file); + } + + return nmatches; +} + +#if ENABLE_FEATURE_CLEAN_UP +#define new_grep_list_data(p, m) add_grep_list_data(p, m) +static char * add_grep_list_data(char *pattern, int flg_used_mem) +#else +#define new_grep_list_data(p, m) add_grep_list_data(p) +static char * add_grep_list_data(char *pattern) +#endif +{ + grep_list_data_t *gl = xmalloc(sizeof(grep_list_data_t)); + gl->pattern = pattern; +#if ENABLE_FEATURE_CLEAN_UP + gl->flg_mem_alocated_compiled = flg_used_mem; +#else + gl->flg_mem_alocated_compiled = 0; +#endif + return (char *)gl; +} + + +static void load_regexes_from_file(llist_t *fopt) +{ + char *line; + FILE *f; + + while (fopt) { + llist_t *cur = fopt; + char *ffile = cur->data; + + fopt = cur->link; + free(cur); + f = xfopen(ffile, "r"); + while ((line = xmalloc_getline(f)) != NULL) { + llist_add_to(&pattern_head, + new_grep_list_data(line, PATTERN_MEM_A)); + } + } +} + + +static int file_action_grep(const char *filename, struct stat *statbuf, void* matched, int depth) +{ + FILE *file = fopen(filename, "r"); + if (file == NULL) { + if (!SUPPRESS_ERR_MSGS) + bb_perror_msg("%s", cur_file); + open_errors = 1; + return 0; + } + cur_file = filename; + *(int*)matched += grep_file(file); + fclose(file); + return 1; +} + + +static int grep_dir(const char *dir) +{ + int matched = 0; + recursive_action(dir, + /* recurse= */ 1, + /* followLinks= */ 0, + /* depthFirst= */ 1, + /* fileAction= */ file_action_grep, + /* dirAction= */ NULL, + /* userData= */ &matched, + /* depth= */ 0); + return matched; +} + + +int grep_main(int argc, char **argv) +{ + FILE *file; + int matched; + llist_t *fopt = NULL; + + /* do normal option parsing */ +#if ENABLE_FEATURE_GREP_CONTEXT + char *slines_after; + char *slines_before; + char *Copt; + + opt_complementary = "H-h:e::f::C-AB"; + getopt32(argc, argv, + GREP_OPTS GREP_OPT_CONTEXT OPT_EGREP, + &pattern_head, &fopt, + &slines_after, &slines_before, &Copt); + + if (option_mask32 & GREP_OPT_C) { + /* -C unsets prev -A and -B, but following -A or -B + may override it */ + if (!(option_mask32 & GREP_OPT_A)) /* not overridden */ + slines_after = Copt; + if (!(option_mask32 & GREP_OPT_B)) /* not overridden */ + slines_before = Copt; + option_mask32 |= GREP_OPT_A|GREP_OPT_B; /* for parser */ + } + if (option_mask32 & GREP_OPT_A) { + lines_after = xatoi_u(slines_after); + } + if (option_mask32 & GREP_OPT_B) { + lines_before = xatoi_u(slines_before); + } + /* sanity checks */ + if (option_mask32 & (GREP_OPT_c|GREP_OPT_q|GREP_OPT_l|GREP_OPT_L)) { + option_mask32 &= ~GREP_OPT_n; + lines_before = 0; + lines_after = 0; + } else if (lines_before > 0) + before_buf = (char **)xzalloc(lines_before * sizeof(char *)); +#else + /* with auto sanity checks */ + opt_complementary = "H-h:e::f::c-n:q-n:l-n"; + getopt32(argc, argv, GREP_OPTS OPT_EGREP, + &pattern_head, &fopt); +#endif + invert_search = ((option_mask32 & GREP_OPT_v) != 0); /* 0 | 1 */ + + if (pattern_head != NULL) { + /* convert char *argv[] to grep_list_data_t */ + llist_t *cur; + + for (cur = pattern_head; cur; cur = cur->link) + cur->data = new_grep_list_data(cur->data, 0); + } + if (option_mask32 & GREP_OPT_f) + load_regexes_from_file(fopt); + + if (ENABLE_FEATURE_GREP_FGREP_ALIAS && applet_name[0] == 'f') + option_mask32 |= GREP_OPT_F; + + if (!(option_mask32 & GREP_OPT_o)) + reflags = REG_NOSUB; + + if (ENABLE_FEATURE_GREP_EGREP_ALIAS && + (applet_name[0] == 'e' || (option_mask32 & GREP_OPT_E))) + reflags |= REG_EXTENDED; + + if (option_mask32 & GREP_OPT_i) + reflags |= REG_ICASE; + + argv += optind; + argc -= optind; + + /* if we didn't get a pattern from a -e and no command file was specified, + * argv[optind] should be the pattern. no pattern, no worky */ + if (pattern_head == NULL) { + if (*argv == NULL) + bb_show_usage(); + else { + char *pattern = new_grep_list_data(*argv++, 0); + + llist_add_to(&pattern_head, pattern); + argc--; + } + } + + /* argv[(optind)..(argc-1)] should be names of file to grep through. If + * there is more than one file to grep, we will print the filenames. */ + if (argc > 1) + print_filename = 1; + /* -H / -h of course override */ + if (option_mask32 & GREP_OPT_H) + print_filename = 1; + if (option_mask32 & GREP_OPT_h) + print_filename = 0; + + /* If no files were specified, or '-' was specified, take input from + * stdin. Otherwise, we grep through all the files specified. */ + if (argc == 0) + argc++; + matched = 0; + while (argc--) { + cur_file = *argv++; + file = stdin; + if (!cur_file || (*cur_file == '-' && !cur_file[1])) { + cur_file = "(standard input)"; + } else { + if (option_mask32 & GREP_OPT_r) { + struct stat st; + if (stat(cur_file, &st) == 0 && S_ISDIR(st.st_mode)) { + if (!(option_mask32 & GREP_OPT_h)) + print_filename = 1; + matched += grep_dir(cur_file); + goto grep_done; + } + } + /* else: fopen(dir) will succeed, but reading won't */ + file = fopen(cur_file, "r"); + if (file == NULL) { + if (!SUPPRESS_ERR_MSGS) + bb_perror_msg("%s", cur_file); + open_errors = 1; + continue; + } + } + matched += grep_file(file); + fclose_if_not_stdin(file); + grep_done: + if (matched < 0) { + /* we found a match but were told to be quiet, stop here and + * return success */ + break; + } + } + + /* destroy all the elments in the pattern list */ + if (ENABLE_FEATURE_CLEAN_UP) { + while (pattern_head) { + llist_t *pattern_head_ptr = pattern_head; + grep_list_data_t *gl = + (grep_list_data_t *)pattern_head_ptr->data; + + pattern_head = pattern_head->link; + if ((gl->flg_mem_alocated_compiled & PATTERN_MEM_A)) + free(gl->pattern); + if ((gl->flg_mem_alocated_compiled & COMPILED)) + regfree(&(gl->preg)); + free(pattern_head_ptr); + } + } + /* 0 = success, 1 = failed, 2 = error */ + /* If the -q option is specified, the exit status shall be zero + * if an input line is selected, even if an error was detected. */ + if (BE_QUIET && matched) + return 0; + if (open_errors) + return 2; + return !matched; /* invert return value 0 = success, 1 = failed */ +} diff --git a/findutils/xargs.c b/findutils/xargs.c new file mode 100644 index 000000000..029d4077d --- /dev/null +++ b/findutils/xargs.c @@ -0,0 +1,539 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini xargs implementation for busybox + * Options are supported: "-prtx -n max_arg -s max_chars -e[ouf_str]" + * + * (C) 2002,2003 by Vladimir Oleynik + * + * Special thanks + * - Mark Whitley and Glenn McGrath for stimulus to rewrite :) + * - Mike Rendell + * and David MacKenzie . + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * xargs is described in the Single Unix Specification v3 at + * http://www.opengroup.org/onlinepubs/007904975/utilities/xargs.html + * + */ + +#include "busybox.h" + +/* COMPAT: SYSV version defaults size (and has a max value of) to 470. + We try to make it as large as possible. */ +#if !defined(ARG_MAX) && defined(_SC_ARG_MAX) +#define ARG_MAX sysconf (_SC_ARG_MAX) +#endif +#ifndef ARG_MAX +#define ARG_MAX 470 +#endif + + +#ifdef TEST +# ifndef CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION +# define CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION +# endif +# ifndef CONFIG_FEATURE_XARGS_SUPPORT_QUOTES +# define CONFIG_FEATURE_XARGS_SUPPORT_QUOTES +# endif +# ifndef CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT +# define CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT +# endif +# ifndef CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM +# define CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM +# endif +#endif + +/* + This function has special algorithm. + Don't use fork and include to main! +*/ +static int xargs_exec(char *const *args) +{ + pid_t p; + volatile int exec_errno = 0; /* shared vfork stack */ + int status; + + p = vfork(); + if (p < 0) + bb_perror_msg_and_die("vfork"); + + if (p == 0) { + /* vfork -- child */ + execvp(args[0], args); + exec_errno = errno; /* set error to shared stack */ + _exit(1); + } + + /* vfork -- parent */ + while (wait(&status) == (pid_t) -1) + if (errno != EINTR) + break; + if (exec_errno) { + errno = exec_errno; + bb_perror_msg("%s", args[0]); + return exec_errno == ENOENT ? 127 : 126; + } + if (WEXITSTATUS(status) == 255) { + bb_error_msg("%s: exited with status 255; aborting", args[0]); + return 124; + } + if (WIFSTOPPED(status)) { + bb_error_msg("%s: stopped by signal %d", + args[0], WSTOPSIG(status)); + return 125; + } + if (WIFSIGNALED(status)) { + bb_error_msg("%s: terminated by signal %d", + args[0], WTERMSIG(status)); + return 125; + } + if (WEXITSTATUS(status)) + return 123; + return 0; +} + + +typedef struct xlist_s { + char *data; + size_t lenght; + struct xlist_s *link; +} xlist_t; + +static int eof_stdin_detected; + +#define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#define ISSPACE(c) (ISBLANK(c) || (c) == '\n' || (c) == '\r' \ + || (c) == '\f' || (c) == '\v') + +#ifdef CONFIG_FEATURE_XARGS_SUPPORT_QUOTES +static xlist_t *process_stdin(xlist_t * list_arg, + const char *eof_str, size_t mc, char *buf) +{ +#define NORM 0 +#define QUOTE 1 +#define BACKSLASH 2 +#define SPACE 4 + + char *s = NULL; /* start word */ + char *p = NULL; /* pointer to end word */ + char q = 0; /* quote char */ + char state = NORM; + char eof_str_detected = 0; + size_t line_l = 0; /* size loaded args line */ + int c; /* current char */ + xlist_t *cur; + xlist_t *prev; + + for (prev = cur = list_arg; cur; cur = cur->link) { + line_l += cur->lenght; /* previous allocated */ + if (prev != cur) + prev = prev->link; + } + + while (!eof_stdin_detected) { + c = getchar(); + if (c == EOF) { + eof_stdin_detected++; + if (s) + goto unexpected_eof; + break; + } + if (eof_str_detected) + continue; + if (state == BACKSLASH) { + state = NORM; + goto set; + } else if (state == QUOTE) { + if (c == q) { + q = 0; + state = NORM; + } else { + goto set; + } + } else { /* if(state == NORM) */ + + if (ISSPACE(c)) { + if (s) { +unexpected_eof: + state = SPACE; + c = 0; + goto set; + } + } else { + if (s == NULL) + s = p = buf; + if (c == '\\') { + state = BACKSLASH; + } else if (c == '\'' || c == '"') { + q = c; + state = QUOTE; + } else { +set: + if ((size_t)(p - buf) >= mc) + bb_error_msg_and_die("argument line too long"); + *p++ = c; + } + } + } + if (state == SPACE) { /* word's delimiter or EOF detected */ + if (q) { + bb_error_msg_and_die("unmatched %s quote", + q == '\'' ? "single" : "double"); + } + /* word loaded */ + if (eof_str) { + eof_str_detected = strcmp(s, eof_str) == 0; + } + if (!eof_str_detected) { + size_t lenght = (p - buf); + + cur = xmalloc(sizeof(xlist_t) + lenght); + cur->data = memcpy(cur + 1, s, lenght); + cur->lenght = lenght; + cur->link = NULL; + if (prev == NULL) { + list_arg = cur; + } else { + prev->link = cur; + } + prev = cur; + line_l += lenght; + if (line_l > mc) { + /* stop memory usage :-) */ + break; + } + } + s = NULL; + state = NORM; + } + } + return list_arg; +} +#else +/* The variant does not support single quotes, double quotes or backslash */ +static xlist_t *process_stdin(xlist_t * list_arg, + const char *eof_str, size_t mc, char *buf) +{ + + int c; /* current char */ + int eof_str_detected = 0; + char *s = NULL; /* start word */ + char *p = NULL; /* pointer to end word */ + size_t line_l = 0; /* size loaded args line */ + xlist_t *cur; + xlist_t *prev; + + for (prev = cur = list_arg; cur; cur = cur->link) { + line_l += cur->lenght; /* previous allocated */ + if (prev != cur) + prev = prev->link; + } + + while (!eof_stdin_detected) { + c = getchar(); + if (c == EOF) { + eof_stdin_detected++; + } + if (eof_str_detected) + continue; + if (c == EOF || ISSPACE(c)) { + if (s == NULL) + continue; + c = EOF; + } + if (s == NULL) + s = p = buf; + if ((p - buf) >= mc) + bb_error_msg_and_die("argument line too long"); + *p++ = c == EOF ? 0 : c; + if (c == EOF) { /* word's delimiter or EOF detected */ + /* word loaded */ + if (eof_str) { + eof_str_detected = strcmp(s, eof_str) == 0; + } + if (!eof_str_detected) { + size_t lenght = (p - buf); + + cur = xmalloc(sizeof(xlist_t) + lenght); + cur->data = memcpy(cur + 1, s, lenght); + cur->lenght = lenght; + cur->link = NULL; + if (prev == NULL) { + list_arg = cur; + } else { + prev->link = cur; + } + prev = cur; + line_l += lenght; + if (line_l > mc) { + /* stop memory usage :-) */ + break; + } + s = NULL; + } + } + } + return list_arg; +} +#endif /* CONFIG_FEATURE_XARGS_SUPPORT_QUOTES */ + + +#ifdef CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION +/* Prompt the user for a response, and + if the user responds affirmatively, return true; + otherwise, return false. Used "/dev/tty", not stdin. */ +static int xargs_ask_confirmation(void) +{ + static FILE *tty_stream; + int c, savec; + + if (!tty_stream) { + tty_stream = xfopen(CURRENT_TTY, "r"); + /* pranoidal security by vodz */ + fcntl(fileno(tty_stream), F_SETFD, FD_CLOEXEC); + } + fputs(" ?...", stderr); + fflush(stderr); + c = savec = getc(tty_stream); + while (c != EOF && c != '\n') + c = getc(tty_stream); + if (savec == 'y' || savec == 'Y') + return 1; + return 0; +} +#else +# define xargs_ask_confirmation() 1 +#endif /* CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION */ + +#ifdef CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM +static xlist_t *process0_stdin(xlist_t * list_arg, const char *eof_str ATTRIBUTE_UNUSED, + size_t mc, char *buf) +{ + int c; /* current char */ + char *s = NULL; /* start word */ + char *p = NULL; /* pointer to end word */ + size_t line_l = 0; /* size loaded args line */ + xlist_t *cur; + xlist_t *prev; + + for (prev = cur = list_arg; cur; cur = cur->link) { + line_l += cur->lenght; /* previous allocated */ + if (prev != cur) + prev = prev->link; + } + + while (!eof_stdin_detected) { + c = getchar(); + if (c == EOF) { + eof_stdin_detected++; + if (s == NULL) + break; + c = 0; + } + if (s == NULL) + s = p = buf; + if ((size_t)(p - buf) >= mc) + bb_error_msg_and_die("argument line too long"); + *p++ = c; + if (c == 0) { /* word's delimiter or EOF detected */ + /* word loaded */ + size_t lenght = (p - buf); + + cur = xmalloc(sizeof(xlist_t) + lenght); + cur->data = memcpy(cur + 1, s, lenght); + cur->lenght = lenght; + cur->link = NULL; + if (prev == NULL) { + list_arg = cur; + } else { + prev->link = cur; + } + prev = cur; + line_l += lenght; + if (line_l > mc) { + /* stop memory usage :-) */ + break; + } + s = NULL; + } + } + return list_arg; +} +#endif /* CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM */ + +/* Correct regardless of combination of CONFIG_xxx */ +enum { + OPTBIT_VERBOSE = 0, + OPTBIT_NO_EMPTY, + OPTBIT_UPTO_NUMBER, + OPTBIT_UPTO_SIZE, + OPTBIT_EOF_STRING, + USE_FEATURE_XARGS_SUPPORT_CONFIRMATION(OPTBIT_INTERACTIVE,) + USE_FEATURE_XARGS_SUPPORT_TERMOPT( OPTBIT_TERMINATE ,) + USE_FEATURE_XARGS_SUPPORT_ZERO_TERM( OPTBIT_ZEROTERM ,) + + OPT_VERBOSE = 1< 20 * 1024) + orig_arg_max = 20 * 1024; + n_max_chars = orig_arg_max; + } + max_chars = xmalloc(n_max_chars); + + if (opt & OPT_UPTO_NUMBER) { + n_max_arg = xatoul_range(max_args, 1, INT_MAX); + } else { + n_max_arg = n_max_chars; + } + + while ((list = read_args(list, eof_str, n_max_chars, max_chars)) != NULL || + !(opt & OPT_NO_EMPTY)) + { + opt |= OPT_NO_EMPTY; + n = 0; + n_chars = 0; +#ifdef CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT + for (cur = list; cur;) { + n_chars += cur->lenght; + n++; + cur = cur->link; + if (n_chars > n_max_chars || (n == n_max_arg && cur)) { + if (opt & OPT_TERMINATE) + bb_error_msg_and_die("argument list too long"); + break; + } + } +#else + for (cur = list; cur; cur = cur->link) { + n_chars += cur->lenght; + n++; + if (n_chars > n_max_chars || n == n_max_arg) { + break; + } + } +#endif /* CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT */ + + /* allocate pointers for execvp: + argc*arg, n*arg from stdin, NULL */ + args = xzalloc((n + argc + 1) * sizeof(char *)); + + /* store the command to be executed + (taken from the command line) */ + for (i = 0; i < argc; i++) + args[i] = argv[i]; + /* (taken from stdin) */ + for (cur = list; n; cur = cur->link) { + args[i++] = cur->data; + n--; + } + + if (opt & (OPT_INTERACTIVE | OPT_VERBOSE)) { + for (i = 0; args[i]; i++) { + if (i) + fputc(' ', stderr); + fputs(args[i], stderr); + } + if (!(opt & OPT_INTERACTIVE)) + fputc('\n', stderr); + } + if (!(opt & OPT_INTERACTIVE) || xargs_ask_confirmation()) { + child_error = xargs_exec(args); + } + + /* clean up */ + for (i = argc; args[i]; i++) { + cur = list; + list = list->link; + free(cur); + } + free(args); + if (child_error > 0 && child_error != 123) { + break; + } + } + if (ENABLE_FEATURE_CLEAN_UP) free(max_chars); + return child_error; +} + + +#ifdef TEST + +const char *applet_name = "debug stuff usage"; + +void bb_show_usage(void) +{ + fprintf(stderr, "Usage: %s [-p] [-r] [-t] -[x] [-n max_arg] [-s max_chars]\n", + applet_name); + exit(1); +} + +int main(int argc, char **argv) +{ + return xargs_main(argc, argv); +} +#endif /* TEST */ diff --git a/include/applets.h b/include/applets.h new file mode 100644 index 000000000..d3e38d813 --- /dev/null +++ b/include/applets.h @@ -0,0 +1,333 @@ +/* vi: set sw=4 ts=4: */ +/* + * applets.h - a listing of all busybox applets. + * + * If you write a new applet, you need to add an entry to this list to make + * busybox aware of it. + * + * It is CRUCIAL that this listing be kept in ascii order, otherwise the binary + * search lookup contributed by Gaute B Strokkenes stops working. If you value + * your kneecaps, you'll be sure to *make sure* that any changes made to this + * file result in the listing remaining in ascii order. You have been warned. + */ + +#undef APPLET +#undef APPLET_ODDNAME +#undef APPLET_NOUSAGE + + +#if defined(PROTOTYPES) +# define APPLET(a,b,c) extern int a##_main(int argc, char **argv); +# define APPLET_NOUSAGE(a,b,c,d) extern int b##_main(int argc, char **argv); +# define APPLET_ODDNAME(a,b,c,d,e) extern int b##_main(int argc, char **argv); +#elif defined(MAKE_USAGE) +# ifdef CONFIG_FEATURE_VERBOSE_USAGE +# define APPLET(a,b,c) a##_trivial_usage "\n\n" a##_full_usage "\0" +# define APPLET_NOUSAGE(a,b,c,d) "\b\0" +# define APPLET_ODDNAME(a,b,c,d,e) e##_trivial_usage "\n\n" e##_full_usage "\0" +# else +# define APPLET(a,b,c) a##_trivial_usage "\0" +# define APPLET_NOUSAGE(a,b,c,d) "\b\0" +# define APPLET_ODDNAME(a,b,c,d,e) e##_trivial_usage "\0" +# endif +#elif defined(MAKE_LINKS) +# define APPLET(a,b,c) LINK b a +# define APPLET_NOUSAGE(a,b,c,d) LINK c a +# define APPLET_ODDNAME(a,b,c,d,e) LINK c a +#else + const struct BB_applet applets[] = { +# define APPLET(a,b,c) {#a,a##_main,b,c}, +# define APPLET_NOUSAGE(a,b,c,d) {#a,b##_main,c,d}, +# define APPLET_ODDNAME(a,b,c,d,e) {#a,b##_main,c,d}, +#endif + +#ifdef CONFIG_INSTALL_NO_USR +# define _BB_DIR_USR_BIN _BB_DIR_BIN +# define _BB_DIR_USR_SBIN _BB_DIR_SBIN +#endif + +// _BB_SUID_ALWAYS: will complain if busybox isn't suid +// and is run by non-root (applet_main() will not be called at all) +// _BB_SUID_NEVER: will drop suid prior to applet_main() +// _BB_SUID_MAYBE: neither of the above + +USE_TEST(APPLET_NOUSAGE([, test, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TEST(APPLET_NOUSAGE([[, test, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_ADDGROUP(APPLET(addgroup, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_ADDUSER(APPLET(adduser, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_ADJTIMEX(APPLET(adjtimex, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_AR(APPLET(ar, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_ARPING(APPLET(arping, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_ASH(APPLET_NOUSAGE(ash, ash, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_AWK(APPLET(awk, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_BASENAME(APPLET(basename, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_BBCONFIG(APPLET(bbconfig, _BB_DIR_BIN, _BB_SUID_NEVER)) +//USE_BBSH(APPLET(bbsh, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_BUNZIP2(APPLET(bunzip2, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +/* Always enabled. */ +APPLET_NOUSAGE(busybox, busybox, _BB_DIR_BIN, _BB_SUID_MAYBE) +USE_BUNZIP2(APPLET_ODDNAME(bzcat, bunzip2, _BB_DIR_USR_BIN, _BB_SUID_NEVER, bzcat)) +USE_CAL(APPLET(cal, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CAT(APPLET(cat, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CATV(APPLET(catv, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CHATTR(APPLET(chattr, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CHGRP(APPLET(chgrp, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CHMOD(APPLET(chmod, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CHOWN(APPLET(chown, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CHPST(APPLET(chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CHROOT(APPLET(chroot, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_CHVT(APPLET(chvt, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CKSUM(APPLET(cksum, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CLEAR(APPLET(clear, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CMP(APPLET(cmp, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_COMM(APPLET(comm, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CP(APPLET(cp, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CPIO(APPLET(cpio, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CROND(APPLET(crond, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_CRONTAB(APPLET(crontab, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS)) +USE_CUT(APPLET(cut, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DATE(APPLET(date, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_DC(APPLET(dc, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DD(APPLET(dd, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_DEALLOCVT(APPLET(deallocvt, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DELGROUP(APPLET_ODDNAME(delgroup, deluser, _BB_DIR_BIN, _BB_SUID_NEVER, delgroup)) +USE_DELUSER(APPLET(deluser, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_DEVFSD(APPLET(devfsd, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_DF(APPLET(df, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_APP_DHCPRELAY(APPLET(dhcprelay, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_DIFF(APPLET(diff, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DIRNAME(APPLET(dirname, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DMESG(APPLET(dmesg, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_DNSD(APPLET(dnsd, _BB_DIR_USR_SBIN, _BB_SUID_ALWAYS)) +USE_DOS2UNIX(APPLET(dos2unix, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DPKG(APPLET(dpkg, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DPKG_DEB(APPLET_ODDNAME(dpkg-deb, dpkg_deb, _BB_DIR_USR_BIN, _BB_SUID_NEVER, dpkg_deb)) +USE_DU(APPLET(du, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DUMPKMAP(APPLET(dumpkmap, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_APP_DUMPLEASES(APPLET(dumpleases, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_E2FSCK(APPLET(e2fsck, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_E2LABEL(APPLET_NOUSAGE(e2label, tune2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_ECHO(APPLET(echo, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_ED(APPLET(ed, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FEATURE_GREP_EGREP_ALIAS(APPLET_NOUSAGE(egrep, grep, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_EJECT(APPLET(eject, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_ENV(APPLET(env, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_ENVDIR(APPLET_ODDNAME(envdir, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, envdir)) +USE_ENVUIDGID(APPLET_ODDNAME(envuidgid, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, envuidgid)) +USE_ETHER_WAKE(APPLET_ODDNAME(ether-wake, ether_wake, _BB_DIR_USR_BIN, _BB_SUID_NEVER, ether_wake)) +USE_EXPR(APPLET(expr, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_FAKEIDENTD(APPLET(fakeidentd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_FALSE(APPLET(false, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FBSET(APPLET(fbset, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_FDFLUSH(APPLET_ODDNAME(fdflush, freeramdisk, _BB_DIR_BIN, _BB_SUID_NEVER, fdflush)) +USE_FDFORMAT(APPLET(fdformat, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_FDISK(APPLET(fdisk, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_FEATURE_GREP_FGREP_ALIAS(APPLET_NOUSAGE(fgrep, grep, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FIND(APPLET(find, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_FINDFS(APPLET_NOUSAGE(findfs, tune2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_FOLD(APPLET(fold, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_FREE(APPLET(free, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_FREERAMDISK(APPLET(freeramdisk, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_FSCK(APPLET(fsck, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_E2FSCK(APPLET_NOUSAGE(fsck.ext2, e2fsck, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_E2FSCK(APPLET_NOUSAGE(fsck.ext3, e2fsck, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_FSCK_MINIX(APPLET_ODDNAME(fsck.minix, fsck_minix, _BB_DIR_SBIN, _BB_SUID_NEVER, fsck_minix)) +USE_FTPGET(APPLET_ODDNAME(ftpget, ftpgetput, _BB_DIR_USR_BIN, _BB_SUID_NEVER,ftpget)) +USE_FTPPUT(APPLET_ODDNAME(ftpput, ftpgetput, _BB_DIR_USR_BIN, _BB_SUID_NEVER,ftpput)) +USE_FUSER(APPLET(fuser, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_GETOPT(APPLET(getopt, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_GETTY(APPLET(getty, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_GREP(APPLET(grep, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_GUNZIP(APPLET(gunzip, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_GZIP(APPLET(gzip, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_HALT(APPLET(halt, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_HDPARM(APPLET(hdparm, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_HEAD(APPLET(head, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_HEXDUMP(APPLET(hexdump, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_HOSTID(APPLET(hostid, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_HOSTNAME(APPLET(hostname, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_HTTPD(APPLET(httpd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_HUSH(APPLET_NOUSAGE(hush, hush, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_HWCLOCK(APPLET(hwclock, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_ID(APPLET(id, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_IFCONFIG(APPLET(ifconfig, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_IFUPDOWN(APPLET_ODDNAME(ifdown, ifupdown, _BB_DIR_SBIN, _BB_SUID_NEVER, ifdown)) +USE_IFUPDOWN(APPLET_ODDNAME(ifup, ifupdown, _BB_DIR_SBIN, _BB_SUID_NEVER, ifup)) +USE_INETD(APPLET(inetd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_INIT(APPLET(init, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_INSMOD(APPLET(insmod, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_INSTALL(APPLET(install, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_IP(APPLET(ip, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPADDR(APPLET(ipaddr, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPCALC(APPLET(ipcalc, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPCRM(APPLET(ipcrm, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS)) +USE_IPCS(APPLET(ipcs, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS)) +USE_IPLINK(APPLET(iplink, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPROUTE(APPLET(iproute, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPRULE(APPLET(iprule, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPTUNNEL(APPLET(iptunnel, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_KILL(APPLET(kill, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_KILLALL(APPLET_ODDNAME(killall, kill, _BB_DIR_USR_BIN, _BB_SUID_NEVER, killall)) +USE_KILLALL5(APPLET_ODDNAME(killall5, kill, _BB_DIR_USR_BIN, _BB_SUID_NEVER, killall5)) +USE_KLOGD(APPLET(klogd, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_LASH(APPLET(lash, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_LAST(APPLET(last, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_LENGTH(APPLET(length, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_LESS(APPLET(less, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SETARCH(APPLET_NOUSAGE(linux32, setarch, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SETARCH(APPLET_NOUSAGE(linux64, setarch, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FEATURE_INITRD(APPLET_NOUSAGE(linuxrc, init, _BB_DIR_ROOT, _BB_SUID_NEVER)) +USE_LN(APPLET(ln, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_LOADFONT(APPLET(loadfont, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_LOADKMAP(APPLET(loadkmap, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_LOGGER(APPLET(logger, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_LOGIN(APPLET(login, _BB_DIR_BIN, _BB_SUID_ALWAYS)) +USE_LOGNAME(APPLET(logname, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_LOGREAD(APPLET(logread, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_LOSETUP(APPLET(losetup, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_LS(APPLET(ls, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_LSATTR(APPLET(lsattr, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_LSMOD(APPLET(lsmod, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_UNLZMA(APPLET_ODDNAME(lzmacat, unlzma, _BB_DIR_USR_BIN, _BB_SUID_NEVER, lzmacat)) +USE_MAKEDEVS(APPLET(makedevs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MD5SUM(APPLET_ODDNAME(md5sum, md5_sha1_sum, _BB_DIR_USR_BIN, _BB_SUID_NEVER, md5sum)) +USE_MDEV(APPLET(mdev, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MESG(APPLET(mesg, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_MKDIR(APPLET(mkdir, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MKE2FS(APPLET(mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MKFIFO(APPLET(mkfifo, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_MKE2FS(APPLET_NOUSAGE(mkfs.ext2, mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MKE2FS(APPLET_NOUSAGE(mkfs.ext3, mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MKFS_MINIX(APPLET_ODDNAME(mkfs.minix, mkfs_minix, _BB_DIR_SBIN, _BB_SUID_NEVER, mkfs_minix)) +USE_MKNOD(APPLET(mknod, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MKSWAP(APPLET(mkswap, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MKTEMP(APPLET(mktemp, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MODPROBE(APPLET(modprobe, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MORE(APPLET(more, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MOUNT(APPLET(mount, _BB_DIR_BIN, USE_DESKTOP(_BB_SUID_MAYBE) SKIP_DESKTOP(_BB_SUID_NEVER))) +USE_MOUNTPOINT(APPLET(mountpoint, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MSH(APPLET_NOUSAGE(msh, msh, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MT(APPLET(mt, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MV(APPLET(mv, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_NAMEIF(APPLET(nameif, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_NC(APPLET(nc, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_NETSTAT(APPLET(netstat, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_NICE(APPLET(nice, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_NMETER(APPLET(nmeter, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_NOHUP(APPLET(nohup, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_NSLOOKUP(APPLET(nslookup, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_OD(APPLET(od, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_OPENVT(APPLET(openvt, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_PASSWD(APPLET(passwd, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS)) +USE_PATCH(APPLET(patch, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_PIDOF(APPLET(pidof, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_PING(APPLET(ping, _BB_DIR_BIN, _BB_SUID_MAYBE)) +USE_PING6(APPLET(ping6, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_PIPE_PROGRESS(APPLET_NOUSAGE(pipe_progress, pipe_progress, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_PIVOT_ROOT(APPLET(pivot_root, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_HALT(APPLET_ODDNAME(poweroff, halt, _BB_DIR_SBIN, _BB_SUID_NEVER, poweroff)) +USE_PRINTENV(APPLET(printenv, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_PRINTF(APPLET(printf, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_PS(APPLET(ps, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_PWD(APPLET(pwd, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_RAIDAUTORUN(APPLET(raidautorun, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_RDATE(APPLET(rdate, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_READAHEAD(APPLET(readahead, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_READLINK(APPLET(readlink, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_READPROFILE(APPLET(readprofile, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_REALPATH(APPLET(realpath, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_HALT(APPLET_ODDNAME(reboot, halt, _BB_DIR_SBIN, _BB_SUID_NEVER, reboot)) +USE_RENICE(APPLET(renice, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RESET(APPLET(reset, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RESIZE(APPLET(resize, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RM(APPLET(rm, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_RMDIR(APPLET(rmdir, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_RMMOD(APPLET(rmmod, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_ROUTE(APPLET(route, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_RPM(APPLET(rpm, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_RPM2CPIO(APPLET(rpm2cpio, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RUN_PARTS(APPLET_ODDNAME(run-parts, run_parts, _BB_DIR_BIN, _BB_SUID_NEVER, run_parts)) +USE_RUNLEVEL(APPLET(runlevel, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_RUNSV(APPLET(runsv, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RUNSVDIR(APPLET(runsvdir, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RX(APPLET(rx, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SED(APPLET(sed, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SEQ(APPLET(seq, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SETARCH(APPLET(setarch, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SETCONSOLE(APPLET(setconsole, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_SETKEYCODES(APPLET(setkeycodes, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SETLOGCONS(APPLET(setlogcons, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_SETSID(APPLET(setsid, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SETUIDGID(APPLET_ODDNAME(setuidgid, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, setuidgid)) +USE_FEATURE_SH_IS_ASH(APPLET_NOUSAGE(sh, ash, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FEATURE_SH_IS_HUSH(APPLET_NOUSAGE(sh, hush, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FEATURE_SH_IS_LASH(APPLET_NOUSAGE(sh, lash, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FEATURE_SH_IS_MSH(APPLET_NOUSAGE(sh, msh, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SHA1SUM(APPLET_ODDNAME(sha1sum, md5_sha1_sum, _BB_DIR_USR_BIN, _BB_SUID_NEVER, sha1sum)) +USE_SLEEP(APPLET(sleep, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SOFTLIMIT(APPLET_ODDNAME(softlimit, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, softlimit)) +USE_SORT(APPLET(sort, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_START_STOP_DAEMON(APPLET_ODDNAME(start-stop-daemon, start_stop_daemon, _BB_DIR_SBIN, _BB_SUID_NEVER, start_stop_daemon)) +USE_STAT(APPLET(stat, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_STRINGS(APPLET(strings, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_STTY(APPLET(stty, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SU(APPLET(su, _BB_DIR_BIN, _BB_SUID_ALWAYS)) +USE_SULOGIN(APPLET(sulogin, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_SUM(APPLET(sum, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SV(APPLET(sv, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SVLOGD(APPLET(svlogd, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SWAPONOFF(APPLET_ODDNAME(swapoff, swap_on_off, _BB_DIR_SBIN, _BB_SUID_NEVER,swapoff)) +USE_SWAPONOFF(APPLET_ODDNAME(swapon, swap_on_off, _BB_DIR_SBIN, _BB_SUID_NEVER, swapon)) +USE_SWITCH_ROOT(APPLET(switch_root, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_SYNC(APPLET(sync, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_BB_SYSCTL(APPLET(sysctl, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_SYSLOGD(APPLET(syslogd, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_TAIL(APPLET(tail, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TAR(APPLET(tar, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_TASKSET(APPLET(taskset, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TEE(APPLET(tee, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TELNET(APPLET(telnet, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TELNETD(APPLET(telnetd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_TEST(APPLET(test, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TFTP(APPLET(tftp, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TIME(APPLET(time, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TOP(APPLET(top, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TOUCH(APPLET(touch, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_TR(APPLET(tr, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TRACEROUTE(APPLET(traceroute, _BB_DIR_USR_BIN, _BB_SUID_MAYBE)) +USE_TRUE(APPLET(true, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_TTY(APPLET(tty, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TUNE2FS(APPLET(tune2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_APP_UDHCPC(APPLET(udhcpc, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_APP_UDHCPD(APPLET(udhcpd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_UMOUNT(APPLET(umount, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_UNAME(APPLET(uname, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_UNCOMPRESS(APPLET(uncompress, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_UNIQ(APPLET(uniq, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_UNIX2DOS(APPLET_ODDNAME(unix2dos, dos2unix, _BB_DIR_USR_BIN, _BB_SUID_NEVER, unix2dos)) +USE_UNLZMA(APPLET(unlzma, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_UNZIP(APPLET(unzip, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_UPTIME(APPLET(uptime, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_USLEEP(APPLET(usleep, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_UUDECODE(APPLET(uudecode, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_UUENCODE(APPLET(uuencode, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_VCONFIG(APPLET(vconfig, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_VI(APPLET(vi, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_VLOCK(APPLET(vlock, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS)) +USE_WATCH(APPLET(watch, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_WATCHDOG(APPLET(watchdog, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_WC(APPLET(wc, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_WGET(APPLET(wget, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_WHICH(APPLET(which, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_WHO(APPLET(who, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_WHOAMI(APPLET(whoami, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_XARGS(APPLET(xargs, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_YES(APPLET(yes, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_GUNZIP(APPLET_ODDNAME(zcat, gunzip, _BB_DIR_BIN, _BB_SUID_NEVER, zcat)) +USE_ZCIP(APPLET(zcip, _BB_DIR_SBIN, _BB_SUID_NEVER)) + +#if !defined(PROTOTYPES) && !defined(MAKE_USAGE) + { 0,NULL,0,0 } +}; + +#endif diff --git a/include/busybox.h b/include/busybox.h new file mode 100644 index 000000000..d20337ff1 --- /dev/null +++ b/include/busybox.h @@ -0,0 +1,47 @@ +/* vi: set sw=4 ts=4: */ +/* + * Busybox main internal header file + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ +#ifndef _BB_INTERNAL_H_ +#define _BB_INTERNAL_H_ 1 + +#include "libbb.h" + +/* order matters: used as index into "install_dir[]" in busybox.c */ +enum Location { + _BB_DIR_ROOT = 0, + _BB_DIR_BIN, + _BB_DIR_SBIN, + _BB_DIR_USR_BIN, + _BB_DIR_USR_SBIN +}; + +enum SUIDRoot { + _BB_SUID_NEVER = 0, + _BB_SUID_MAYBE, + _BB_SUID_ALWAYS +}; + +struct BB_applet { + const char *name; + int (*main) (int argc, char **argv); + __extension__ enum Location location:4; + __extension__ enum SUIDRoot need_suid:4; +}; + +/* From busybox.c */ +extern const struct BB_applet applets[]; + +/* Automagically pull in all the applet function prototypes and + * applet usage strings. These are all of the form: + * extern int foo_main(int argc, char **argv); + * extern const char foo_usage[]; + * These are all autogenerated from the set of currently defined applets. + */ +#define PROTOTYPES +#include "applets.h" +#undef PROTOTYPES + +#endif /* _BB_INTERNAL_H_ */ diff --git a/include/dump.h b/include/dump.h new file mode 100644 index 000000000..7e1715430 --- /dev/null +++ b/include/dump.h @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +#define F_IGNORE 0x01 /* %_A */ +#define F_SETREP 0x02 /* rep count set, not default */ +#define F_ADDRESS 0x001 /* print offset */ +#define F_BPAD 0x002 /* blank pad */ +#define F_C 0x004 /* %_c */ +#define F_CHAR 0x008 /* %c */ +#define F_DBL 0x010 /* %[EefGf] */ +#define F_INT 0x020 /* %[di] */ +#define F_P 0x040 /* %_p */ +#define F_STR 0x080 /* %s */ +#define F_U 0x100 /* %_u */ +#define F_UINT 0x200 /* %[ouXx] */ +#define F_TEXT 0x400 /* no conversions */ + +enum _vflag { ALL, DUP, FIRST, WAIT }; /* -v values */ + +typedef struct _pr { + struct _pr *nextpr; /* next print unit */ + unsigned int flags; /* flag values */ + int bcnt; /* byte count */ + char *cchar; /* conversion character */ + char *fmt; /* printf format */ + char *nospace; /* no whitespace version */ +} PR; + +typedef struct _fu { + struct _fu *nextfu; /* next format unit */ + struct _pr *nextpr; /* next print unit */ + unsigned int flags; /* flag values */ + int reps; /* repetition count */ + int bcnt; /* byte count */ + char *fmt; /* format string */ +} FU; + +typedef struct _fs { /* format strings */ + struct _fs *nextfs; /* linked list of format strings */ + struct _fu *nextfu; /* linked list of format units */ + int bcnt; +} FS; + +extern void bb_dump_add(const char *fmt); +extern int bb_dump_dump(char **argv); +extern int bb_dump_size(FS * fs); + +extern FS *bb_dump_fshead; /* head of format strings */ +extern int bb_dump_blocksize; /* data block size */ +extern int bb_dump_length; /* max bytes to read */ +extern enum _vflag bb_dump_vflag; +extern off_t bb_dump_skip; /* bytes to skip */ diff --git a/include/grp_.h b/include/grp_.h new file mode 100644 index 000000000..b34addfa2 --- /dev/null +++ b/include/grp_.h @@ -0,0 +1,115 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright (C) 1991,92,95,96,97,98,99,2000,01 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* + * POSIX Standard: 9.2.1 Group Database Access + */ + + +#if !defined CONFIG_USE_BB_PWD_GRP +#include + +#else + +#ifndef _GRP_H +#define _GRP_H 1 + +#include +#include +#include + +/* The group structure. */ +struct group +{ + char *gr_name; /* Group name. */ + char *gr_passwd; /* Password. */ + gid_t gr_gid; /* Group ID. */ + char **gr_mem; /* Member list. */ +}; + + +/* Rewind the group-file stream. */ +extern void setgrent (void); + +/* Close the group-file stream. */ +extern void endgrent (void); + +/* Read an entry from the group-file stream, opening it if necessary. */ +extern struct group *getgrent (void); + +/* Read a group entry from STREAM. */ +extern struct group *fgetgrent (FILE *__stream); + +/* Write the given entry onto the given stream. */ +extern int putgrent (__const struct group *__restrict __p, + FILE *__restrict __f); + +/* Search for an entry with a matching group ID. */ +extern struct group *getgrgid (gid_t __gid); + +/* Search for an entry with a matching group name. */ +extern struct group *getgrnam (__const char *__name); + +/* Reentrant versions of some of the functions above. + + PLEASE NOTE: the `getgrent_r' function is not (yet) standardized. + The interface may change in later versions of this library. But + the interface is designed following the principals used for the + other reentrant functions so the chances are good this is what the + POSIX people would choose. */ + +extern int getgrent_r (struct group *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct group **__restrict __result); + +/* Search for an entry with a matching group ID. */ +extern int getgrgid_r (gid_t __gid, struct group *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct group **__restrict __result); + +/* Search for an entry with a matching group name. */ +extern int getgrnam_r (__const char *__restrict __name, + struct group *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct group **__restrict __result); + +/* Read a group entry from STREAM. This function is not standardized + an probably never will. */ +extern int fgetgrent_r (FILE *__restrict __stream, + struct group *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct group **__restrict __result); + +/* Set the group set for the current user to GROUPS (N of them). */ +extern int setgroups (size_t __n, __const gid_t *__groups); + +/* Store at most *NGROUPS members of the group set for USER into + *GROUPS. Also include GROUP. The actual number of groups found is + returned in *NGROUPS. Return -1 if the if *NGROUPS is too small. */ +extern int getgrouplist (__const char *__user, gid_t __group, + gid_t *__groups, int *__ngroups); + +/* Initialize the group set for the current user + by reading the group database and using all groups + of which USER is a member. Also include GROUP. */ +extern int initgroups (__const char *__user, gid_t __group); + + +#endif /* grp.h */ +#endif diff --git a/include/inet_common.h b/include/inet_common.h new file mode 100644 index 000000000..1c16f6ca2 --- /dev/null +++ b/include/inet_common.h @@ -0,0 +1,30 @@ +/* vi: set sw=4 ts=4: */ +/* + * stolen from net-tools-1.59 and stripped down for busybox by + * Erik Andersen + * + * Heavily modified by Manuel Novoa III Mar 12, 2001 + * + * Version: $Id: inet_common.h,v 1.4 2004/03/10 07:42:37 mjn3 Exp $ + * + */ + +#include +#include +#include +#include "platform.h" + +/* hostfirst!=0 If we expect this to be a hostname, + try hostname database first + */ +extern int INET_resolve(const char *name, struct sockaddr_in *s_in, int hostfirst); + +/* numeric: & 0x8000: "default" instead of "*", + * & 0x4000: host instead of net, + * & 0x0fff: don't resolve + */ +extern int INET_rresolve(char *name, size_t len, struct sockaddr_in *s_in, + int numeric, unsigned int netmask); + +extern int INET6_resolve(const char *name, struct sockaddr_in6 *sin6); +extern int INET6_rresolve(char *name, size_t len, struct sockaddr_in6 *sin6, int numeric); diff --git a/include/libbb.h b/include/libbb.h new file mode 100644 index 000000000..1d91a0a72 --- /dev/null +++ b/include/libbb.h @@ -0,0 +1,689 @@ +/* vi: set sw=4 ts=4: */ +/* + * Busybox main internal header file + * + * Based in part on code from sash, Copyright (c) 1999 by David I. Bell + * Permission has been granted to redistribute this code under the GPL. + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ +#ifndef __LIBBUSYBOX_H__ +#define __LIBBUSYBOX_H__ 1 + +#include "platform.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SELINUX +#include +#endif + +#ifdef CONFIG_LOCALE_SUPPORT +#include +#else +#define setlocale(x,y) +#endif + +#include "pwd_.h" +#include "grp_.h" +#include "shadow_.h" + +/* Try to pull in PATH_MAX */ +#include +#include +#ifndef PATH_MAX +#define PATH_MAX 256 +#endif + +/* Tested to work correctly (IIRC :]) */ +#define MAXINT(T) (T)( \ + ((T)-1) > 0 \ + ? (T)-1 \ + : (T)~((T)1 << (sizeof(T)*8-1)) \ + ) + +#define MININT(T) (T)( \ + ((T)-1) > 0 \ + ? (T)0 \ + : ((T)1 << (sizeof(T)*8-1)) \ + ) + +/* Large file support */ +/* Note that CONFIG_LFS forces bbox to be built with all common ops + * (stat, lseek etc) mapped to "largefile" variants by libc. + * Practically it means that open() automatically has O_LARGEFILE added + * and all filesize/file_offset parameters and struct members are "large" + * (in today's world - signed 64bit). For full support of large files, + * we need a few helper #defines (below) and careful use of off_t + * instead of int/ssize_t. No lseek64(), O_LARGEFILE etc necessary */ +#if ENABLE_LFS +/* CONFIG_LFS is on */ +# if ULONG_MAX > 0xffffffff +/* "long" is long enough on this system */ +# define XATOOFF(a) xatoul_range(a, 0, LONG_MAX) +/* usage: sz = BB_STRTOOFF(s, NULL, 10); if (errno || sz < 0) die(); */ +# define BB_STRTOOFF bb_strtoul +# define STRTOOFF strtoul +/* usage: printf("size: %"OFF_FMT"d (%"OFF_FMT"x)\n", sz, sz); */ +# define OFF_FMT "l" +# else +/* "long" is too short, need "long long" */ +# define XATOOFF(a) xatoull_range(a, 0, LLONG_MAX) +# define BB_STRTOOFF bb_strtoull +# define STRTOOFF strtoull +# define OFF_FMT "ll" +# endif +#else +/* CONFIG_LFS is off */ +# if UINT_MAX == 0xffffffff +/* While sizeof(off_t) == sizeof(int), off_t is typedef'ed to long anyway. + * gcc will throw warnings on printf("%d", off_t). Crap... */ +# define XATOOFF(a) xatoi_u(a) +# define BB_STRTOOFF bb_strtou +# define STRTOOFF strtol +# define OFF_FMT "l" +# else +# define XATOOFF(a) xatoul_range(a, 0, LONG_MAX) +# define BB_STRTOOFF bb_strtoul +# define STRTOOFF strtol +# define OFF_FMT "l" +# endif +#endif +/* scary. better ideas? (but do *test* them first!) */ +#define OFF_T_MAX ((off_t)~((off_t)1 << (sizeof(off_t)*8-1))) + +/* Some useful definitions */ +#undef FALSE +#define FALSE ((int) 0) +#undef TRUE +#define TRUE ((int) 1) +#undef SKIP +#define SKIP ((int) 2) + +/* for mtab.c */ +#define MTAB_GETMOUNTPT '1' +#define MTAB_GETDEVICE '2' + +#define BUF_SIZE 8192 +#define EXPAND_ALLOC 1024 + +/* Macros for min/max. */ +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +/* buffer allocation schemes */ +#ifdef CONFIG_FEATURE_BUFFERS_GO_ON_STACK +#define RESERVE_CONFIG_BUFFER(buffer,len) char buffer[len] +#define RESERVE_CONFIG_UBUFFER(buffer,len) unsigned char buffer[len] +#define RELEASE_CONFIG_BUFFER(buffer) ((void)0) +#else +#ifdef CONFIG_FEATURE_BUFFERS_GO_IN_BSS +#define RESERVE_CONFIG_BUFFER(buffer,len) static char buffer[len] +#define RESERVE_CONFIG_UBUFFER(buffer,len) static unsigned char buffer[len] +#define RELEASE_CONFIG_BUFFER(buffer) ((void)0) +#else +#define RESERVE_CONFIG_BUFFER(buffer,len) char *buffer=xmalloc(len) +#define RESERVE_CONFIG_UBUFFER(buffer,len) unsigned char *buffer=xmalloc(len) +#define RELEASE_CONFIG_BUFFER(buffer) free (buffer) +#endif +#endif + + +#if (__GLIBC__ < 2) +int vdprintf(int d, const char *format, va_list ap); +#endif +// This is declared here rather than #including in order to avoid +// confusing the two versions of basename. See the dirname/basename man page +// for details. +char *dirname(char *path); +/* Include our own copy of struct sysinfo to avoid binary compatibility + * problems with Linux 2.4, which changed things. Grumble, grumble. */ +struct sysinfo { + long uptime; /* Seconds since boot */ + unsigned long loads[3]; /* 1, 5, and 15 minute load averages */ + unsigned long totalram; /* Total usable main memory size */ + unsigned long freeram; /* Available memory size */ + unsigned long sharedram; /* Amount of shared memory */ + unsigned long bufferram; /* Memory used by buffers */ + unsigned long totalswap; /* Total swap space size */ + unsigned long freeswap; /* swap space still available */ + unsigned short procs; /* Number of current processes */ + unsigned short pad; /* Padding needed for m68k */ + unsigned long totalhigh; /* Total high memory size */ + unsigned long freehigh; /* Available high memory size */ + unsigned int mem_unit; /* Memory unit size in bytes */ + char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding: libc5 uses this.. */ +}; +extern int sysinfo(struct sysinfo* info); + + +extern void chomp(char *s); +extern void trim(char *s); +extern char *skip_whitespace(const char *); + +extern const char *bb_mode_string(int mode); +extern int is_directory(const char *name, int followLinks, struct stat *statBuf); +extern int remove_file(const char *path, int flags); +extern int copy_file(const char *source, const char *dest, int flags); +extern int recursive_action(const char *fileName, int recurse, + int followLinks, int depthFirst, + int (*fileAction) (const char *fileName, struct stat* statbuf, void* userData, int depth), + int (*dirAction) (const char *fileName, struct stat* statbuf, void* userData, int depth), + void* userData, int depth); +extern int device_open(const char *device, int mode); +extern int get_console_fd(void); +extern char *find_block_device(char *path); +extern off_t bb_copyfd_size(int fd1, int fd2, off_t size); +extern off_t bb_copyfd_eof(int fd1, int fd2); +extern char bb_process_escape_sequence(const char **ptr); +extern char *bb_get_last_path_component(char *path); +extern int ndelay_on(int fd); + + +extern DIR *xopendir(const char *path); +extern DIR *warn_opendir(const char *path); + +char *xgetcwd(char *cwd); +char *xreadlink(const char *path); +extern void xstat(char *filename, struct stat *buf); +extern pid_t spawn(char **argv); +extern pid_t xspawn(char **argv); +extern int wait4pid(int pid); +extern void xsetgid(gid_t gid); +extern void xsetuid(uid_t uid); +extern void xdaemon(int nochdir, int noclose); +extern void xchdir(const char *path); +extern void xsetenv(const char *key, const char *value); +extern int xopen(const char *pathname, int flags); +extern int xopen3(const char *pathname, int flags, int mode); +extern off_t xlseek(int fd, off_t offset, int whence); +extern off_t fdlength(int fd); + + +extern int xsocket(int domain, int type, int protocol); +extern void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); +extern void xlisten(int s, int backlog); +extern void xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen); +extern int xconnect_tcp_v4(struct sockaddr_in *s_addr); +extern struct hostent *xgethostbyname(const char *name); +extern struct hostent *xgethostbyname2(const char *name, int af); +extern int xsocket_stream_ip4or6(sa_family_t *fp); +typedef union { + struct sockaddr sa; + struct sockaddr_in sin; +#if ENABLE_FEATURE_IPV6 + struct sockaddr_in6 sin6; +#endif +} sockaddr_inet; +extern int dotted2sockaddr(const char *dotted, struct sockaddr* sp, int socklen); +extern int create_and_bind_socket_ip4or6(const char *hostaddr, int port); +extern int setsockopt_reuseaddr(int fd); +extern int setsockopt_broadcast(int fd); + + +extern char *xstrdup(const char *s); +extern char *xstrndup(const char *s, int n); +extern char *safe_strncpy(char *dst, const char *src, size_t size); +extern char *xasprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); + +/* dmalloc will redefine these to it's own implementation. It is safe + * to have the prototypes here unconditionally. */ +extern void *xmalloc(size_t size); +extern void *xrealloc(void *old, size_t size); +extern void *xzalloc(size_t size); + +extern ssize_t safe_read(int fd, void *buf, size_t count); +extern ssize_t full_read(int fd, void *buf, size_t count); +extern void xread(int fd, void *buf, size_t count); +extern unsigned char xread_char(int fd); +extern char *reads(int fd, char *buf, size_t count); +extern ssize_t read_close(int fd, void *buf, size_t count); +extern ssize_t open_read_close(const char *filename, void *buf, size_t count); +extern void *xmalloc_open_read_close(const char *filename, size_t *sizep); + +extern ssize_t safe_write(int fd, const void *buf, size_t count); +extern ssize_t full_write(int fd, const void *buf, size_t count); +extern void xwrite(int fd, const void *buf, size_t count); + +/* Reads and prints to stdout till eof, then closes FILE. Exits on error: */ +extern void xprint_and_close_file(FILE *file); +extern char *xmalloc_fgets(FILE *file); +/* Read up to (and including) TERMINATING_STRING: */ +extern char *xmalloc_fgets_str(FILE *file, const char *terminating_string); +/* Chops off '\n' from the end, unlike fgets: */ +extern char *xmalloc_getline(FILE *file); +extern char *bb_get_chunk_from_file(FILE *file, int *end); +extern void die_if_ferror(FILE *file, const char *msg); +extern void die_if_ferror_stdout(void); +extern void xfflush_stdout(void); +extern void fflush_stdout_and_exit(int retval) ATTRIBUTE_NORETURN; +extern int fclose_if_not_stdin(FILE *file); +extern FILE *xfopen(const char *filename, const char *mode); +/* Prints warning to stderr and returns NULL on failure: */ +extern FILE *fopen_or_warn(const char *filename, const char *mode); +/* "Opens" stdin if filename is special, else just opens file: */ +extern FILE *fopen_or_warn_stdin(const char *filename); + + +extern void smart_ulltoa5(unsigned long long ul, char buf[5]); +extern void utoa_to_buf(unsigned n, char *buf, unsigned buflen); +extern char *utoa(unsigned n); +extern void itoa_to_buf(int n, char *buf, unsigned buflen); +extern char *itoa(int n); + +struct suffix_mult { + const char *suffix; + unsigned mult; +}; +#include "xatonum.h" +/* Specialized: */ +/* Using xatoi() instead of naive atoi() is not always convenient - + * in many places people want *non-negative* values, but store them + * in signed int. Therefore we need this one: + * dies if input is not in [0, INT_MAX] range. Also will reject '-0' etc */ +int xatoi_u(const char *numstr); +/* Useful for reading port numbers */ +uint16_t xatou16(const char *numstr); + + +/* These parse entries in /etc/passwd and /etc/group. This is desirable + * for BusyBox since we want to avoid using the glibc NSS stuff, which + * increases target size and is often not needed on embedded systems. */ +extern long bb_xgetpwnam(const char *name); +extern long bb_xgetgrnam(const char *name); +/*extern char *bb_getug(char *buffer, char *idname, long id, int bufsize, char prefix);*/ +extern char *bb_getpwuid(char *name, long uid, int bufsize); +extern char *bb_getgrgid(char *group, long gid, int bufsize); +/* from chpst */ +struct bb_uidgid_t { + uid_t uid; + gid_t gid; +}; +extern unsigned uidgid_get(struct bb_uidgid_t*, const char* /*, unsigned*/); + + +enum { BB_GETOPT_ERROR = 0x80000000 }; +extern const char *opt_complementary; +#if ENABLE_GETOPT_LONG +extern const struct option *applet_long_options; +#endif +extern uint32_t option_mask32; +extern uint32_t getopt32(int argc, char **argv, const char *applet_opts, ...); + + +typedef struct llist_s { + char *data; + struct llist_s *link; +} llist_t; +extern void llist_add_to(llist_t **old_head, void *data); +extern void llist_add_to_end(llist_t **list_head, void *data); +extern void *llist_pop(llist_t **elm); +extern void llist_free(llist_t *elm, void (*freeit)(void *data)); +extern llist_t* rev_llist(llist_t *list); + +enum { + LOGMODE_NONE = 0, + LOGMODE_STDIO = 1<<0, + LOGMODE_SYSLOG = 1<<1, + LOGMODE_BOTH = LOGMODE_SYSLOG + LOGMODE_STDIO, +}; +extern const char *msg_eol; +extern int logmode; +extern int die_sleep; +extern int xfunc_error_retval; +extern void bb_show_usage(void) ATTRIBUTE_NORETURN ATTRIBUTE_EXTERNALLY_VISIBLE; +extern void bb_error_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +extern void bb_error_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); +extern void bb_perror_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +extern void bb_perror_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); +extern void bb_vherror_msg(const char *s, va_list p); +extern void bb_herror_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +extern void bb_herror_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); +extern void bb_perror_nomsg_and_die(void) ATTRIBUTE_NORETURN; +extern void bb_perror_nomsg(void); +extern void bb_info_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +/* These are used internally -- you shouldn't need to use them */ +extern void bb_verror_msg(const char *s, va_list p, const char *strerr); +extern void bb_vperror_msg(const char *s, va_list p); +extern void bb_vinfo_msg(const char *s, va_list p); + + +extern int bb_echo(int argc, char** argv); +extern int bb_test(int argc, char** argv); + +#ifndef BUILD_INDIVIDUAL +extern struct BB_applet *find_applet_by_name(const char *name); +extern void run_applet_by_name(const char *name, int argc, char **argv); +#endif + +extern struct mntent *find_mount_point(const char *name, const char *table); +extern void erase_mtab(const char * name); +extern unsigned int tty_baud_to_value(speed_t speed); +extern speed_t tty_value_to_baud(unsigned int value); +extern void bb_warn_ignoring_args(int n); + +extern int get_linux_version_code(void); + +extern char *query_loop(const char *device); +extern int del_loop(const char *device); +extern int set_loop(char **device, const char *file, unsigned long long offset); + + +const char *make_human_readable_str(unsigned long long size, + unsigned long block_size, unsigned long display_unit); + +char *bb_askpass(int timeout, const char * prompt); +int bb_ask_confirmation(void); +int klogctl(int type, char * b, int len); + +extern int bb_parse_mode(const char* s, mode_t* theMode); + +char *concat_path_file(const char *path, const char *filename); +char *concat_subpath_file(const char *path, const char *filename); +char *last_char_is(const char *s, int c); + +int execable_file(const char *name); +char *find_execable(const char *filename); +int exists_execable(const char *filename); + +USE_DESKTOP(long long) int uncompress(int fd_in, int fd_out); +int inflate(int in, int out); + +int create_icmp_socket(void); +int create_icmp6_socket(void); + +unsigned short bb_lookup_port(const char *port, const char *protocol, unsigned short default_port); +void bb_lookup_host(struct sockaddr_in *s_in, const char *host); + +int bb_make_directory(char *path, long mode, int flags); + +int get_signum(const char *name); +const char *get_signame(int number); + +char *bb_simplify_path(const char *path); + +#define FAIL_DELAY 3 +extern void bb_do_delay(int seconds); +extern void change_identity(const struct passwd *pw); +extern const char *change_identity_e2str(const struct passwd *pw); +extern void run_shell(const char *shell, int loginshell, const char *command, const char **additional_args); +#ifdef CONFIG_SELINUX +extern void renew_current_security_context(void); +extern void set_current_security_context(security_context_t sid); +#endif +extern int restricted_shell(const char *shell); +extern void setup_environment(const char *shell, int loginshell, int changeenv, const struct passwd *pw); +extern int correct_password(const struct passwd *pw); +extern char *pw_encrypt(const char *clear, const char *salt); +extern int obscure(const char *old, const char *newval, const struct passwd *pwdp); +extern int index_in_str_array(const char * const string_array[], const char *key); +extern int index_in_substr_array(const char * const string_array[], const char *key); +extern void print_login_issue(const char *issue_file, const char *tty); +extern void print_login_prompt(void); +#ifdef BB_NOMMU +extern void vfork_daemon(int nochdir, int noclose); +extern void vfork_daemon_rexec(int nochdir, int noclose, + int argc, char **argv, char *foreground_opt); +#endif +extern int get_terminal_width_height(int fd, int *width, int *height); +extern unsigned long get_ug_id(const char *s, long (*__bb_getxxnam)(const char *)); + +int is_in_ino_dev_hashtable(const struct stat *statbuf, char **name); +void add_to_ino_dev_hashtable(const struct stat *statbuf, const char *name); +void reset_ino_dev_hashtable(void); +#ifdef __GLIBC__ +/* At least glibc has horrendously large inline for this, so wrap it */ +extern unsigned long long bb_makedev(unsigned int major, unsigned int minor); +#undef makedev +#define makedev(a,b) bb_makedev(a,b) +#endif + + +#ifndef COMM_LEN +#ifdef TASK_COMM_LEN +enum { COMM_LEN = TASK_COMM_LEN }; +#else +/* synchronize with sizeof(task_struct.comm) in /usr/include/linux/sched.h */ +enum { COMM_LEN = 16 }; +#endif +#endif +typedef struct { + DIR *dir; +/* Fields are set to 0/NULL if failed to determine (or not requested) */ + char *cmd; + unsigned long rss; + unsigned long stime, utime; + unsigned pid; + unsigned ppid; + unsigned pgid; + unsigned sid; + unsigned uid; + unsigned gid; + /* basename of executable file in call to exec(2), size from */ + /* sizeof(task_struct.comm) in /usr/include/linux/sched.h */ + char state[4]; + char comm[COMM_LEN]; +// user/group? - use passwd/group parsing functions +} procps_status_t; +enum { + PSSCAN_PID = 1 << 0, + PSSCAN_PPID = 1 << 1, + PSSCAN_PGID = 1 << 2, + PSSCAN_SID = 1 << 3, + PSSCAN_UIDGID = 1 << 4, + PSSCAN_COMM = 1 << 5, + PSSCAN_CMD = 1 << 6, + PSSCAN_STATE = 1 << 7, + PSSCAN_RSS = 1 << 8, + PSSCAN_STIME = 1 << 9, + PSSCAN_UTIME = 1 << 10, + /* These are all retrieved from proc/NN/stat in one go: */ + PSSCAN_STAT = PSSCAN_PPID | PSSCAN_PGID | PSSCAN_SID + | PSSCAN_COMM | PSSCAN_STATE + | PSSCAN_RSS | PSSCAN_STIME | PSSCAN_UTIME, +}; +procps_status_t* alloc_procps_scan(int flags); +void free_procps_scan(procps_status_t* sp); +procps_status_t* procps_scan(procps_status_t* sp, int flags); +pid_t *find_pid_by_name(const char* procName); +pid_t *pidlist_reverse(pid_t *pidList); +void clear_username_cache(void); +const char* get_cached_username(uid_t uid); +const char* get_cached_groupname(gid_t gid); + + +extern const char bb_uuenc_tbl_base64[]; +extern const char bb_uuenc_tbl_std[]; +void bb_uuencode(const unsigned char *s, char *store, const int length, const char *tbl); + +typedef struct sha1_ctx_t { + uint32_t count[2]; + uint32_t hash[5]; + uint32_t wbuf[16]; +} sha1_ctx_t; +void sha1_begin(sha1_ctx_t *ctx); +void sha1_hash(const void *data, size_t length, sha1_ctx_t *ctx); +void *sha1_end(void *resbuf, sha1_ctx_t *ctx); + +typedef struct md5_ctx_t { + uint32_t A; + uint32_t B; + uint32_t C; + uint32_t D; + uint64_t total; + uint32_t buflen; + char buffer[128]; +} md5_ctx_t; +void md5_begin(md5_ctx_t *ctx); +void md5_hash(const void *data, size_t length, md5_ctx_t *ctx); +void *md5_end(void *resbuf, md5_ctx_t *ctx); + +uint32_t *crc32_filltable(int endian); + + +enum { /* DO NOT CHANGE THESE VALUES! cp.c depends on them. */ + FILEUTILS_PRESERVE_STATUS = 1, + FILEUTILS_DEREFERENCE = 2, + FILEUTILS_RECUR = 4, + FILEUTILS_FORCE = 8, + FILEUTILS_INTERACTIVE = 0x10, + FILEUTILS_MAKE_HARDLINK = 0x20, + FILEUTILS_MAKE_SOFTLINK = 0x40, +}; +#define FILEUTILS_CP_OPTSTR "pdRfils" + +extern const char *applet_name; +extern const char BB_BANNER[]; + +extern const char bb_msg_full_version[]; +extern const char bb_msg_memory_exhausted[]; +extern const char bb_msg_invalid_date[]; +extern const char bb_msg_read_error[]; +extern const char bb_msg_write_error[]; +extern const char bb_msg_unknown[]; +extern const char bb_msg_can_not_create_raw_socket[]; +extern const char bb_msg_perm_denied_are_you_root[]; +extern const char bb_msg_requires_arg[]; +extern const char bb_msg_invalid_arg[]; +extern const char bb_msg_standard_input[]; +extern const char bb_msg_standard_output[]; + +extern const char bb_str_default[]; + +extern const char bb_path_mtab_file[]; +extern const char bb_path_nologin_file[]; +extern const char bb_path_passwd_file[]; +extern const char bb_path_shadow_file[]; +extern const char bb_path_gshadow_file[]; +extern const char bb_path_group_file[]; +extern const char bb_path_securetty_file[]; +extern const char bb_path_motd_file[]; +extern const char bb_path_wtmp_file[]; +extern const char bb_dev_null[]; + +#ifndef BUFSIZ +#define BUFSIZ 4096 +#endif +extern char bb_common_bufsiz1[BUFSIZ+1]; + +/* You can change LIBBB_DEFAULT_LOGIN_SHELL, but don't use it, + * use bb_default_login_shell and following defines. + * If you change LIBBB_DEFAULT_LOGIN_SHELL, + * don't forget to change increment constant. */ +#define LIBBB_DEFAULT_LOGIN_SHELL "-/bin/sh" +extern const char bb_default_login_shell[]; +/* "/bin/sh" */ +#define DEFAULT_SHELL (bb_default_login_shell+1) +/* "sh" */ +#define DEFAULT_SHELL_SHORT_NAME (bb_default_login_shell+6) + + +#ifdef CONFIG_FEATURE_DEVFS +# define CURRENT_VC "/dev/vc/0" +# define VC_1 "/dev/vc/1" +# define VC_2 "/dev/vc/2" +# define VC_3 "/dev/vc/3" +# define VC_4 "/dev/vc/4" +# define VC_5 "/dev/vc/5" +#if defined(__sh__) || defined(__H8300H__) || defined(__H8300S__) +/* Yes, this sucks, but both SH (including sh64) and H8 have a SCI(F) for their + respective serial ports .. as such, we can't use the common device paths for + these. -- PFM */ +# define SC_0 "/dev/ttsc/0" +# define SC_1 "/dev/ttsc/1" +# define SC_FORMAT "/dev/ttsc/%d" +#else +# define SC_0 "/dev/tts/0" +# define SC_1 "/dev/tts/1" +# define SC_FORMAT "/dev/tts/%d" +#endif +# define VC_FORMAT "/dev/vc/%d" +# define LOOP_FORMAT "/dev/loop/%d" +# define LOOP_NAME "/dev/loop/" +# define FB_0 "/dev/fb/0" +#else +# define CURRENT_VC "/dev/tty0" +# define VC_1 "/dev/tty1" +# define VC_2 "/dev/tty2" +# define VC_3 "/dev/tty3" +# define VC_4 "/dev/tty4" +# define VC_5 "/dev/tty5" +#if defined(__sh__) || defined(__H8300H__) || defined(__H8300S__) +# define SC_0 "/dev/ttySC0" +# define SC_1 "/dev/ttySC1" +# define SC_FORMAT "/dev/ttySC%d" +#else +# define SC_0 "/dev/ttyS0" +# define SC_1 "/dev/ttyS1" +# define SC_FORMAT "/dev/ttyS%d" +#endif +# define VC_FORMAT "/dev/tty%d" +# define LOOP_FORMAT "/dev/loop%d" +# define LOOP_NAME "/dev/loop" +# define FB_0 "/dev/fb0" +#endif + +/* The following devices are the same on devfs and non-devfs systems. */ +#define CURRENT_TTY "/dev/tty" +#define CONSOLE_DEV "/dev/console" + + +#ifndef RB_POWER_OFF +/* Stop system and switch power off if possible. */ +#define RB_POWER_OFF 0x4321fedc +#endif + +/* Make sure we call functions instead of macros. */ +#undef isalnum +#undef isalpha +#undef isascii +#undef isblank +#undef iscntrl +#undef isgraph +#undef islower +#undef isprint +#undef ispunct +#undef isspace +#undef isupper +#undef isxdigit + +/* This one is more efficient - we save ~400 bytes */ +#undef isdigit +#define isdigit(a) ((unsigned)((a) - '0') <= 9) + + +#ifdef DMALLOC +#include +#endif + +#endif /* __LIBBUSYBOX_H__ */ diff --git a/include/platform.h b/include/platform.h new file mode 100644 index 000000000..345e9cb7a --- /dev/null +++ b/include/platform.h @@ -0,0 +1,276 @@ +/* vi: set sw=4 ts=4: */ +/* + Copyright 2006, Bernhard Fischer + + Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +*/ +#ifndef __PLATFORM_H +#define __PLATFORM_H 1 + +/* Convenience macros to test the version of gcc. */ +#undef __GNUC_PREREQ +#if defined __GNUC__ && defined __GNUC_MINOR__ +# define __GNUC_PREREQ(maj, min) \ + ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +#else +# define __GNUC_PREREQ(maj, min) 0 +#endif + +/* __restrict is known in EGCS 1.2 and above. */ +#if !__GNUC_PREREQ (2,92) +# ifndef __restrict +# define __restrict /* Ignore */ +# endif +#endif + +/* Define macros for some gcc attributes. This permits us to use the + macros freely, and know that they will come into play for the + version of gcc in which they are supported. */ + +#if !__GNUC_PREREQ (2,7) +# ifndef __attribute__ +# define __attribute__(x) +# endif +#endif + +#undef inline +#if defined(__STDC_VERSION__) && __STDC_VERSION__ > 199901L +/* it's a keyword */ +#else +# if __GNUC_PREREQ (2,7) +# define inline __inline__ +# else +# define inline +# endif +#endif + +#ifndef __const +# define __const const +#endif + +# define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +# define ATTRIBUTE_NORETURN __attribute__ ((__noreturn__)) +# define ATTRIBUTE_PACKED __attribute__ ((__packed__)) +# define ATTRIBUTE_ALIGNED(m) __attribute__ ((__aligned__(m))) +# if __GNUC_PREREQ (3,0) +# define ATTRIBUTE_ALWAYS_INLINE __attribute__ ((always_inline)) inline +# else +# define ATTRIBUTE_ALWAYS_INLINE inline +# endif + +/* -fwhole-program makes all symbols local. The attribute externally_visible + forces a symbol global. */ +# if __GNUC_PREREQ (4,1) +# define ATTRIBUTE_EXTERNALLY_VISIBLE __attribute__ ((__externally_visible__)) +# else +# define ATTRIBUTE_EXTERNALLY_VISIBLE +# endif /* GNUC >= 4.1 */ + +/* We use __extension__ in some places to suppress -pedantic warnings + about GCC extensions. This feature didn't work properly before + gcc 2.8. */ +#if !__GNUC_PREREQ (2,8) +# ifndef __extension__ +# define __extension__ +# endif +#endif + +/* gcc-2.95 had no va_copy but only __va_copy. */ +#if !__GNUC_PREREQ (3,0) +# include +# if !defined va_copy && defined __va_copy +# define va_copy(d,s) __va_copy((d),(s)) +# endif +#endif + +/* ---- Endian Detection ------------------------------------ */ + +#if (defined __digital__ && defined __unix__) +# include +# define __BIG_ENDIAN__ (BYTE_ORDER == BIG_ENDIAN) +# define __BYTE_ORDER BYTE_ORDER +#elif !defined __APPLE__ +# include +# include +#endif + +#ifdef __BIG_ENDIAN__ +# define BB_BIG_ENDIAN 1 +# define BB_LITTLE_ENDIAN 0 +#elif __BYTE_ORDER == __BIG_ENDIAN +# define BB_BIG_ENDIAN 1 +# define BB_LITTLE_ENDIAN 0 +#else +# define BB_BIG_ENDIAN 0 +# define BB_LITTLE_ENDIAN 1 +#endif + +#if BB_BIG_ENDIAN +#define SWAP_BE16(x) (x) +#define SWAP_BE32(x) (x) +#define SWAP_BE64(x) (x) +#define SWAP_LE16(x) bswap_16(x) +#define SWAP_LE32(x) bswap_32(x) +#define SWAP_LE64(x) bswap_64(x) +#else +#define SWAP_BE16(x) bswap_16(x) +#define SWAP_BE32(x) bswap_32(x) +#define SWAP_BE64(x) bswap_64(x) +#define SWAP_LE16(x) (x) +#define SWAP_LE32(x) (x) +#define SWAP_LE64(x) (x) +#endif + +/* ---- Networking ------------------------------------------ */ +#ifndef __APPLE__ +# include +#else +# include +#endif + +#ifndef __socklen_t_defined +typedef int socklen_t; +#endif + +/* ---- Compiler dependent settings ------------------------- */ +#ifndef __GNUC__ +#if defined __INTEL_COMPILER +__extension__ typedef __signed__ long long __s64; +__extension__ typedef unsigned long long __u64; +#endif /* __INTEL_COMPILER */ +#endif /* ifndef __GNUC__ */ + +#if (defined __digital__ && defined __unix__) +# undef HAVE_MNTENT_H +#else +# define HAVE_MNTENT_H 1 +#endif /* ___digital__ && __unix__ */ + +/*----- Kernel versioning ------------------------------------*/ +#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) + +/* ---- miscellaneous --------------------------------------- */ + +#if defined(__GNU_LIBRARY__) && __GNU_LIBRARY__ < 5 && \ + !defined(__dietlibc__) && \ + !defined(_NEWLIB_VERSION) && \ + !(defined __digital__ && defined __unix__) +# error "Sorry, this libc version is not supported :(" +#endif + +// Don't perpetuate e2fsck crap into the headers. Clean up e2fsck instead. + +#if defined __GLIBC__ || defined __UCLIBC__ \ + || defined __dietlibc__ || defined _NEWLIB_VERSION +#include +#define HAVE_FEATURES_H +#include +#define HAVE_STDINT_H +#else +/* Largest integral types. */ +#if __BIG_ENDIAN__ +typedef long int intmax_t; +typedef unsigned long int uintmax_t; +#else +__extension__ +typedef long long int intmax_t; +__extension__ +typedef unsigned long long int uintmax_t; +#endif +#endif + +/* uclibc does not implement daemon() for no-mmu systems. + * For 0.9.29 and svn, __ARCH_USE_MMU__ indicates no-mmu reliably. + * For earlier versions there is no reliable way to check if we are building + * for a mmu-less system; the user should pass EXTRA_CFLAGS="-DBB_NOMMU" + * on his own. + */ +#if defined __UCLIBC__ && __UCLIBC_MAJOR__ >= 0 && __UCLIBC_MINOR__ >= 9 && \ + __UCLIBC_SUBLEVEL__ > 28 && !defined __ARCH_USE_MMU__ +#define BB_NOMMU +#endif + +/* Platforms that haven't got dprintf need to implement fdprintf() in + * libbb. This would require a platform.c. It's not going to be cleaned + * out of the tree, so stop saying it should be. */ +#define fdprintf dprintf + +/* Don't use lchown with glibc older then 2.1.x ... uC-libc lacks it */ +#if (defined __GLIBC__ && __GLIBC__ <= 2 && __GLIBC_MINOR__ < 1) || \ + defined __UC_LIBC__ +# define lchown chown +#endif + +/* THIS SHOULD BE CLEANED OUT OF THE TREE ENTIRELY */ +/* FIXME: fix tar.c! */ +#ifndef FNM_LEADING_DIR +#define FNM_LEADING_DIR 0 +#endif + +#if (defined __digital__ && defined __unix__) +#include +#define HAVE_STANDARDS_H +#include +#define HAVE_INTTYPES_H +#define PRIu32 "u" + +/* use legacy setpgrp(pidt_,pid_t) for now. move to platform.c */ +#define bb_setpgrp do{pid_t __me = getpid();setpgrp(__me,__me);}while(0) + +#if !defined ADJ_OFFSET_SINGLESHOT && defined MOD_CLKA && defined MOD_OFFSET +#define ADJ_OFFSET_SINGLESHOT (MOD_CLKA | MOD_OFFSET) +#endif +#if !defined ADJ_FREQUENCY && defined MOD_FREQUENCY +#define ADJ_FREQUENCY MOD_FREQUENCY +#endif +#if !defined ADJ_TIMECONST && defined MOD_TIMECONST +#define ADJ_TIMECONST MOD_TIMECONST +#endif +#if !defined ADJ_TICK && defined MOD_CLKB +#define ADJ_TICK MOD_CLKB +#endif + +#else +#define bb_setpgrp setpgrp() +#endif + +#if defined(__linux__) +#include +// Make sure we have all the new mount flags we actually try to use. +#ifndef MS_BIND +#define MS_BIND (1<<12) +#endif +#ifndef MS_MOVE +#define MS_MOVE (1<<13) +#endif +#ifndef MS_RECURSIVE +#define MS_RECURSIVE (1<<14) +#endif +#ifndef MS_SILENT +#define MS_SILENT (1<<15) +#endif + +// The shared subtree stuff, which went in around 2.6.15 +#ifndef MS_UNBINDABLE +#define MS_UNBINDABLE (1<<17) +#endif +#ifndef MS_PRIVATE +#define MS_PRIVATE (1<<18) +#endif +#ifndef MS_SLAVE +#define MS_SLAVE (1<<19) +#endif +#ifndef MS_SHARED +#define MS_SHARED (1<<20) +#endif + + +#if !defined(BLKSSZGET) +#define BLKSSZGET _IO(0x12, 104) +#endif +#if !defined(BLKGETSIZE64) +#define BLKGETSIZE64 _IOR(0x12,114,size_t) +#endif +#endif + +#endif /* platform.h */ diff --git a/include/pwd_.h b/include/pwd_.h new file mode 100644 index 000000000..d161c0e8f --- /dev/null +++ b/include/pwd_.h @@ -0,0 +1,107 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright (C) 1991,92,95,96,97,98,99,2001 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* + * POSIX Standard: 9.2.2 User Database Access + */ + +#if !defined CONFIG_USE_BB_PWD_GRP +#include + +#else + +#ifndef _PWD_H +#define _PWD_H 1 + +#include +#include +#include + +/* The passwd structure. */ +struct passwd +{ + char *pw_name; /* Username. */ + char *pw_passwd; /* Password. */ + uid_t pw_uid; /* User ID. */ + gid_t pw_gid; /* Group ID. */ + char *pw_gecos; /* Real name. */ + char *pw_dir; /* Home directory. */ + char *pw_shell; /* Shell program. */ +}; + + +/* Rewind the password-file stream. */ +extern void setpwent (void); + +/* Close the password-file stream. */ +extern void endpwent (void); + +/* Read an entry from the password-file stream, opening it if necessary. */ +extern struct passwd *getpwent (void); + +/* Read an entry from STREAM. */ +extern struct passwd *fgetpwent (FILE *__stream); + +/* Write the given entry onto the given stream. */ +extern int putpwent (__const struct passwd *__restrict __p, + FILE *__restrict __f); + +/* Search for an entry with a matching user ID. */ +extern struct passwd *getpwuid (uid_t __uid); + +/* Search for an entry with a matching username. */ +extern struct passwd *getpwnam (__const char *__name); + +/* Reentrant versions of some of the functions above. + + PLEASE NOTE: the `getpwent_r' function is not (yet) standardized. + The interface may change in later versions of this library. But + the interface is designed following the principals used for the + other reentrant functions so the chances are good this is what the + POSIX people would choose. */ + +extern int getpwent_r (struct passwd *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct passwd **__restrict __result); + +extern int getpwuid_r (uid_t __uid, + struct passwd *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct passwd **__restrict __result); + +extern int getpwnam_r (__const char *__restrict __name, + struct passwd *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct passwd **__restrict __result); + + +/* Read an entry from STREAM. This function is not standardized and + probably never will. */ +extern int fgetpwent_r (FILE *__restrict __stream, + struct passwd *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct passwd **__restrict __result); + +/* Re-construct the password-file line for the given uid + in the given buffer. This knows the format that the caller + will expect, but this need not be the format of the password file. */ +extern int getpw (uid_t __uid, char *__buffer); + +#endif /* pwd.h */ +#endif diff --git a/include/shadow_.h b/include/shadow_.h new file mode 100644 index 000000000..177ee5f93 --- /dev/null +++ b/include/shadow_.h @@ -0,0 +1,99 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright (C) 1996, 1997, 1998, 1999 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* Declaration of types and functions for shadow password suite. */ + +#if !defined CONFIG_USE_BB_SHADOW +#include +#else + +#ifndef _SHADOW_H +#define _SHADOW_H 1 + +#include + +/* Paths to the user database files. */ +#ifndef _PATH_SHADOW +#define _PATH_SHADOW "/etc/shadow" +#endif +#define SHADOW _PATH_SHADOW + + +/* Structure of the password file. */ +struct spwd +{ + char *sp_namp; /* Login name. */ + char *sp_pwdp; /* Encrypted password. */ + long int sp_lstchg; /* Date of last change. */ + long int sp_min; /* Minimum number of days between changes. */ + long int sp_max; /* Maximum number of days between changes. */ + long int sp_warn; /* Number of days to warn user to change + the password. */ + long int sp_inact; /* Number of days the account may be + inactive. */ + long int sp_expire; /* Number of days since 1970-01-01 until + account expires. */ + unsigned long int sp_flag; /* Reserved. */ +}; + + +/* Open database for reading. */ +extern void setspent (void); + +/* Close database. */ +extern void endspent (void); + +/* Get next entry from database, perhaps after opening the file. */ +extern struct spwd *getspent (void); + +/* Get shadow entry matching NAME. */ +extern struct spwd *getspnam (__const char *__name); + +/* Read shadow entry from STRING. */ +extern struct spwd *sgetspent (__const char *__string); + +/* Read next shadow entry from STREAM. */ +extern struct spwd *fgetspent (FILE *__stream); + +/* Write line containing shadow password entry to stream. */ +extern int putspent (__const struct spwd *__p, FILE *__stream); + +/* Reentrant versions of some of the functions above. */ +extern int getspent_r (struct spwd *__result_buf, char *__buffer, + size_t __buflen, struct spwd **__result); + +extern int getspnam_r (__const char *__name, struct spwd *__result_buf, + char *__buffer, size_t __buflen, + struct spwd **__result); + +extern int sgetspent_r (__const char *__string, struct spwd *__result_buf, + char *__buffer, size_t __buflen, + struct spwd **__result); + +extern int fgetspent_r (FILE *__stream, struct spwd *__result_buf, + char *__buffer, size_t __buflen, + struct spwd **__result); +/* Protect password file against multi writers. */ +extern int lckpwdf (void); + +/* Unlock password file. */ +extern int ulckpwdf (void); + +#endif /* shadow.h */ +#endif diff --git a/include/unarchive.h b/include/unarchive.h new file mode 100644 index 000000000..7de6a63fe --- /dev/null +++ b/include/unarchive.h @@ -0,0 +1,114 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __UNARCHIVE_H__ +#define __UNARCHIVE_H__ + +#define ARCHIVE_PRESERVE_DATE 1 +#define ARCHIVE_CREATE_LEADING_DIRS 2 +#define ARCHIVE_EXTRACT_UNCONDITIONAL 4 +#define ARCHIVE_EXTRACT_QUIET 8 +#define ARCHIVE_EXTRACT_NEWER 16 +#define ARCHIVE_NOPRESERVE_OWN 32 +#define ARCHIVE_NOPRESERVE_PERM 64 + +#include "libbb.h" + +typedef struct file_headers_s { + char *name; + char *link_name; + off_t size; + uid_t uid; + gid_t gid; + mode_t mode; + time_t mtime; + dev_t device; +} file_header_t; + +typedef struct archive_handle_s { + /* define if the header and data component should processed */ + char (*filter)(struct archive_handle_s *); + llist_t *accept; + /* List of files that have been rejected */ + llist_t *reject; + /* List of files that have successfully been worked on */ + llist_t *passed; + + /* Contains the processed header entry */ + file_header_t *file_header; + + /* process the header component, e.g. tar -t */ + void (*action_header)(const file_header_t *); + + /* process the data component, e.g. extract to filesystem */ + void (*action_data)(struct archive_handle_s *); + + /* How to process any sub archive, e.g. get_header_tar_gz */ + char (*action_data_subarchive)(struct archive_handle_s *); + + /* Contains the handle to a sub archive */ + struct archive_handle_s *sub_archive; + + /* The raw stream as read from disk or stdin */ + int src_fd; + + /* Count the number of bytes processed */ + off_t offset; + + /* Function that skips data: read_by_char or read_by_skip */ + void (*seek)(const struct archive_handle_s *archive_handle, const unsigned int amount); + + /* Temporary storage */ + char *buffer; + + /* Flags and misc. stuff */ + unsigned char flags; + +} archive_handle_t; + +extern archive_handle_t *init_handle(void); + +extern char filter_accept_all(archive_handle_t *archive_handle); +extern char filter_accept_list(archive_handle_t *archive_handle); +extern char filter_accept_list_reassign(archive_handle_t *archive_handle); +extern char filter_accept_reject_list(archive_handle_t *archive_handle); + +extern void unpack_ar_archive(archive_handle_t *ar_archive); + +extern void data_skip(archive_handle_t *archive_handle); +extern void data_extract_all(archive_handle_t *archive_handle); +extern void data_extract_to_stdout(archive_handle_t *archive_handle); +extern void data_extract_to_buffer(archive_handle_t *archive_handle); + +extern void header_skip(const file_header_t *file_header); +extern void header_list(const file_header_t *file_header); +extern void header_verbose_list(const file_header_t *file_header); + +extern void check_header_gzip(int src_fd); + +extern char get_header_ar(archive_handle_t *archive_handle); +extern char get_header_cpio(archive_handle_t *archive_handle); +extern char get_header_tar(archive_handle_t *archive_handle); +extern char get_header_tar_bz2(archive_handle_t *archive_handle); +extern char get_header_tar_lzma(archive_handle_t *archive_handle); +extern char get_header_tar_gz(archive_handle_t *archive_handle); + +extern void seek_by_jump(const archive_handle_t *archive_handle, const unsigned int amount); +extern void seek_by_read(const archive_handle_t *archive_handle, const unsigned int amount); + +extern ssize_t archive_xread_all_eof(archive_handle_t *archive_handle, unsigned char *buf, size_t count); + +extern void data_align(archive_handle_t *archive_handle, const unsigned short boundary); +extern const llist_t *find_list_entry(const llist_t *list, const char *filename); +extern const llist_t *find_list_entry2(const llist_t *list, const char *filename); + +extern USE_DESKTOP(long long) int uncompressStream(int src_fd, int dst_fd); +extern void inflate_init(unsigned int bufsize); +extern void inflate_cleanup(void); +extern USE_DESKTOP(long long) int inflate_unzip(int in, int out); +extern USE_DESKTOP(long long) int inflate_gunzip(int in, int out); +extern USE_DESKTOP(long long) int unlzma(int src_fd, int dst_fd); + +extern int open_transformer(int src_fd, + USE_DESKTOP(long long) int (*transformer)(int src_fd, int dst_fd)); + + +#endif diff --git a/include/usage.h b/include/usage.h new file mode 100644 index 000000000..0fba9b848 --- /dev/null +++ b/include/usage.h @@ -0,0 +1,3495 @@ +/* vi: set sw=8 ts=8: */ +/* + * This file suffers from chronically incorrect tabification + * of messages. Before editing this file: + * 1. Switch you editor to 8-space tab mode. + * 2. Do not use \t in messages, use real tab character. + * 3. Start each source line with message as follows: + * |<7 spaces>"text with tabs".... + */ + +#ifndef __BB_USAGE_H__ +#define __BB_USAGE_H__ + +#define addgroup_trivial_usage \ + "[-g GID] group_name [user_name]" +#define addgroup_full_usage \ + "Adds a group to the system\n\n" \ + "Options:\n" \ + " -g GID specify gid" + +#define adduser_trivial_usage \ + "[OPTIONS] user_name" +#define adduser_full_usage \ + "Adds a user to the system\n\n" \ + "Options:\n" \ + " -h DIR Assign home directory DIR\n" \ + " -g GECOS Assign gecos field GECOS\n" \ + " -s SHELL Assign login shell SHELL\n" \ + " -G Add the user to existing group GROUP\n" \ + " -S create a system user (ignored)\n" \ + " -D Do not assign a password (logins still possible via ssh)\n" \ + " -H Do not create the home directory" + +#define adjtimex_trivial_usage \ + "[-q] [-o offset] [-f frequency] [-p timeconstant] [-t tick]" +#define adjtimex_full_usage \ + "Reads and optionally sets system timebase parameters.\n" \ + "See adjtimex(2).\n\n" \ + "Options:\n" \ + " -q quiet mode - do not print\n" \ + " -o offset time offset, microseconds\n" \ + " -f frequency frequency adjust, integer kernel units (65536 is 1ppm)\n" \ + " (positive values make the system clock run fast)\n" \ + " -t tick microseconds per tick, usually 10000\n" \ + " -p timeconstant" + +#define ar_trivial_usage \ + "[-o] [-v] [-p] [-t] [-x] ARCHIVE FILES" +#define ar_full_usage \ + "Extract or list FILES from an ar archive.\n\n" \ + "Options:\n" \ + " -o preserve original dates\n" \ + " -p extract to stdout\n" \ + " -t list\n" \ + " -x extract\n" \ + " -v verbosely list files processed" + +#define arping_trivial_usage \ + "[-fqbDUA] [-c count] [-w timeout] [-i device] [-s sender] target" +#define arping_full_usage \ + "Ping hosts by ARP requests/replies.\n\n" \ + "Options:\n" \ + " -f Quit on first ARP reply\n" \ + " -q Be quiet\n" \ + " -b Keep broadcasting, don't go unicast\n" \ + " -D Duplicated address detection mode\n" \ + " -U Unsolicited ARP mode, update your neighbours\n" \ + " -A ARP answer mode, update your neighbours\n" \ + " -c count Stop after sending count ARP request packets\n" \ + " -w timeout Time to wait for ARP reply, in seconds\n" \ + " -i device Outgoing interface name, default is eth0\n" \ + " -s sender Set specific sender IP address\n" \ + " target Target IP address of ARP request" + +#define ash_trivial_usage \ + "[FILE]...\n" \ + "or: ash -c command [args]..." +#define ash_full_usage \ + "The ash shell (command interpreter)" + +#define awk_trivial_usage \ + "[OPTION]... [program-text] [FILE ...]" +#define awk_full_usage \ + "Options:\n" \ + " -v var=val assign value 'val' to variable 'var'\n" \ + " -F sep use 'sep' as field separator\n" \ + " -f progname read program source from file 'progname'" + +#define basename_trivial_usage \ + "FILE [SUFFIX]" +#define basename_full_usage \ + "Strips directory path and suffixes from FILE.\n" \ + "If specified, also removes any trailing SUFFIX." +#define basename_example_usage \ + "$ basename /usr/local/bin/foo\n" \ + "foo\n" \ + "$ basename /usr/local/bin/\n" \ + "bin\n" \ + "$ basename /foo/bar.txt .txt\n" \ + "bar" + +#define bunzip2_trivial_usage \ + "[OPTION]... [FILE]" +#define bunzip2_full_usage \ + "Uncompress FILE (or standard input if FILE is '-' or omitted).\n\n" \ + "Options:\n" \ + " -c Write output to standard output\n" \ + " -f Force" + +#define busybox_notes_usage \ + "Hello world!\n" + +#define bzcat_trivial_usage \ + "FILE" +#define bzcat_full_usage \ + "Uncompress to stdout." + +#define unlzma_trivial_usage \ + "[OPTION]... [FILE]" +#define unlzma_full_usage \ + "Uncompress FILE (or standard input if FILE is '-' or omitted).\n\n" \ + "Options:\n" \ + " -c Write output to standard output\n" \ + " -f Force" + +#define lzmacat_trivial_usage \ + "FILE" +#define lzmacat_full_usage \ + "Uncompress to stdout." + +#define cal_trivial_usage \ + "[-jy] [[month] year]" +#define cal_full_usage \ + "Display a calendar.\n" \ + "\nOptions:\n" \ + " -j Use julian dates\n" \ + " -y Display the entire year" + +#define cat_trivial_usage \ + "[-u] [FILE]..." +#define cat_full_usage \ + "Concatenates FILE(s) and prints them to stdout.\n\n" \ + "Options:\n" \ + " -u ignored since unbuffered i/o is always used" +#define cat_example_usage \ + "$ cat /proc/uptime\n" \ + "110716.72 17.67" + +#define catv_trivial_usage \ + "[-etv] [FILE]..." +#define catv_full_usage \ + "Display nonprinting characters as ^x or M-x.\n\n"\ + " -e End each line with $\n" \ + " -t Show tabs as ^I\n" \ + " -v Don't use ^x or M-x escapes." +#define chattr_trivial_usage \ + "[-R] [-+=AacDdijsStTu] [-v version] files..." +#define chattr_full_usage \ + "change file attributes on an ext2 fs\n\n" \ + "Modifiers:\n" \ + " - remove attributes\n" \ + " + add attributes\n" \ + " = set attributes\n" \ + "Attributes:\n" \ + " A don't track atime\n" \ + " a append mode only\n" \ + " c enable compress\n" \ + " D write dir contents synchronously\n" \ + " d do not backup with dump\n" \ + " i cannot be modified (immutable)\n" \ + " j write all data to journal first\n" \ + " s zero disk storage when deleted\n" \ + " S write file contents synchronously\n" \ + " t disable tail-merging of partial blocks with other files\n" \ + " u allow file to be undeleted\n" \ + "Options:\n" \ + " -R recursively list subdirectories\n" \ + " -v set the file's version/generation number" + +#define chgrp_trivial_usage \ + "[-Rh"USE_DESKTOP("cvf")"]... GROUP FILE..." +#define chgrp_full_usage \ + "Change the group membership of each FILE to GROUP.\n" \ + "\nOptions:\n" \ + " -R Changes files and directories recursively\n" \ + " -h Do not dereference symbolic links" \ + USE_DESKTOP( \ + "\n -c List changed files" \ + "\n -v List all files" \ + "\n -f Hide errors" \ + ) +#define chgrp_example_usage \ + "$ ls -l /tmp/foo\n" \ + "-r--r--r-- 1 andersen andersen 0 Apr 12 18:25 /tmp/foo\n" \ + "$ chgrp root /tmp/foo\n" \ + "$ ls -l /tmp/foo\n" \ + "-r--r--r-- 1 andersen root 0 Apr 12 18:25 /tmp/foo\n" + +#define chmod_trivial_usage \ + "[-R"USE_DESKTOP("cvf")"] MODE[,MODE]... FILE..." +#define chmod_full_usage \ + "Each MODE is one or more of the letters ugoa, one of the\n" \ + "symbols +-= and one or more of the letters rwxst.\n\n" \ + "Options:\n" \ + " -R Changes files and directories recursively" \ + USE_DESKTOP( \ + "\n -c List changed files" \ + "\n -v List all files" \ + "\n -f Hide errors" \ + ) +#define chmod_example_usage \ + "$ ls -l /tmp/foo\n" \ + "-rw-rw-r-- 1 root root 0 Apr 12 18:25 /tmp/foo\n" \ + "$ chmod u+x /tmp/foo\n" \ + "$ ls -l /tmp/foo\n" \ + "-rwxrw-r-- 1 root root 0 Apr 12 18:25 /tmp/foo*\n" \ + "$ chmod 444 /tmp/foo\n" \ + "$ ls -l /tmp/foo\n" \ + "-r--r--r-- 1 root root 0 Apr 12 18:25 /tmp/foo\n" + +#define chown_trivial_usage \ + "[-Rh"USE_DESKTOP("cvf")"]... OWNER[<.|:>[GROUP]] FILE..." +#define chown_full_usage \ + "Change the owner and/or group of each FILE to OWNER and/or GROUP.\n" \ + "\nOptions:\n" \ + " -R Changes files and directories recursively\n" \ + " -h Do not dereference symbolic links" \ + USE_DESKTOP( \ + "\n -c List changed files" \ + "\n -v List all files" \ + "\n -f Hide errors" \ + ) +#define chown_example_usage \ + "$ ls -l /tmp/foo\n" \ + "-r--r--r-- 1 andersen andersen 0 Apr 12 18:25 /tmp/foo\n" \ + "$ chown root /tmp/foo\n" \ + "$ ls -l /tmp/foo\n" \ + "-r--r--r-- 1 root andersen 0 Apr 12 18:25 /tmp/foo\n" \ + "$ chown root.root /tmp/foo\n" \ + "ls -l /tmp/foo\n" \ + "-r--r--r-- 1 root root 0 Apr 12 18:25 /tmp/foo\n" + +#define chpst_trivial_usage \ + "[-vP012] [-u user[:group]] [-U user[:group]] [-e dir] " \ + "[-/ dir] [-n nice] [-m bytes] [-d bytes] [-o files] " \ + "[-p processes] [-f bytes] [-c bytes] prog args" +#define chpst_full_usage \ + "Change the process state and run specified program.\n\n" \ + "-u user[:grp] set uid and gid\n" \ + "-U user[:grp] set environment variables UID and GID\n" \ + "-e dir set environment variables as specified by files\n" \ + " in the directory: file=1st_line_of_file\n" \ + "-/ dir chroot to dir\n" \ + "-n inc add inc to nice value\n" \ + "-m bytes limit data segment, stack segment, locked physical pages,\n" \ + " and total of all segment per process to bytes bytes each\n" \ + "-d bytes limit data segment\n" \ + "-o n limit the number of open file descriptors per process to n\n" \ + "-p n limit number of processes per uid to n\n" \ + "-f bytes limit output file size to bytes bytes\n" \ + "-c bytes limit core file size to bytes bytes\n" \ + "-v verbose\n" \ + "-P run prog in a new process group\n" \ + "-0 close standard input\n" \ + "-1 close standard output\n" \ + "-2 close standard error" +#define setuidgid_trivial_usage \ + "account prog args" +#define setuidgid_full_usage \ + "Sets uid and gid to account's uid and gid, removing all supplementary\n" \ + "groups, then runs prog" +#define envuidgid_trivial_usage \ + "account prog args" +#define envuidgid_full_usage \ + "Sets $UID to account's uid and $GID to account's gid, then runs prog" +#define envdir_trivial_usage \ + "dir prog args" +#define envdir_full_usage \ + "Sets various environment variables as specified by files\n" \ + "in the directory dir, then runs prog" +#define softlimit_trivial_usage \ + "[-a allbytes] [-c corebytes] [-d databytes] [-f filebytes] " \ + "[-l lockbytes] [-m membytes] [-o openfiles] [-p processes] " \ + "[-r residentbytes] [-s stackbytes] [-t cpusecs] prog args" +#define softlimit_full_usage \ + "Sets soft resource limits as specified by options, then runs prog\n" \ + "\n" \ + "-m n Same as -d n -s n -l n -a n\n" \ + "-d n Limit the data segment per process to n bytes\n" \ + "-s n Limit the stack segment per process to n bytes\n" \ + "-l n Limit the locked physical pages per process to n bytes\n" \ + "-a n Limit the total of all segments per process to n bytes\n" \ + "-o n Limit the number of open file descriptors per process to n\n" \ + "-p n Limit the number of processes per uid to n\n" \ + "Options controlling file sizes:\n" \ + "-f n Limit output file sizes to n bytes\n" \ + "-c n Limit core file sizes to n bytes\n" \ + "Efficiency opts:\n" \ + "-r n Limit the resident set size to n bytes. This limit is not\n" \ + " enforced unless physical memory is full\n" \ + "-t n Limit the CPU time to n seconds. This limit is not enforced\n" \ + " except that the process receives a SIGXCPU signal after n seconds\n" \ + "\n" \ + "Some options may have no effect on some operating systems\n" \ + "n may be =, indicating that soft limit should be set equal to hard limit" + +#define chroot_trivial_usage \ + "NEWROOT [COMMAND...]" +#define chroot_full_usage \ + "Run COMMAND with root directory set to NEWROOT." +#define chroot_example_usage \ + "$ ls -l /bin/ls\n" \ + "lrwxrwxrwx 1 root root 12 Apr 13 00:46 /bin/ls -> /BusyBox\n" \ + "# mount /dev/hdc1 /mnt -t minix\n" \ + "# chroot /mnt\n" \ + "# ls -l /bin/ls\n" \ + "-rwxr-xr-x 1 root root 40816 Feb 5 07:45 /bin/ls*\n" + +#define chvt_trivial_usage \ + "N" +#define chvt_full_usage \ + "Changes the foreground virtual terminal to /dev/ttyN" + +#define cksum_trivial_usage \ + "FILES..." +#define cksum_full_usage \ + "Calculates the CRC32 checksums of FILES." + +#define clear_trivial_usage \ + "" +#define clear_full_usage \ + "Clear screen." + +#define cmp_trivial_usage \ + "[-l] [-s] FILE1 [FILE2]" +#define cmp_full_usage \ + "Compares FILE1 vs stdin if FILE2 is not specified.\n\n" \ + "Options:\n" \ + " -l Write the byte numbers (decimal) and values (octal)\n" \ + " for all differing bytes\n" \ + " -s quiet mode - do not print" + +#define comm_trivial_usage \ + "[-123] FILE1 FILE2" +#define comm_full_usage \ + "Compares FILE1 to FILE2, or to stdin if = is specified.\n\n" \ + "Options:\n" \ + " -1 Suppress lines unique to FILE1\n" \ + " -2 Suppress lines unique to FILE2\n" \ + " -3 Suppress lines common to both files" + +#define bbconfig_trivial_usage \ + "" +#define bbconfig_full_usage \ + "Print the config file which built busybox" + +#define bbsh_trivial_usage \ + "[FILE]...\n" \ + "or: bbsh -c command [args]..." +#define bbsh_full_usage \ + "The bbsh shell (command interpreter)" + +#define cp_trivial_usage \ + "[OPTION]... SOURCE DEST" +#define cp_full_usage \ + "Copies SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.\n" \ + "\n" \ + " -a Same as -dpR\n" \ + " -d,-P Preserve links\n" \ + " -H,-L Dereference all symlinks (implied by default)\n" \ + " -p Preserve file attributes if possible\n" \ + " -f force, overwrite\n" \ + " -i interactive, prompt before overwrite\n" \ + " -R,-r Copy directories recursively\n" \ + " -l,-s Create (sym)links" + +#define cpio_trivial_usage \ + "-[dimtuv][F cpiofile]" +#define cpio_full_usage \ + "Extract or list files from a cpio archive\n" \ + "Main operation mode:\n" \ + " d make leading directories\n" \ + " i extract\n" \ + " m preserve mtime\n" \ + " t list\n" \ + " v verbose\n" \ + " u unconditional overwrite\n" \ + " F input from file" + +#define crond_trivial_usage \ + "-d[#] -c -f -b" +#define crond_full_usage \ + " -d [#] -l [#] -S -L logfile -f -b -c dir\n" \ + " -d num debug level\n" \ + " -l num log level (8 - default)\n" \ + " -S log to syslogd (default)\n" \ + " -L file log to file\n" \ + " -f run in foreground\n" \ + " -b run in background (default)\n" \ + " -c dir working dir" + +#define crontab_trivial_usage \ + "[-c dir] {file|-}|[-u|-l|-e|-d user]" +#define crontab_full_usage \ + " file replace crontab from file\n" \ + " - replace crontab from stdin\n" \ + " -u user specify user\n" \ + " -l [user] list crontab for user\n" \ + " -e [user] edit crontab for user\n" \ + " -d [user] delete crontab for user\n" \ + " -c dir specify crontab directory" + + +#define cut_trivial_usage \ + "[OPTION]... [FILE]..." +#define cut_full_usage \ + "Prints selected fields from each input FILE to standard output.\n\n" \ + "Options:\n" \ + " -b LIST Output only bytes from LIST\n" \ + " -c LIST Output only characters from LIST\n" \ + " -d CHAR Use CHAR instead of tab as the field delimiter\n" \ + " -s Output only the lines containing delimiter\n" \ + " -f N Print only these fields\n" \ + " -n Ignored" +#define cut_example_usage \ + "$ echo \"Hello world\" | cut -f 1 -d ' '\n" \ + "Hello\n" \ + "$ echo \"Hello world\" | cut -f 2 -d ' '\n" \ + "world\n" + +#define date_trivial_usage \ + "[OPTION]... [MMDDhhmm[[CC]YY][.ss]] [+FORMAT]" +#define date_full_usage \ + "Displays the current time in the given FORMAT, or sets the system date.\n" \ + "\nOptions:\n" \ + " -R Outputs RFC-822 compliant date string\n" \ + " -d STRING Displays time described by STRING, not 'now'\n" \ +USE_FEATURE_DATE_ISOFMT( \ + " -I[TIMESPEC] Outputs an ISO-8601 compliant date/time string\n" \ + " TIMESPEC='date' (or missing) for date only,\n" \ + " 'hours', 'minutes', or 'seconds' for date and,\n" \ + " time to the indicated precision\n" \ + " -D hint Use 'hint' as date format, via strptime()\n" \ +) \ + " -s Sets time described by STRING\n" \ + " -r FILE Displays the last modification time of FILE\n" \ + " -u Prints or sets Coordinated Universal Time" +#define date_example_usage \ + "$ date\n" \ + "Wed Apr 12 18:52:41 MDT 2000\n" + +#define dc_trivial_usage \ + "expression ..." +#define dc_full_usage \ + "This is a Tiny RPN calculator that understands the\n" \ + "following operations: +, add, -, sub, *, mul, /, div, %, mod, " \ + "**, exp, and, or, not, eor.\n" \ + "For example: 'dc 2 2 add' -> 4, and 'dc 8 8 \\* 2 2 + /' -> 16.\n" \ + "\nOptions:\n" \ + "p - Prints the value on the top of the stack, without altering the stack\n" \ + "f - Prints the entire contents of the stack without altering anything\n" \ + "o - Pops the value off the top of the stack and uses it to set the output radix\n" \ + " Only 10 and 16 are supported" +#define dc_example_usage \ + "$ dc 2 2 + p\n" \ + "4\n" \ + "$ dc 8 8 \\* 2 2 + / p\n" \ + "16\n" \ + "$ dc 0 1 and p\n" \ + "0\n" \ + "$ dc 0 1 or p\n" \ + "1\n" \ + "$ echo 72 9 div 8 mul p | dc\n" \ + "64\n" + +#define dd_trivial_usage \ + "[if=FILE] [of=FILE] " USE_FEATURE_DD_IBS_OBS("[ibs=N] [obs=N] ") "[bs=N] [count=N] [skip=N]\n" \ + " [seek=N]" USE_FEATURE_DD_IBS_OBS(" [conv=notrunc|noerror|sync]") +#define dd_full_usage \ + "Copy a file, converting and formatting according to options\n\n" \ + " if=FILE read from FILE instead of stdin\n" \ + " of=FILE write to FILE instead of stdout\n" \ + " bs=N read and write N bytes at a time\n" \ + USE_FEATURE_DD_IBS_OBS( \ + " ibs=N read N bytes at a time\n") \ + USE_FEATURE_DD_IBS_OBS( \ + " obs=N write N bytes at a time\n") \ + " count=N copy only N input blocks\n" \ + " skip=N skip N input blocks\n" \ + " seek=N skip N output blocks\n" \ + USE_FEATURE_DD_IBS_OBS( \ + " conv=notrunc don't truncate output file\n" \ + " conv=noerror continue after read errors\n" \ + " conv=sync pad blocks with zeros\n") \ + "\n" \ + "Numbers may be suffixed by c (x1), w (x2), b (x512), kD (x1000), k (x1024),\n" \ + "MD (x1000000), M (x1048576), GD (x1000000000) or G (x1073741824)" +#define dd_example_usage \ + "$ dd if=/dev/zero of=/dev/ram1 bs=1M count=4\n" \ + "4+0 records in\n" \ + "4+0 records out\n" + +#define deallocvt_trivial_usage \ + "[N]" +#define deallocvt_full_usage \ + "Deallocate unused virtual terminal /dev/ttyN" + +#define delgroup_trivial_usage \ + "GROUP" +#define delgroup_full_usage \ + "Deletes group GROUP from the system" + +#define deluser_trivial_usage \ + "USER" +#define deluser_full_usage \ + "Deletes user USER from the system" + +#define devfsd_trivial_usage \ + "mntpnt [-v]" \ + USE_DEVFSD_FG_NP("[-fg][-np]" ) +#define devfsd_full_usage \ + "Optional daemon for managing devfs permissions and old device name symlinks.\n" \ + "\nOptions:\n" \ + " mntpnt The mount point where devfs is mounted.\n\n" \ + " -v Print the protocol version numbers for devfsd\n" \ + " and the kernel-side protocol version and exits." \ + USE_DEVFSD_FG_NP( "\n\n -fg Run the daemon in the foreground.\n\n" \ + " -np Exit after parsing the configuration file\n" \ + " and processing synthetic REGISTER events.\n" \ + " Do not poll for events.") + +#define df_trivial_usage \ + "[-" USE_FEATURE_HUMAN_READABLE("hm") "k] [FILESYSTEM ...]" +#define df_full_usage \ + "Print the filesystem space used and space available.\n\n" \ + "Options:\n" \ + USE_FEATURE_HUMAN_READABLE( \ + "\n -h print sizes in human readable format (e.g., 1K 243M 2G )\n" \ + " -m print sizes in megabytes\n" \ + " -k print sizes in kilobytes(default)") \ + SKIP_FEATURE_HUMAN_READABLE("\n -k ignored") +#define df_example_usage \ + "$ df\n" \ + "Filesystem 1k-blocks Used Available Use% Mounted on\n" \ + "/dev/sda3 8690864 8553540 137324 98% /\n" \ + "/dev/sda1 64216 36364 27852 57% /boot\n" \ + "$ df /dev/sda3\n" \ + "Filesystem 1k-blocks Used Available Use% Mounted on\n" \ + "/dev/sda3 8690864 8553540 137324 98% /\n" + +#define dhcprelay_trivial_usage \ + "[client_device_list] [server_device]" +#define dhcprelay_full_usage \ + "Relays dhcp requests from client devices to server device" + +#define dhcprelay_trivial_usage \ + "[client_device_list] [server_device]" +#define dhcprelay_full_usage \ + "Relays dhcp requests from client devices to server device" + +#define diff_trivial_usage \ + "[-abdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2" +#define diff_full_usage \ + "Compare files line by line and output the differences between them.\n" \ + "This diff implementation only supports unified diffs.\n\n" \ + "Options:\n" \ + " -a Treat all files as text\n" \ + " -b Ignore changes in the amount of whitespace\n" \ + " -d Try hard to find a smaller set of changes\n" \ + " -i Ignore case differences\n" \ + " -L Use LABEL instead of the filename in the unified header\n" \ + " -N Treat absent files as empty\n" \ + " -q Output only whether files differ\n" \ + " -r Recursively compare any subdirectories\n" \ + " -S Start with FILE when comparing directories\n" \ + " -T Make tabs line up by prefixing a tab when necessary\n" \ + " -s Report when two files are the same\n" \ + " -t Expand tabs to spaces in output\n" \ + " -U Output LINES lines of context\n" \ + " -w Ignore all whitespace\n" + +#define dirname_trivial_usage \ + "FILENAME" +#define dirname_full_usage \ + "Strips non-directory suffix from FILENAME" +#define dirname_example_usage \ + "$ dirname /tmp/foo\n" \ + "/tmp\n" \ + "$ dirname /tmp/foo/\n" \ + "/tmp\n" + +#define dmesg_trivial_usage \ + "[-c] [-n LEVEL] [-s SIZE]" +#define dmesg_full_usage \ + "Prints or controls the kernel ring buffer\n\n" \ + "Options:\n" \ + " -c Clears the ring buffer's contents after printing\n" \ + " -n LEVEL Sets console logging level\n" \ + " -s SIZE Use a buffer of size SIZE" + +#define dnsd_trivial_usage \ + "[-c config] [-t seconds] [-p port] [-i iface-ip] [-d]" +#define dnsd_full_usage \ + "Small and static DNS server daemon\n\n" \ + "Options:\n" \ + " -c config filename\n" \ + " -t TTL in seconds\n" \ + " -p listening port\n" \ + " -i listening iface ip (default all)\n" \ + " -d daemonize" + +#define dos2unix_trivial_usage \ + "[option] [FILE]" +#define dos2unix_full_usage \ + "Converts FILE from dos format to unix format. When no option\n" \ + "is given, the input is converted to the opposite output format.\n" \ + "When no file is given, uses stdin for input and stdout for output.\n\n" \ + "Options:\n" \ + " -u output will be in UNIX format\n" \ + " -d output will be in DOS format" + +#define dpkg_trivial_usage \ + "[-ilCPru] [-F option] package_name" +#define dpkg_full_usage \ + "dpkg is a utility to install, remove and manage Debian packages.\n\n" \ + "Options:\n" \ + " -i Install the package\n" \ + " -l List of installed packages\n" \ + " -C Configure an unpackaged package\n" \ + " -F depends Ignore dependency problems\n" \ + " -P Purge all files of a package\n" \ + " -r Remove all but the configuration files for a package\n" \ + " -u Unpack a package, but don't configure it" + +#define dpkg_deb_trivial_usage \ + "[-cefxX] FILE [argument]" +#define dpkg_deb_full_usage \ + "Perform actions on Debian packages (.debs)\n\n" \ + "Options:\n" \ + " -c List contents of filesystem tree\n" \ + " -e Extract control files to [argument] directory\n" \ + " -f Display control field name starting with [argument]\n" \ + " -x Extract packages filesystem tree to directory\n" \ + " -X Verbose extract" +#define dpkg_deb_example_usage \ + "$ dpkg-deb -X ./busybox_0.48-1_i386.deb /tmp\n" + +#define du_trivial_usage \ + "[-aHLdclsx" USE_FEATURE_HUMAN_READABLE("hm") "k] [FILE]..." +#define du_full_usage \ + "Summarizes disk space used for each FILE and/or directory.\n" \ + "Disk space is printed in units of " \ + USE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K("1024") \ + SKIP_FEATURE_DU_DEFAULT_BLOCKSIZE_1K("512") \ + " bytes.\n\n" \ + "Options:\n" \ + " -a show sizes of files in addition to directories\n" \ + " -H follow symbolic links that are FILE command line args\n" \ + " -L follow all symbolic links encountered\n" \ + " -d N limit output to directories (and files with -a) of depth < N\n" \ + " -c output a grand total\n" \ + " -l count sizes many times if hard linked\n" \ + " -s display only a total for each argument\n" \ + " -x skip directories on different filesystems\n" \ + USE_FEATURE_HUMAN_READABLE( \ + " -h print sizes in human readable format (e.g., 1K 243M 2G )\n" \ + " -m print sizes in megabytes\n" \ + ) \ + " -k print sizes in kilobytes" \ + USE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K("(default)") +#define du_example_usage \ + "$ du\n" \ + "16 ./CVS\n" \ + "12 ./kernel-patches/CVS\n" \ + "80 ./kernel-patches\n" \ + "12 ./tests/CVS\n" \ + "36 ./tests\n" \ + "12 ./scripts/CVS\n" \ + "16 ./scripts\n" \ + "12 ./docs/CVS\n" \ + "104 ./docs\n" \ + "2417 .\n" + +#define dumpkmap_trivial_usage \ + "> keymap" +#define dumpkmap_full_usage \ + "Prints out a binary keyboard translation table to standard output." +#define dumpkmap_example_usage \ + "$ dumpkmap > keymap\n" + +#define dumpleases_trivial_usage \ + "[-r|-a] [-f LEASEFILE]" +#define dumpleases_full_usage \ + "Displays the DHCP leases granted by udhcpd.\n\n" \ + "Options:\n" \ + " -f, --file=FILENAME Leases file to load\n" \ + " -r, --remaining Interpret lease times as time remaining\n" \ + " -a, --absolute Interpret lease times as expire time" + +#define e2fsck_trivial_usage \ + "[-panyrcdfvstDFSV] [-b superblock] [-B blocksize] " \ + "[-I inode_buffer_blocks] [-P process_inode_size] " \ + "[-l|-L bad_blocks_file] [-C fd] [-j external_journal] " \ + "[-E extended-options] device" +#define e2fsck_full_usage \ + "Check a Linux ext2/ext3 file system.\n\n" \ + "Options:\n" \ + " -p Automatic repair (no questions)\n" \ + " -n Make no changes to the filesystem\n" \ + " -y Assume 'yes' to all questions\n" \ + " -c Check for bad blocks and add them to the badblock list\n" \ + " -f Force checking even if filesystem is marked clean\n" \ + " -v Be verbose\n" \ + " -b superblock Use alternative superblock\n" \ + " -B blocksize Force blocksize when looking for superblock\n" \ + " -j journal Set location of the external journal\n" \ + " -l file Add to badblocks list\n" \ + " -L file Set badblocks list" + +#define echo_trivial_usage \ + USE_FEATURE_FANCY_ECHO("[-neE] ") "[ARG ...]" +#define echo_full_usage \ + "Prints the specified ARGs to stdout\n\n" \ + USE_FEATURE_FANCY_ECHO( \ + "Options:\n" \ + " -n suppress trailing newline\n" \ + " -e interpret backslash-escaped characters (i.e., \\t=tab)\n" \ + " -E disable interpretation of backslash-escaped characters" \ + ) +#define echo_example_usage \ + "$ echo \"Erik is cool\"\n" \ + "Erik is cool\n" \ + USE_FEATURE_FANCY_ECHO("$ echo -e \"Erik\\nis\\ncool\"\n" \ + "Erik\n" \ + "is\n" \ + "cool\n" \ + "$ echo \"Erik\\nis\\ncool\"\n" \ + "Erik\\nis\\ncool\n") + +#define eject_trivial_usage \ + "[-t] [-T] [DEVICE]" +#define eject_full_usage \ + "Eject specified DEVICE (or default /dev/cdrom).\n\n" \ + "Options:\n" \ + " -t close tray\n" \ + " -T open/close tray (toggle)" + +#define ed_trivial_usage "" +#define ed_full_usage "" + +#define env_trivial_usage \ + "[-iu] [-] [name=value]... [command]" +#define env_full_usage \ + "Prints the current environment or runs a program after setting\n" \ + "up the specified environment.\n\n" \ + "Options:\n" \ + " -, -i start with an empty environment\n" \ + " -u remove variable from the environment" + +#define ether_wake_trivial_usage \ + "[-b] [-i iface] [-p aa:bb:cc:dd[:ee:ff]] MAC" +#define ether_wake_full_usage \ + "Send a magic packet to wake up sleeping machines.\n" \ + "MAC must be a station address (00:11:22:33:44:55) or\n" \ + " a hostname with a known 'ethers' entry.\n\n" \ + "Options:\n" \ + " -b Send wake-up packet to the broadcast address\n" \ + " -i iface Use interface ifname instead of the default \"eth0\"\n" \ + " -p pass Append the four or six byte password PW to the packet" + +#define expr_trivial_usage \ + "EXPRESSION" +#define expr_full_usage \ + "Prints the value of EXPRESSION to standard output.\n\n" \ + "EXPRESSION may be:\n" \ + " ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2\n" \ + " ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0\n" \ + " ARG1 < ARG2 ARG1 is less than ARG2\n" \ + " ARG1 <= ARG2 ARG1 is less than or equal to ARG2\n" \ + " ARG1 = ARG2 ARG1 is equal to ARG2\n" \ + " ARG1 != ARG2 ARG1 is unequal to ARG2\n" \ + " ARG1 >= ARG2 ARG1 is greater than or equal to ARG2\n" \ + " ARG1 > ARG2 ARG1 is greater than ARG2\n" \ + " ARG1 + ARG2 arithmetic sum of ARG1 and ARG2\n" \ + " ARG1 - ARG2 arithmetic difference of ARG1 and ARG2\n" \ + " ARG1 * ARG2 arithmetic product of ARG1 and ARG2\n" \ + " ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2\n" \ + " ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2\n" \ + " STRING : REGEXP anchored pattern match of REGEXP in STRING\n" \ + " match STRING REGEXP same as STRING : REGEXP\n" \ + " substr STRING POS LENGTH substring of STRING, POS counted from 1\n" \ + " index STRING CHARS index in STRING where any CHARS is found,\n" \ + " or 0\n" \ + " length STRING length of STRING\n" \ + " quote TOKEN interpret TOKEN as a string, even if\n" \ + " it is a keyword like 'match' or an\n" \ + " operator like '/'\n" \ + " ( EXPRESSION ) value of EXPRESSION\n\n" \ + "Beware that many operators need to be escaped or quoted for shells.\n" \ + "Comparisons are arithmetic if both ARGs are numbers, else\n" \ + "lexicographical. Pattern matches return the string matched between\n" \ + "\\( and \\) or null; if \\( and \\) are not used, they return the number\n" \ + "of characters matched or 0." + +#define fakeidentd_trivial_usage \ + "[-b ip] [STRING]" +#define fakeidentd_full_usage \ + "Returns a set string to auth requests\n\n" \ + " -b Bind to ip address\n" \ + " STRING The ident answer string (default is nobody)" + +#define false_trivial_usage \ + "" +#define false_full_usage \ + "Return an exit code of FALSE (1)." +#define false_example_usage \ + "$ false\n" \ + "$ echo $?\n" \ + "1\n" + +#define fbset_trivial_usage \ + "[options] [mode]" +#define fbset_full_usage \ + "Show and modify frame buffer settings" +#define fbset_example_usage \ + "$ fbset\n" \ + "mode \"1024x768-76\"\n" \ + " # D: 78.653 MHz, H: 59.949 kHz, V: 75.694 Hz\n" \ + " geometry 1024 768 1024 768 16\n" \ + " timings 12714 128 32 16 4 128 4\n" \ + " accel false\n" \ + " rgba 5/11,6/5,5/0,0/0\n" \ + "endmode\n" + +#define fdflush_trivial_usage \ + "DEVICE" +#define fdflush_full_usage \ + "Forces floppy disk drive to detect disk change" + +#define fdformat_trivial_usage \ + "[-n] DEVICE" +#define fdformat_full_usage \ + "Low-level formats a floppy disk\n\n" \ + "Options:\n" \ + " -n Don't verify after format" + +#define fdisk_trivial_usage \ + "[-luv] [-C CYLINDERS] [-H HEADS] [-S SECTORS] [-b SSZ] DISK" +#define fdisk_full_usage \ + "Change partition table\n" \ + "Options:\n" \ + " -l List partition table(s)\n" \ + " -u Give Start and End in sector (instead of cylinder) units\n" \ + " -s PARTITION Give partition size(s) in blocks\n" \ + " -b 2048: (for certain MO disks) use 2048-byte sectors\n" \ + " -C CYLINDERS Set the number of cylinders\n" \ + " -H HEADS Set the number of heads\n" \ + " -S SECTORS Set the number of sectors\n" \ + " -v Give fdisk version" + +#define find_trivial_usage \ + "[PATH...] [EXPRESSION]" +#define find_full_usage \ + "Search for files in a directory hierarchy. The default PATH is\n" \ + "the current directory; default EXPRESSION is '-print'\n" \ + "\nEXPRESSION may consist of:\n" \ + " -follow Dereference symbolic links\n" \ + " -name PATTERN File name (leading directories removed) matches PATTERN\n" \ + " -print Print (default and assumed)" \ + USE_FEATURE_FIND_PRINT0( \ + "\n -print0 Delimit output with null characters rather than" \ + "\n newlines" \ + ) USE_FEATURE_FIND_TYPE( \ + "\n -type X Filetype matches X (where X is one of: f,d,l,b,c,...)" \ + ) USE_FEATURE_FIND_PERM( \ + "\n -perm PERMS Permissions match any of (+NNN); all of (-NNN);" \ + "\n or exactly (NNN)" \ + ) USE_FEATURE_FIND_MTIME( \ + "\n -mtime DAYS Modified time is greater than (+N); less than (-N);" \ + "\n or exactly (N) days" \ + ) USE_FEATURE_FIND_MMIN( \ + "\n -mmin MINS Modified time is greater than (+N); less than (-N);" \ + "\n or exactly (N) minutes" \ + ) USE_FEATURE_FIND_NEWER( \ + "\n -newer FILE Modified time is more recent than FILE's" \ + ) USE_FEATURE_FIND_INUM( \ + "\n -inum N File has inode number N" \ + ) USE_FEATURE_FIND_EXEC( \ + "\n -exec CMD Execute CMD with all instances of {} replaced by the" \ + "\n files matching EXPRESSION" \ + ) USE_DESKTOP( \ + "\n -size N File size is N" \ + "\n -prune Stop traversing current subtree" \ + "\n (expr) Group" \ + ) + +#define find_example_usage \ + "$ find / -name passwd\n" \ + "/etc/passwd\n" + +#define fold_trivial_usage \ + "[-bs] [-w WIDTH] [FILE]" +#define fold_full_usage \ + "Wrap input lines in each FILE (standard input by default), writing to\n" \ + "standard output.\n\n" \ + "Options:\n" \ + " -b count bytes rather than columns\n" \ + " -s break at spaces\n" \ + " -w use WIDTH columns instead of 80" + +#define free_trivial_usage \ + "" +#define free_full_usage \ + "Displays the amount of free and used system memory" +#define free_example_usage \ + "$ free\n" \ + " total used free shared buffers\n" \ + " Mem: 257628 248724 8904 59644 93124\n" \ + " Swap: 128516 8404 120112\n" \ + "Total: 386144 257128 129016\n" \ + +#define freeramdisk_trivial_usage \ + "DEVICE" +#define freeramdisk_full_usage \ + "Frees all memory used by the specified ramdisk." +#define freeramdisk_example_usage \ + "$ freeramdisk /dev/ram2\n" + +#define fsck_trivial_usage \ + "[-ANPRTV] [ -C [ fd ] ] [-t fstype] [fs-options] [filesys ...]" +#define fsck_full_usage \ + "Check and repair filesystems.\n\n" \ + "Options:\n" \ + " -A Walk /etc/fstab and check all filesystems\n" \ + " -N Don't execute, just show what would be done\n" \ + " -P When using -A, check filesystems in parallel\n" \ + " -R When using -A, skip the root filesystem\n" \ + " -T Don't show title on startup\n" \ + " -V Verbose mode\n" \ + " -C Write status information to specified filedescriptor\n" \ + " -t List of filesystem types to check" + +#define fsck_minix_trivial_usage \ + "[-larvsmf] /dev/name" +#define fsck_minix_full_usage \ + "Performs a consistency check for MINIX filesystems.\n\n" \ + "Options:\n" \ + " -l Lists all filenames\n" \ + " -r Perform interactive repairs\n" \ + " -a Perform automatic repairs\n" \ + " -v verbose\n" \ + " -s Outputs super-block information\n" \ + " -m Activates MINIX-like \"mode not cleared\" warnings\n" \ + " -f Force file system check" + +#define ftpget_trivial_usage \ + "[options] remote-host local-file remote-file" +#define ftpget_full_usage \ + "Retrieve a remote file via FTP.\n\n" \ + "Options:\n" \ + " -c, --continue Continue a previous transfer\n" \ + " -v, --verbose Verbose\n" \ + " -u, --username Username to be used\n" \ + " -p, --password Password to be used\n" \ + " -P, --port Port number to be used" + +#define ftpput_trivial_usage \ + "[options] remote-host remote-file local-file" +#define ftpput_full_usage \ + "Store a local file on a remote machine via FTP.\n\n" \ + "Options:\n" \ + " -v, --verbose Verbose\n" \ + " -u, --username Username to be used\n" \ + " -p, --password Password to be used\n" \ + " -P, --port Port number to be used" + +#define fuser_trivial_usage \ + "[options] file OR port/proto" +#define fuser_full_usage \ + "Options:\n" \ + " -m Show all processes on the same mounted fs\n" \ + " -k Kill all processes that match.\n" \ + " -s Don't print or kill anything.\n" \ + " -4 When using port/proto only search IPv4 space\n" \ + " -6 When using port/proto only search IPv6 space\n" \ + " -SIGNAL When used with -k, this signal will be used to kill" + +#define getopt_trivial_usage \ + "[OPTIONS]..." +#define getopt_full_usage \ + "Parse command options\n" \ + " -a, --alternative Allow long options starting with single -\n" \ + " -l, --longoptions=longopts Long options to be recognized\n" \ + " -n, --name=progname The name under which errors are reported\n" \ + " -o, --options=optstring Short options to be recognized\n" \ + " -q, --quiet Disable error reporting by getopt(3)\n" \ + " -Q, --quiet-output No normal output\n" \ + " -s, --shell=shell Set shell quoting conventions\n" \ + " -T, --test Test for getopt(1) version\n" \ + " -u, --unquoted Do not quote the output" +#define getopt_example_usage \ + "$ cat getopt.test\n" \ + "#!/bin/sh\n" \ + "GETOPT=`getopt -o ab:c:: --long a-long,b-long:,c-long:: \\\n" \ + " -n 'example.busybox' -- \"$@\"`\n" \ + "if [ $? != 0 ] ; then exit 1 ; fi\n" \ + "eval set -- \"$GETOPT\"\n" \ + "while true ; do\n" \ + " case $1 in\n" \ + " -a|--a-long) echo \"Option a\" ; shift ;;\n" \ + " -b|--b-long) echo \"Option b, argument '$2'\" ; shift 2 ;;\n" \ + " -c|--c-long)\n" \ + " case \"$2\" in\n" \ + " \"\") echo \"Option c, no argument\"; shift 2 ;;\n" \ + " *) echo \"Option c, argument '$2'\" ; shift 2 ;;\n" \ + " esac ;;\n" \ + " --) shift ; break ;;\n" \ + " *) echo \"Internal error!\" ; exit 1 ;;\n" \ + " esac\n" \ + "done\n" + +#define getty_trivial_usage \ + "[OPTIONS]... baud_rate,... line [termtype]" +#define getty_full_usage \ + "Opens a tty, prompts for a login name, then invokes /bin/login\n\n" \ + "Options:\n" \ + " -h Enable hardware (RTS/CTS) flow control\n" \ + " -i Do not display /etc/issue before running login\n" \ + " -L Local line, so do not do carrier detect\n" \ + " -m Get baud rate from modem's CONNECT status message\n" \ + " -w Wait for a CR or LF before sending /etc/issue\n" \ + " -n Do not prompt the user for a login name\n" \ + " -f issue_file Display issue_file instead of /etc/issue\n" \ + " -l login_app Invoke login_app instead of /bin/login\n" \ + " -t timeout Terminate after timeout if no username is read\n" \ + " -I initstring Sets the init string to send before anything else\n" \ + " -H login_host Log login_host into the utmp file as the hostname" + +#define grep_trivial_usage \ + "[-rihHnqvso" \ + USE_FEATURE_GREP_EGREP_ALIAS("E") \ + USE_FEATURE_GREP_CONTEXT("ABC") \ + "] PATTERN [FILEs...]" +#define grep_full_usage \ + "Search for PATTERN in each FILE or standard input.\n\n" \ + "Options:\n" \ + " -H prefix output lines with filename where match was found\n" \ + " -h suppress the prefixing filename on output\n" \ + " -r recurse subdirectories\n" \ + " -i ignore case distinctions\n" \ + " -l list names of files that match\n" \ + " -L list names of files that do not match\n" \ + " -n print line number with output lines\n" \ + " -q be quiet. Returns 0 if PATTERN was found, 1 otherwise\n" \ + " -v select non-matching lines\n" \ + " -s suppress file open/read error messages\n" \ + " -c only print count of matching lines\n" \ + " -f read PATTERN from file\n" \ + " -o show only the part of a line that matches PATTERN\n" \ + " -e PATTERN is a regular expression\n" \ + " -F PATTERN is a set of newline-separated strings" \ + USE_FEATURE_GREP_EGREP_ALIAS("\n -E PATTERN is an extended regular expression") \ + USE_FEATURE_GREP_CONTEXT("\n -A print NUM lines of trailing context") \ + USE_FEATURE_GREP_CONTEXT("\n -B print NUM lines of leading context") \ + USE_FEATURE_GREP_CONTEXT("\n -C print NUM lines of output context") + +#define grep_example_usage \ + "$ grep root /etc/passwd\n" \ + "root:x:0:0:root:/root:/bin/bash\n" \ + "$ grep ^[rR]oo. /etc/passwd\n" \ + "root:x:0:0:root:/root:/bin/bash\n" + +#define gunzip_trivial_usage \ + "[OPTION]... FILE" +#define gunzip_full_usage \ + "Uncompress FILE (or standard input if FILE is '-').\n\n" \ + "Options:\n" \ + " -c Write output to standard output\n" \ + " -f Force read when source is a terminal\n" \ + " -t Test compressed file integrity" +#define gunzip_example_usage \ + "$ ls -la /tmp/BusyBox*\n" \ + "-rw-rw-r-- 1 andersen andersen 557009 Apr 11 10:55 /tmp/BusyBox-0.43.tar.gz\n" \ + "$ gunzip /tmp/BusyBox-0.43.tar.gz\n" \ + "$ ls -la /tmp/BusyBox*\n" \ + "-rw-rw-r-- 1 andersen andersen 1761280 Apr 14 17:47 /tmp/BusyBox-0.43.tar\n" + +#define gzip_trivial_usage \ + "[OPTION]... [FILE]..." +#define gzip_full_usage \ + "Compress FILE(s) with maximum compression.\n" \ + "When FILE is '-' or unspecified, reads standard input. Implies -c.\n\n" \ + "Options:\n" \ + " -c Write output to standard output instead of FILE.gz\n" \ + " -d Decompress\n" \ + " -f Force write when destination is a terminal" +#define gzip_example_usage \ + "$ ls -la /tmp/busybox*\n" \ + "-rw-rw-r-- 1 andersen andersen 1761280 Apr 14 17:47 /tmp/busybox.tar\n" \ + "$ gzip /tmp/busybox.tar\n" \ + "$ ls -la /tmp/busybox*\n" \ + "-rw-rw-r-- 1 andersen andersen 554058 Apr 14 17:49 /tmp/busybox.tar.gz\n" + +#define halt_trivial_usage \ + "[-d] [-n] [-f]" +#define halt_full_usage \ + "Halt the system.\n" \ + "Options:\n" \ + " -d delay interval for halting\n" \ + " -n no call to sync()\n" \ + " -f force halt (don't go through init)" + +#define hdparm_trivial_usage \ + "[options] [device] .." +#define hdparm_full_usage \ + USE_FEATURE_HDPARM_GET_IDENTITY(" If no device name is specified try to read from stdin.\n") \ + "\nOptions:\n" \ + " -a get/set fs readahead\n" \ + " -A set drive read-lookahead flag (0/1)\n" \ + " -b get/set bus state (0 == off, 1 == on, 2 == tristate)\n" \ + " -B set Advanced Power Management setting (1-255)\n" \ + " -c get/set IDE 32-bit IO setting\n" \ + " -C check IDE power mode status\n" \ + USE_FEATURE_HDPARM_HDIO_GETSET_DMA(" -d get/set using_dma flag\n") \ + " -D enable/disable drive defect-mgmt\n" \ + " -f flush buffer cache for device on exit\n" \ + " -g display drive geometry\n" \ + " -h display terse usage information\n" \ + USE_FEATURE_HDPARM_GET_IDENTITY(" -i display drive identification\n") \ + USE_FEATURE_HDPARM_GET_IDENTITY(" -I detailed/current information directly from drive\n") \ + " -k get/set keep_settings_over_reset flag (0/1)\n" \ + " -K set drive keep_features_over_reset flag (0/1)\n" \ + " -L set drive doorlock (0/1) (removable harddisks only)\n" \ + " -m get/set multiple sector count\n" \ + " -n get/set ignore-write-errors flag (0/1)\n" \ + " -p set PIO mode on IDE interface chipset (0,1,2,3,4,...)\n" \ + " -P set drive prefetch count\n" \ + " -q change next setting quietly\n" \ + " -Q get/set DMA tagged-queuing depth (if supported)\n" \ + " -r get/set readonly flag (DANGEROUS to set)\n" \ + USE_FEATURE_HDPARM_HDIO_SCAN_HWIF(" -R register an IDE interface (DANGEROUS)\n") \ + " -S set standby (spindown) timeout\n" \ + " -t perform device read timings\n" \ + " -T perform cache read timings\n" \ + " -u get/set unmaskirq flag (0/1)\n" \ + USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF(" -U un-register an IDE interface (DANGEROUS)\n") \ + " -v defaults; same as -mcudkrag for IDE drives\n" \ + " -V display program version and exit immediately\n" \ + USE_FEATURE_HDPARM_HDIO_DRIVE_RESET(" -w perform device reset (DANGEROUS)\n") \ + " -W set drive write-caching flag (0/1) (DANGEROUS)\n" \ + USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(" -x tristate device for hotswap (0/1) (DANGEROUS)\n") \ + " -X set IDE xfer mode (DANGEROUS)\n" \ + " -y put IDE drive in standby mode\n" \ + " -Y put IDE drive to sleep\n" \ + " -Z disable Seagate auto-powersaving mode\n" \ + " -z re-read partition table" + +#define head_trivial_usage \ + "[OPTION]... [FILE]..." +#define head_full_usage \ + "Print first 10 lines of each FILE to standard output.\n" \ + "With more than one FILE, precede each with a header giving the\n" \ + "file name. With no FILE, or when FILE is -, read standard input.\n\n" \ + "Options:\n" \ + " -n NUM Print first NUM lines instead of first 10" \ + USE_FEATURE_FANCY_HEAD( \ + "\n -c NUM output the first NUM bytes\n" \ + " -q never output headers giving file names\n" \ + " -v always output headers giving file names" ) +#define head_example_usage \ + "$ head -n 2 /etc/passwd\n" \ + "root:x:0:0:root:/root:/bin/bash\n" \ + "daemon:x:1:1:daemon:/usr/sbin:/bin/sh\n" + +#define hexdump_trivial_usage \ + "[-[bcCdefnosvx]] [OPTION] FILE" +#define hexdump_full_usage \ + "The hexdump utility is a filter which displays the specified files,\n" \ + "or the standard input, if no files are specified, in a user specified\n" \ + "format\n" \ + " -b One-byte octal display\n" \ + " -c One-byte character display\n" \ + " -C Canonical hex+ASCII, 16 bytes per line\n" \ + " -d Two-byte decimal display\n" \ + " -e FORMAT STRING\n" \ + " -f FORMAT FILE\n" \ + " -n LENGTH Interpret only length bytes of input\n" \ + " -o Two-byte octal display\n" \ + " -s OFFSET Skip offset byte\n" \ + " -v display all input data\n" \ + " -x Two-byte hexadecimal display" + +#define hostid_trivial_usage \ + "" +#define hostid_full_usage \ + "Print out a unique 32-bit identifier for the machine." + +#define hostname_trivial_usage \ + "[OPTION] {hostname | -F FILE}" +#define hostname_full_usage \ + "Get or set the hostname or DNS domain name. If a hostname is given\n" \ + "(or FILE with the -F parameter), the host name will be set.\n\n" \ + "Options:\n" \ + " -s Short\n" \ + " -i Addresses for the hostname\n" \ + " -d DNS domain name\n" \ + " -f Fully qualified domain name\n" \ + " -F FILE Use the contents of FILE to specify the hostname" +#define hostname_example_usage \ + "$ hostname\n" \ + "sage\n" + +#define httpd_trivial_usage \ + "[-c ]" \ + " [-p ]" \ + " [-i] [-f]" \ + USE_FEATURE_HTTPD_SETUID(" [-u user[:grp]]") \ + USE_FEATURE_HTTPD_BASIC_AUTH(" [-r ]") \ + USE_FEATURE_HTTPD_AUTH_MD5(" [-m pass]") \ + " [-h home]" \ + " [-d/-e ]" +#define httpd_full_usage \ + "Listens for incoming http server requests.\n\n" \ + "Options:\n" \ + " -c FILE Specifies configuration file. (default httpd.conf)\n" \ + " -p PORT Server port (default 80)\n" \ + " -i Assume that we are started from inetd\n" \ + " -f Do not daemonize\n" \ + USE_FEATURE_HTTPD_SETUID(" -u USER[:GRP] Set uid/gid after binding to port\n") \ + USE_FEATURE_HTTPD_BASIC_AUTH(" -r REALM Authentication Realm for Basic Authentication\n") \ + USE_FEATURE_HTTPD_AUTH_MD5(" -m PASS Crypt PASS with md5 algorithm\n") \ + " -h HOME Specifies http HOME directory (default ./)\n" \ + " -e STRING HTML encode STRING\n" \ + " -d STRING URL decode STRING" + +#define hwclock_trivial_usage \ + "[-r|--show] [-s|--hctosys] [-w|--systohc] [-l|--localtime] [-u|--utc]" +#define hwclock_full_usage \ + "Query and set the hardware clock (RTC)\n\n" \ + "Options:\n" \ + " -r read hardware clock and print result\n" \ + " -s set the system time from the hardware clock\n" \ + " -w set the hardware clock to the current system time\n" \ + " -u the hardware clock is kept in coordinated universal time\n" \ + " -l the hardware clock is kept in local time" + +#define id_trivial_usage \ + "[OPTIONS]... [USERNAME]" +#define id_full_usage \ + "Print information for USERNAME or the current user\n\n" \ + "Options:\n" \ + USE_SELINUX(" -c prints only the security context\n") \ + " -g prints only the group ID\n" \ + " -u prints only the user ID\n" \ + " -n print a name instead of a number\n" \ + " -r prints the real user ID instead of the effective ID" +#define id_example_usage \ + "$ id\n" \ + "uid=1000(andersen) gid=1000(andersen)\n" + +#define ifconfig_trivial_usage \ + USE_FEATURE_IFCONFIG_STATUS("[-a]") " [
    ]" +#define ifconfig_full_usage \ + "configure a network interface\n\n" \ + "Options:\n" \ + USE_FEATURE_IPV6(" [add
    [/]]\n") \ + USE_FEATURE_IPV6(" [del
    [/]]\n") \ + " [[-]broadcast [
    ]] [[-]pointopoint [
    ]]\n" \ + " [netmask
    ] [dstaddr
    ]\n" \ + USE_FEATURE_IFCONFIG_SLIP(" [outfill ] [keepalive ]\n") \ + " " USE_FEATURE_IFCONFIG_HW("[hw ether
    ] ") \ + "[metric ] [mtu ]\n" \ + " [[-]trailers] [[-]arp] [[-]allmulti]\n" \ + " [multicast] [[-]promisc] [txqueuelen ] [[-]dynamic]\n" \ + USE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ(" [mem_start ] [io_addr ] [irq ]\n") \ + " [up|down] ..." + +#define ifup_trivial_usage \ + "<-ahinv> " +#define ifup_full_usage \ + "ifup \n\n" \ + "Options:\n" \ + " -h this help\n" \ + " -a de/configure all interfaces automatically\n" \ + " -i FILE use FILE for interface definitions\n" \ + " -n print out what would happen, but don't do it\n" \ + " (note that this option doesn't disable mappings)\n" \ + " -v print out what would happen before doing it\n" \ + " -m don't run any mappings\n" \ + " -f force de/configuration" + +#define ifdown_trivial_usage \ + "<-ahinv> " +#define ifdown_full_usage \ + "ifdown \n\n" \ + "Options:\n" \ + " -h this help\n" \ + " -a de/configure all interfaces automatically\n" \ + " -i FILE use FILE for interface definitions\n" \ + " -n print out what would happen, but don't do it\n" \ + " (note that this option doesn't disable mappings)\n" \ + " -v print out what would happen before doing it\n" \ + " -m don't run any mappings\n" \ + " -f force de/configuration" + +#define inetd_trivial_usage \ + "[-f] [-q len] [conf]" +#define inetd_full_usage \ + "Listens for network connections and launches programs\n\n" \ + "Option:\n" \ + " -f Run as a foreground progress\n" \ + " -q Sets the size of the socket listen queue to\n" \ + " the specified value. Default is 128" + +#define init_trivial_usage \ + "" +#define init_full_usage \ + "Init is the parent of all processes." +#define init_notes_usage \ +"This version of init is designed to be run only by the kernel.\n" \ +"\n" \ +"BusyBox init doesn't support multiple runlevels. The runlevels field of\n" \ +"the /etc/inittab file is completely ignored by BusyBox init. If you want\n" \ +"runlevels, use sysvinit.\n" \ +"\n" \ +"BusyBox init works just fine without an inittab. If no inittab is found,\n" \ +"it has the following default behavior:\n" \ +"\n" \ +" ::sysinit:/etc/init.d/rcS\n" \ +" ::askfirst:/bin/sh\n" \ +" ::ctrlaltdel:/sbin/reboot\n" \ +" ::shutdown:/sbin/swapoff -a\n" \ +" ::shutdown:/bin/umount -a -r\n" \ +" ::restart:/sbin/init\n" \ +"\n" \ +"if it detects that /dev/console is _not_ a serial console, it will also run:\n" \ +"\n" \ +" tty2::askfirst:/bin/sh\n" \ +" tty3::askfirst:/bin/sh\n" \ +" tty4::askfirst:/bin/sh\n" \ +"\n" \ +"If you choose to use an /etc/inittab file, the inittab entry format is as follows:\n" \ +"\n" \ +" :::\n" \ +"\n" \ +" :\n" \ +"\n" \ +" WARNING: This field has a non-traditional meaning for BusyBox init!\n" \ +" The id field is used by BusyBox init to specify the controlling tty for\n" \ +" the specified process to run on. The contents of this field are\n" \ +" appended to \"/dev/\" and used as-is. There is no need for this field to\n" \ +" be unique, although if it isn't you may have strange results. If this\n" \ +" field is left blank, the controlling tty is set to the console. Also\n" \ +" note that if BusyBox detects that a serial console is in use, then only\n" \ +" entries whose controlling tty is either the serial console or /dev/null\n" \ +" will be run. BusyBox init does nothing with utmp. We don't need no\n" \ +" stinkin' utmp.\n" \ +"\n" \ +" :\n" \ +"\n" \ +" The runlevels field is completely ignored.\n" \ +"\n" \ +" :\n" \ +"\n" \ +" Valid actions include: sysinit, respawn, askfirst, wait,\n" \ +" once, restart, ctrlaltdel, and shutdown.\n" \ +"\n" \ +" The available actions can be classified into two groups: actions\n" \ +" that are run only once, and actions that are re-run when the specified\n" \ +" process exits.\n" \ +"\n" \ +" Run only-once actions:\n" \ +"\n" \ +" 'sysinit' is the first item run on boot. init waits until all\n" \ +" sysinit actions are completed before continuing. Following the\n" \ +" completion of all sysinit actions, all 'wait' actions are run.\n" \ +" 'wait' actions, like 'sysinit' actions, cause init to wait until\n" \ +" the specified task completes. 'once' actions are asynchronous,\n" \ +" therefore, init does not wait for them to complete. 'restart' is\n" \ +" the action taken to restart the init process. By default this should\n" \ +" simply run /sbin/init, but can be a script which runs pivot_root or it\n" \ +" can do all sorts of other interesting things. The 'ctrlaltdel' init\n" \ +" actions are run when the system detects that someone on the system\n" \ +" console has pressed the CTRL-ALT-DEL key combination. Typically one\n" \ +" wants to run 'reboot' at this point to cause the system to reboot.\n" \ +" Finally the 'shutdown' action specifies the actions to taken when\n" \ +" init is told to reboot. Unmounting filesystems and disabling swap\n" \ +" is a very good here\n" \ +"\n" \ +" Run repeatedly actions:\n" \ +"\n" \ +" 'respawn' actions are run after the 'once' actions. When a process\n" \ +" started with a 'respawn' action exits, init automatically restarts\n" \ +" it. Unlike sysvinit, BusyBox init does not stop processes from\n" \ +" respawning out of control. The 'askfirst' actions acts just like\n" \ +" respawn, except that before running the specified process it\n" \ +" displays the line \"Please press Enter to activate this console.\"\n" \ +" and then waits for the user to press enter before starting the\n" \ +" specified process.\n" \ +"\n" \ +" Unrecognized actions (like initdefault) will cause init to emit an\n" \ +" error message, and then go along with its business. All actions are\n" \ +" run in the order they appear in /etc/inittab.\n" \ +"\n" \ +" :\n" \ +"\n" \ +" Specifies the process to be executed and its command line.\n" \ +"\n" \ +"Example /etc/inittab file:\n" \ +"\n" \ +" # This is run first except when booting in single-user mode.\n" \ +" #\n" \ +" ::sysinit:/etc/init.d/rcS\n" \ +" \n" \ +" # /bin/sh invocations on selected ttys\n" \ +" #\n" \ +" # Start an \"askfirst\" shell on the console (whatever that may be)\n" \ +" ::askfirst:-/bin/sh\n" \ +" # Start an \"askfirst\" shell on /dev/tty2-4\n" \ +" tty2::askfirst:-/bin/sh\n" \ +" tty3::askfirst:-/bin/sh\n" \ +" tty4::askfirst:-/bin/sh\n" \ +" \n" \ +" # /sbin/getty invocations for selected ttys\n" \ +" #\n" \ +" tty4::respawn:/sbin/getty 38400 tty4\n" \ +" tty5::respawn:/sbin/getty 38400 tty5\n" \ +" \n" \ +" \n" \ +" # Example of how to put a getty on a serial line (for a terminal)\n" \ +" #\n" \ +" #::respawn:/sbin/getty -L ttyS0 9600 vt100\n" \ +" #::respawn:/sbin/getty -L ttyS1 9600 vt100\n" \ +" #\n" \ +" # Example how to put a getty on a modem line.\n" \ +" #::respawn:/sbin/getty 57600 ttyS2\n" \ +" \n" \ +" # Stuff to do when restarting the init process\n" \ +" ::restart:/sbin/init\n" \ +" \n" \ +" # Stuff to do before rebooting\n" \ +" ::ctrlaltdel:/sbin/reboot\n" \ +" ::shutdown:/bin/umount -a -r\n" \ +" ::shutdown:/sbin/swapoff -a\n" + +#define insmod_trivial_usage \ + "[OPTION]... MODULE [symbol=value]..." +#define insmod_full_usage \ + "Loads the specified kernel modules into the kernel.\n\n" \ + "Options:\n" \ + " -f Force module to load into the wrong kernel version\n" \ + " -k Make module autoclean-able\n" \ + " -v verbose output\n" \ + " -q quiet output\n" \ + " -L Lock to prevent simultaneous loads of a module\n" \ + USE_FEATURE_INSMOD_LOAD_MAP(" -m Output load map to stdout\n") \ + " -o NAME Set internal module name to NAME\n" \ + " -x do not export externs" + +#define install_trivial_usage \ + "[-cgmops] [sources] " +#define install_full_usage \ + "Copies files and set attributes\n\n" \ + "Options:\n" \ + " -c copy the file, default\n" \ + " -d create directories\n" \ + " -g set group ownership\n" \ + " -m set permission modes\n" \ + " -o set ownership\n" \ + " -p preserve date\n" \ + " -s strip symbol tables" + +#define ip_trivial_usage \ + "[OPTIONS] {address | link | route | tunnel | rule} {COMMAND}" +#define ip_full_usage \ + "ip [OPTIONS] OBJECT {COMMAND}\n" \ + "where OBJECT := {link | addr | route | tunnel |rule}\n" \ + "OPTIONS := { -f[amily] { inet | inet6 | link } | -o[neline] }" + +#define ipaddr_trivial_usage \ + "{ {add|del} IFADDR dev STRING | {show|flush}\n" \ + " [ dev STRING ] [ to PREFIX ] }" +#define ipaddr_full_usage \ + "ipaddr {add|delete} IFADDR dev STRING\n" \ + "ipaddr {show|flush} [ dev STRING ] [ scope SCOPE-ID ]\n" \ + " [ to PREFIX ] [ label PATTERN ]\n" \ + " IFADDR := PREFIX | ADDR peer PREFIX\n" \ + " [ broadcast ADDR ] [ anycast ADDR ]\n" \ + " [ label STRING ] [ scope SCOPE-ID ]\n" \ + " SCOPE-ID := [ host | link | global | NUMBER ]" + +#define ipcalc_trivial_usage \ + "[OPTION]...
    [[/]] [NETMASK]" +#define ipcalc_full_usage \ + "Calculate IP network settings from a IP address\n\n" \ + "Options:\n" \ + " -b --broadcast Display calculated broadcast address\n" \ + " -n --network Display calculated network address\n" \ + " -m --netmask Display default netmask for IP" \ + USE_FEATURE_IPCALC_FANCY( \ + "\n -p --prefix Display the prefix for IP/NETMASK\n" \ + " -h --hostname Display first resolved host name\n" \ + " -s --silent Don't ever display error messages") + +#define ipcrm_trivial_usage \ + "[-[MQS] key] [-[mqs] id]" +#define ipcrm_full_usage \ + "The upper-case options MQS are used to remove a shared memory segment by a\n" \ + "segment by a shmkey value. The lower-case options mqs are used\n" \ + "to remove a segment by shmid value.\n" \ + " -[mM] Remove the memory segment after the last detach\n" \ + " -[qQ] Remove the message queue\n" \ + " -[sS] Remove the semaphore" + +#define ipcs_trivial_usage \ + "[[-smq] -i shmid] | [[-asmq] [-tcplu]]" +#define ipcs_full_usage \ + " -i specify a specific resource id\n" \ + "Resource specification:\n" \ + " -m shared memory segments\n" \ + " -q message queues\n" \ + " -s semaphore arrays\n" \ + " -a all (default)\n" \ + "Output format:\n" \ + " -t time\n" \ + " -c creator\n" \ + " -p pid\n" \ + " -l limits\n" \ + " -u summary" + +#define iplink_trivial_usage \ + "{ set DEVICE { up | down | arp { on | off } | show [ DEVICE ] }" +#define iplink_full_usage \ + "iplink set DEVICE { up | down | arp { on | off } |\n" \ + " dynamic { on | off } |\n" \ + " mtu MTU }\n" \ + " iplink show [ DEVICE ]" + +#define iproute_trivial_usage \ + "{ list | flush | { add | del | change | append |\n" \ + " replace | monitor } ROUTE }" +#define iproute_full_usage \ + "iproute { list | flush } SELECTOR\n" \ + "iproute get ADDRESS [ from ADDRESS iif STRING ]\n" \ + " [ oif STRING ] [ tos TOS ]\n" \ + " iproute { add | del | change | append | replace | monitor } ROUTE\n" \ + " SELECTOR := [ root PREFIX ] [ match PREFIX ] [ proto RTPROTO ]\n" \ + " ROUTE := [ TYPE ] PREFIX [ tos TOS ] [ proto RTPROTO ]" + +#define iprule_trivial_usage \ + "{[ list | add | del ] RULE}" +#define iprule_full_usage \ + "iprule [ list | add | del ] SELECTOR ACTION\n" \ + " SELECTOR := [ from PREFIX ] [ to PREFIX ] [ tos TOS ] [ fwmark FWMARK ]\n" \ + " [ dev STRING ] [ pref NUMBER ]\n" \ + " ACTION := [ table TABLE_ID ] [ nat ADDRESS ]\n" \ + " [ prohibit | reject | unreachable ]\n" \ + " [ realms [SRCREALM/]DSTREALM ]\n" \ + " TABLE_ID := [ local | main | default | NUMBER ]" + +#define iptunnel_trivial_usage \ + "{ add | change | del | show } [ NAME ]\n" \ + " [ mode { ipip | gre | sit } ]\n" \ + " [ remote ADDR ] [ local ADDR ] [ ttl TTL ]" +#define iptunnel_full_usage \ + "iptunnel { add | change | del | show } [ NAME ]\n" \ + " [ mode { ipip | gre | sit } ] [ remote ADDR ] [ local ADDR ]\n" \ + " [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n" \ + " [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ]" + +#define kill_trivial_usage \ + "[-l] [-signal] process-id [process-id ...]" +#define kill_full_usage \ + "Send a signal (default is TERM) to the specified process(es).\n\n" \ + "Options:\n" \ + " -l List all signal names and numbers" +#define kill_example_usage \ + "$ ps | grep apache\n" \ + "252 root root S [apache]\n" \ + "263 www-data www-data S [apache]\n" \ + "264 www-data www-data S [apache]\n" \ + "265 www-data www-data S [apache]\n" \ + "266 www-data www-data S [apache]\n" \ + "267 www-data www-data S [apache]\n" \ + "$ kill 252\n" + +#define killall_trivial_usage \ + "[-l] [-q] [-signal] process-name [process-name ...]" +#define killall_full_usage \ + "Send a signal (default is TERM) to the specified process(es).\n\n" \ + "Options:\n" \ + " -l List all signal names and numbers\n" \ + " -q Do not complain if no processes were killed" +#define killall_example_usage \ + "$ killall apache\n" + +#define killall5_trivial_usage \ + "[-l] [-signal]" +#define killall5_full_usage \ + "Send a signal (default is TERM) to all processes outside current session.\n\n" \ + "Options:\n" \ + " -l List all signal names and numbers\n" \ + +#define klogd_trivial_usage \ + "[-c n] [-n]" +#define klogd_full_usage \ + "Kernel logger.\n" \ + "Options:\n" \ + " -c n Sets the default log level of console messages to n\n" \ + " -n Run as a foreground process" + +#define length_trivial_usage \ + "STRING" +#define length_full_usage \ + "Prints out the length of the specified STRING." +#define length_example_usage \ + "$ length Hello\n" \ + "5\n" + +#define less_trivial_usage \ + "[-EMNmh~?] FILE1 FILE2..." +#define less_full_usage \ + "View a file or list of files. The position within files can be\n" \ + "changed, and files can be manipulated in various ways with the\n" \ + "following options:\n\n" \ + " -E Quit once the end of a file is reached\n" \ + " -M Display a status line containing the current line numbers\n" \ + " and the percentage through the file\n" \ + " -N Prefix line numbers to each line\n" \ + " -m Display a status line containing the percentage through the\n" \ + " file\n" \ + " -~ Suppress ~s displayed when input past the end of the file is\n" \ + " reached.\n" \ + " -h, -? Display this help message" + +#define setarch_trivial_usage \ + " [args ...]" +#define setarch_full_usage \ + "Personality may be:\n" \ + " linux32 Set 32bit uname emulation\n" \ + " linux64 Set 64bit uname emulation" + +#define ln_trivial_usage \ + "[OPTION] TARGET... LINK_NAME|DIRECTORY" +#define ln_full_usage \ + "Create a link named LINK_NAME or DIRECTORY to the specified TARGET\n" \ + "\nYou may use '--' to indicate that all following arguments are non-options.\n\n" \ + "Options:\n" \ + " -s make symbolic links instead of hard links\n" \ + " -f remove existing destination files\n" \ + " -n no dereference symlinks - treat like normal file\n" \ + " -b make a backup of the target (if exists) before link operation\n" \ + " -S suffix use suffix instead of ~ when making backup files" +#define ln_example_usage \ + "$ ln -s BusyBox /tmp/ls\n" \ + "$ ls -l /tmp/ls\n" \ + "lrwxrwxrwx 1 root root 7 Apr 12 18:39 ls -> BusyBox*\n" + +#define loadfont_trivial_usage \ + "< font" +#define loadfont_full_usage \ + "Loads a console font from standard input." +#define loadfont_example_usage \ + "$ loadfont < /etc/i18n/fontname\n" + +#define loadkmap_trivial_usage \ + "< keymap" +#define loadkmap_full_usage \ + "Loads a binary keyboard translation table from standard input." +#define loadkmap_example_usage \ + "$ loadkmap < /etc/i18n/lang-keymap\n" + +#define logger_trivial_usage \ + "[OPTION]... [MESSAGE]" +#define logger_full_usage \ + "Write MESSAGE to the system log. If MESSAGE is omitted, log stdin.\n\n" \ + "Options:\n" \ + " -s Log to stderr as well as the system log\n" \ + " -t TAG Log using the specified tag (defaults to user name)\n" \ + " -p PRIORITY Enter the message with the specified priority\n" \ + " This may be numerical or a 'facility.level' pair" +#define logger_example_usage \ + "$ logger \"hello\"\n" + +#define login_trivial_usage \ + "[OPTION]... [username] [ENV=VAR ...]" +#define login_full_usage \ + "Begin a new session on the system\n\n" \ + "Options:\n" \ + " -f Do not authenticate (user already authenticated)\n" \ + " -h Name of the remote host for this login\n" \ + " -p Preserve environment" + +#define logname_trivial_usage \ + "" +#define logname_full_usage \ + "Print the name of the current user." +#define logname_example_usage \ + "$ logname\n" \ + "root\n" + +#define logread_trivial_usage \ + "[OPTION]..." +#define logread_full_usage \ + "Shows the messages from syslogd (using circular buffer).\n\n" \ + "Options:\n" \ + " -f output data as the log grows" + +#define losetup_trivial_usage \ + "[-o OFFSET] [-d] LOOPDEVICE [FILE]]" +#define losetup_full_usage \ + "(Dis)associate LOOPDEVICE with FILE, or display current associations.\n\n" \ + "Options:\n" \ + " -d Disassociate LOOPDEVICE\n" \ + " -o OFFSET Start OFFSET bytes into FILE" +#define losetup_notes_usage \ + "No arguments will display all current associations.\n" \ + "One argument (losetup /dev/loop1) will display the current association\n" \ + "(if any), or disassociate it (with -d). The display shows the offset\n" \ + "and filename of the file the loop device is currently bound to.\n\n" \ + "Two arguments (losetup /dev/loop1 file.img) create a new association,\n" \ + "with an optional offset (-o 12345). Encryption is not yet supported.\n\n" + +#define ls_trivial_usage \ + "[-1Aa" USE_FEATURE_LS_TIMESTAMPS("c") "Cd" \ + USE_FEATURE_LS_TIMESTAMPS("e") USE_FEATURE_LS_FILETYPES("F") "iln" \ + USE_FEATURE_LS_FILETYPES("p") USE_FEATURE_LS_FOLLOWLINKS("L") \ + USE_FEATURE_LS_RECURSIVE("R") USE_FEATURE_LS_SORTFILES("rS") "s" \ + USE_FEATURE_AUTOWIDTH("T") USE_FEATURE_LS_TIMESTAMPS("tu") \ + USE_FEATURE_LS_SORTFILES("v") USE_FEATURE_AUTOWIDTH("w") "x" \ + USE_FEATURE_LS_SORTFILES("X") USE_FEATURE_HUMAN_READABLE("h") "k" \ + USE_SELINUX("K") "] [filenames...]" +#define ls_full_usage \ + "List directory contents\n\n" \ + "Options:\n" \ + " -1 list files in a single column\n" \ + " -A do not list implied . and ..\n" \ + " -a do not hide entries starting with .\n" \ + " -C list entries by columns\n" \ + USE_FEATURE_LS_TIMESTAMPS(" -c with -l: show ctime\n") \ + USE_FEATURE_LS_COLOR(" --color[={always,never,auto}] to control coloring\n") \ + " -d list directory entries instead of contents\n" \ + USE_FEATURE_LS_TIMESTAMPS(" -e list both full date and full time\n") \ + USE_FEATURE_LS_FILETYPES(" -F append indicator (one of */=@|) to entries\n") \ + " -i list the i-node for each file\n" \ + " -l use a long listing format\n" \ + " -n list numeric UIDs and GIDs instead of names\n" \ + USE_FEATURE_LS_FILETYPES(" -p append indicator (one of /=@|) to entries\n") \ + USE_FEATURE_LS_FOLLOWLINKS(" -L list entries pointed to by symbolic links\n") \ + USE_FEATURE_LS_RECURSIVE(" -R list subdirectories recursively\n") \ + USE_FEATURE_LS_SORTFILES(" -r sort the listing in reverse order\n") \ + USE_FEATURE_LS_SORTFILES(" -S sort the listing by file size\n") \ + " -s list the size of each file, in blocks\n" \ + USE_FEATURE_AUTOWIDTH(" -T NUM assume Tabstop every NUM columns\n") \ + USE_FEATURE_LS_TIMESTAMPS(" -t with -l: show modification time\n") \ + USE_FEATURE_LS_TIMESTAMPS(" -u with -l: show access time\n") \ + USE_FEATURE_LS_SORTFILES(" -v sort the listing by version\n") \ + USE_FEATURE_AUTOWIDTH(" -w NUM assume the terminal is NUM columns wide\n") \ + " -x list entries by lines instead of by columns\n" \ + USE_FEATURE_LS_SORTFILES(" -X sort the listing by extension\n") \ + USE_FEATURE_HUMAN_READABLE( \ + " -h print sizes in human readable format (e.g., 1K 243M 2G )\n") \ + USE_SELINUX(" -k print security context\n") \ + USE_SELINUX(" -K print security context in long format\n") + +#define lsattr_trivial_usage \ + "[-Radlv] [files...]" +#define lsattr_full_usage \ + "list file attributes on an ext2 fs\n\n" \ + "Options:\n" \ + " -R recursively list subdirectories\n" \ + " -a do not hide entries starting with .\n" \ + " -d list directory entries instead of contents\n" \ + " -l print long flag names\n" \ + " -v list the file's version/generation number" + +#define lsmod_trivial_usage \ + "" +#define lsmod_full_usage \ + "List the currently loaded kernel modules." + +#ifdef CONFIG_FEATURE_MAKEDEVS_LEAF +#define makedevs_trivial_usage \ + "NAME TYPE MAJOR MINOR FIRST LAST [s]" +#define makedevs_full_usage \ + "Creates a range of block or character special files\n\n" \ + "TYPEs include:\n" \ + " b: Make a block (buffered) device.\n" \ + " c or u: Make a character (un-buffered) device.\n" \ + " p: Make a named pipe. MAJOR and MINOR are ignored for named pipes.\n\n" \ + "FIRST specifies the number appended to NAME to create the first device.\n" \ + "LAST specifies the number of the last item that should be created\n" \ + "If 's' is the last argument, the base device is created as well.\n\n" \ + "For example:\n" \ + " makedevs /dev/ttyS c 4 66 2 63 -> ttyS2-ttyS63\n" \ + " makedevs /dev/hda b 3 0 0 8 s -> hda,hda1-hda8" +#define makedevs_example_usage \ + "# makedevs /dev/ttyS c 4 66 2 63\n" \ + "[creates ttyS2-ttyS63]\n" \ + "# makedevs /dev/hda b 3 0 0 8 s\n" \ + "[creates hda,hda1-hda8]\n" +#endif + +#ifdef CONFIG_FEATURE_MAKEDEVS_TABLE +#define makedevs_trivial_usage \ + "[-d device_table] rootdir" +#define makedevs_full_usage \ + "Creates a range of special files as specified in a device table.\n" \ + "Device table entries take the form of:\n" \ + " \n" \ + "Where name is the file name, type can be one of:\n" \ + " f A regular file\n" \ + " d Directory\n" \ + " c Character special device file\n" \ + " b Block special device file\n" \ + " p Fifo (named pipe)\n" \ + "uid is the user id for the target file, gid is the group id for the\n" \ + "target file. The rest of the entries (major, minor, etc) apply to\n" \ + "to device special files. A '-' may be used for blank entries." +#define makedevs_example_usage \ + "For example:\n" \ + " \n" \ + "/dev d 755 0 0 - - - - -\n" \ + "/dev/console c 666 0 0 5 1 - - -\n" \ + "/dev/null c 666 0 0 1 3 0 0 -\n" \ + "/dev/zero c 666 0 0 1 5 0 0 -\n" \ + "/dev/hda b 640 0 0 3 0 0 0 -\n" \ + "/dev/hda b 640 0 0 3 1 1 1 15\n\n" \ + "Will Produce:\n" \ + "/dev\n" \ + "/dev/console\n" \ + "/dev/null\n" \ + "/dev/zero\n" \ + "/dev/hda\n" \ + "/dev/hda[0-15]\n" +#endif + +#define md5sum_trivial_usage \ + "[OPTION] [FILEs...]" \ + USE_FEATURE_MD5_SHA1_SUM_CHECK("\n or: md5sum [OPTION] -c [FILE]") +#define md5sum_full_usage \ + "Print" USE_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " MD5 checksums.\n\n" \ + "Options:\n" \ + "With no FILE, or when FILE is -, read standard input." \ + USE_FEATURE_MD5_SHA1_SUM_CHECK("\n\n" \ + " -c check MD5 sums against given list\n" \ + "\nThe following two options are useful only when verifying checksums:\n" \ + " -s don't output anything, status code shows success\n" \ + " -w warn about improperly formatted MD5 checksum lines") +#define md5sum_example_usage \ + "$ md5sum < busybox\n" \ + "6fd11e98b98a58f64ff3398d7b324003\n" \ + "$ md5sum busybox\n" \ + "6fd11e98b98a58f64ff3398d7b324003 busybox\n" \ + "$ md5sum -c -\n" \ + "6fd11e98b98a58f64ff3398d7b324003 busybox\n" \ + "busybox: OK\n" \ + "^D\n" + +#define mdev_trivial_usage \ + "[-s]" +#define mdev_full_usage \ + " -s Scan /sys and populate /dev during system boot\n\n" \ + "Called with no options (via hotplug) it uses environment variables\n" \ + "to determine which device to add/remove." +#define mdev_notes_usage "" \ +USE_FEATURE_MDEV_CONFIG( \ + "The mdev config file contains lines that look like:\n" \ + " hd[a-z][0-9]* 0:3 660\n\n" \ + "That's device name (with regex match), uid:gid, and permissions.\n\n" \ + USE_FEATURE_MDEV_EXEC( \ + "Optionally, that can be followed (on the same line) by a special character\n" \ + "and a command line to run after creating/before deleting the corresponding\n" \ + "device(s). The environment variable $MDEV indicates the active device node\n" \ + "(which is useful if it's a regex match). For example:\n\n" \ + " hdc root:cdrom 660 *ln -s $MDEV cdrom\n\n" \ + "The special characters are @ (run after creating), $ (run before deleting),\n" \ + "and * (run both after creating and before deleting). The commands run in\n" \ + "the /dev directory, and use system() which calls /bin/sh.\n\n" \ + ) \ + "Config file parsing stops on the first matching line. If no config\n"\ + "entry is matched, devices are created with default 0:0 660. (Make\n"\ + "the last line match .* to override this.)\n\n" \ +) + +#define mesg_trivial_usage \ + "[y|n]" +#define mesg_full_usage \ + "mesg controls write access to your terminal\n" \ + " y Allow write access to your terminal\n" \ + " n Disallow write access to your terminal" + +#define mkdir_trivial_usage \ + "[OPTION] DIRECTORY..." +#define mkdir_full_usage \ + "Create the DIRECTORY(ies) if they do not already exist\n\n" \ + "Options:\n" \ + " -m set permission mode (as in chmod), not rwxrwxrwx - umask\n" \ + " -p no error if existing, make parent directories as needed" +#define mkdir_example_usage \ + "$ mkdir /tmp/foo\n" \ + "$ mkdir /tmp/foo\n" \ + "/tmp/foo: File exists\n" \ + "$ mkdir /tmp/foo/bar/baz\n" \ + "/tmp/foo/bar/baz: No such file or directory\n" \ + "$ mkdir -p /tmp/foo/bar/baz\n" + +#define mke2fs_trivial_usage \ + "[-c|-l filename] [-b block-size] [-f fragment-size] [-g blocks-per-group] " \ + "[-i bytes-per-inode] [-j] [-J journal-options] [-N number-of-inodes] [-n] " \ + "[-m reserved-blocks-percentage] [-o creator-os] [-O feature[,...]] [-q] " \ + "[r fs-revision-level] [-E extended-options] [-v] [-F] [-L volume-label] " \ + "[-M last-mounted-directory] [-S] [-T filesystem-type] " \ + "device [blocks-count]" +#define mke2fs_full_usage \ + " -b size block size in bytes\n" \ + " -c check for bad blocks before creating\n" \ + " -E opts set extended options\n" \ + " -f size fragment size in bytes\n" \ + " -F force (ignore sanity checks)\n" \ + " -g num number of blocks in a block group\n" \ + " -i ratio the bytes/inode ratio\n" \ + " -j create a journal (ext3)\n" \ + " -J opts set journal options (size/device)\n" \ + " -l file read bad blocks list from file\n" \ + " -L lbl set the volume label\n" \ + " -m percent percent of fs blocks to reserve for admin\n" \ + " -M dir set last mounted directory\n" \ + " -n do not actually create anything\n" \ + " -N num number of inodes to create\n" \ + " -o os set the 'creator os' field\n" \ + " -O features dir_index/filetype/has_journal/journal_dev/sparse_super\n" \ + " -q quiet execution\n" \ + " -r rev set filesystem revision\n" \ + " -S write superblock and group descriptors only\n" \ + " -T fs-type set usage type (news/largefile/largefile4)\n" \ + " -v verbose execution" + +#define mkfifo_trivial_usage \ + "[OPTIONS] name" +#define mkfifo_full_usage \ + "Creates a named pipe (identical to 'mknod name p')\n\n" \ + "Options:\n" \ + " -m create the pipe using the specified mode (default a=rw)" + +#define mkfs_minix_trivial_usage \ + "[-c | -l filename] [-nXX] [-iXX] /dev/name [blocks]" +#define mkfs_minix_full_usage \ + "Make a MINIX filesystem.\n\n" \ + "Options:\n" \ + " -c Check the device for bad blocks\n" \ + " -n [14|30] Specify the maximum length of filenames\n" \ + " -i INODES Specify the number of inodes for the filesystem\n" \ + " -l FILENAME Read the bad blocks list from FILENAME\n" \ + " -v Make a Minix version 2 filesystem" + +#define mknod_trivial_usage \ + "[OPTIONS] NAME TYPE MAJOR MINOR" +#define mknod_full_usage \ + "Create a special file (block, character, or pipe).\n\n" \ + "Options:\n" \ + " -m create the special file using the specified mode (default a=rw)\n\n" \ + "TYPEs include:\n" \ + " b: Make a block (buffered) device\n" \ + " c or u: Make a character (un-buffered) device\n" \ + " p: Make a named pipe. MAJOR and MINOR are ignored for named pipes" +#define mknod_example_usage \ + "$ mknod /dev/fd0 b 2 0\n" \ + "$ mknod -m 644 /tmp/pipe p\n" + +#define mkswap_trivial_usage \ + "[-c] [-v0|-v1] device [block-count]" +#define mkswap_full_usage \ + "Prepare a disk partition to be used as a swap partition.\n\n" \ + "Options:\n" \ + " -c Check for read-ability\n" \ + " -v0 Make version 0 swap [max 128 Megs]\n" \ + " -v1 Make version 1 swap [big!] (default for kernels >\n" \ + " 2.1.117)\n" \ + " block-count Number of block to use (default is entire partition)" + +#define mktemp_trivial_usage \ + "[-dq] TEMPLATE" +#define mktemp_full_usage \ + "Creates a temporary file with its name based on TEMPLATE.\n" \ + "TEMPLATE is any name with six 'Xs' (i.e., /tmp/temp.XXXXXX).\n\n" \ + "Options:\n" \ + " -d Make a directory instead of a file\n" \ + " -q Fail silently if an error occurs" +#define mktemp_example_usage \ + "$ mktemp /tmp/temp.XXXXXX\n" \ + "/tmp/temp.mWiLjM\n" \ + "$ ls -la /tmp/temp.mWiLjM\n" \ + "-rw------- 1 andersen andersen 0 Apr 25 17:10 /tmp/temp.mWiLjM\n" + +#define modprobe_trivial_usage \ + "[-knqrsv] MODULE [symbol=value ...]" +#define modprobe_full_usage \ + "Options:\n" \ + " -k Make module autoclean-able\n" \ + " -n Just show what would be done\n" \ + " -q Quiet output\n" \ + " -r Remove module (stacks) or do autoclean\n" \ + " -s Report via syslog instead of stderr\n" \ + " -v Verbose output" +#define modprobe_notes_usage \ +"modprobe can (un)load a stack of modules, passing each module options (when\n" \ +"loading). modprobe uses a configuration file to determine what option(s) to\n" \ +"pass each module it loads.\n" \ +"\n" \ +"The configuration file is searched (in order) amongst:\n" \ +"\n" \ +" /etc/modprobe.conf (2.6 only)\n" \ +" /etc/modules.conf\n" \ +" /etc/conf.modules (deprecated)\n" \ +"\n" \ +"They all have the same syntax (see below). If none is present, it is\n" \ +"_not_ an error; each loaded module is then expected to load without\n" \ +"options. Once a file is found, the others are tested for.\n" \ +"\n" \ +"/etc/modules.conf entry format:\n" \ +"\n" \ +" alias \n" \ +" Makes it possible to modprobe alias_name, when there is no such module.\n" \ +" It makes sense if your mod_name is long, or you want a more representative\n" \ +" name for that module (eg. 'scsi' in place of 'aha7xxx').\n" \ +" This makes it also possible to use a different set of options (below) for\n" \ +" the module and the alias.\n" \ +" A module can be aliased more than once.\n" \ +"\n" \ +" options \n" \ +" When loading module mod_name (or the module aliased by alias_name), pass\n" \ +" the \"symbol=value\" pairs as option to that module.\n" \ +"\n" \ +"Sample /etc/modules.conf file:\n" \ +"\n" \ +" options tulip irq=3\n" \ +" alias tulip tulip2\n" \ +" options tulip2 irq=4 io=0x308\n" \ +"\n" \ +"Other functionality offered by 'classic' modprobe is not available in\n" \ +"this implementation.\n" \ +"\n" \ +"If module options are present both in the config file, and on the command line,\n" \ +"then the options from the command line will be passed to the module _after_\n" \ +"the options from the config file. That way, you can have defaults in the config\n" \ +"file, and override them for a specific usage from the command line.\n" +#define modprobe_example_usage \ + "(with the above /etc/modules.conf):\n\n" \ + "$ modprobe tulip\n" \ + " will load the module 'tulip' with default option 'irq=3'\n\n" \ + "$ modprobe tulip irq=5\n" \ + " will load the module 'tulip' with option 'irq=5', thus overriding the default\n\n" \ + "$ modprobe tulip2\n" \ + " will load the module 'tulip' with default options 'irq=4 io=0x308',\n" \ + " which are the default for alias 'tulip2'\n\n" \ + "$ modprobe tulip2 irq=8\n" \ + " will load the module 'tulip' with default options 'irq=4 io=0x308 irq=8',\n" \ + " which are the default for alias 'tulip2' overridden by the option 'irq=8'\n\n" \ + " from the command line\n\n" \ + "$ modprobe tulip2 irq=2 io=0x210\n" \ + " will load the module 'tulip' with default options 'irq=4 io=0x308 irq=4 io=0x210',\n" \ + " which are the default for alias 'tulip2' overridden by the options 'irq=2 io=0x210'\n\n" \ + " from the command line\n" + +#define more_trivial_usage \ + "[FILE ...]" +#define more_full_usage \ + "More is a filter for viewing FILE one screenful at a time." +#define more_example_usage \ + "$ dmesg | more\n" + +#define mount_trivial_usage \ + "[flags] DEVICE NODE [-o options,more-options]" +#define mount_full_usage \ + "Mount a filesystem. Filesystem autodetection requires /proc be mounted.\n\n" \ + "Flags:\n" \ + " -a: Mount all filesystems in fstab\n" \ + USE_FEATURE_MTAB_SUPPORT( \ + " -f: \"Fake\" Add entry to mount table but don't mount it\n" \ + " -n: Don't write a mount table entry\n" \ + ) \ + " -o option: One of many filesystem options, listed below\n" \ + " -r: Mount the filesystem read-only\n" \ + " -t fs-type: Specify the filesystem type\n" \ + " -w: Mount for reading and writing (default)\n" \ + "\n" \ + "Options for use with the \"-o\" flag:\n" \ + USE_FEATURE_MOUNT_LOOP( \ + " loop: Ignored (loop devices are autodetected)\n" \ + ) \ + USE_FEATURE_MOUNT_FLAGS( \ + " [a]sync: Writes are asynchronous / synchronous\n" \ + " [no]atime: Disable / enable updates to inode access times\n" \ + " [no]diratime: Disable / enable atime updates to directories\n" \ + " [no]dev: Allow use of special device files / disallow them\n" \ + " [no]exec: Allow use of executable files / disallow them\n" \ + " [no]suid: Allow set-user-id-root programs / disallow them\n" \ + " [r]shared: Convert [recursively] to a shared subtree.\n" \ + " [r]slave: Convert [recursively] to a slave subtree.\n" \ + " [r]private: Convert [recursively] to a private subtree\n" \ + " [un]bindable: Make mount point [un]able to be bind mounted.\n" \ + " bind: Bind a directory to an additional location\n" \ + " move: Relocate an existing mount point.\n" \ + ) \ + " remount: Re-mount a mounted filesystem, changing its flags\n" \ + " ro/rw: Mount for read-only / read-write\n" \ + "\nThere are EVEN MORE flags that are specific to each filesystem\n" \ + "You'll have to see the written documentation for those filesystems" +#define mount_example_usage \ + "$ mount\n" \ + "/dev/hda3 on / type minix (rw)\n" \ + "proc on /proc type proc (rw)\n" \ + "devpts on /dev/pts type devpts (rw)\n" \ + "$ mount /dev/fd0 /mnt -t msdos -o ro\n" \ + "$ mount /tmp/diskimage /opt -t ext2 -o loop\n" \ + "$ mount cd_image.iso mydir\n" +#define mount_notes_usage \ + "Returns 0 for success, number of failed mounts for -a, or errno for one mount." + +#define mountpoint_trivial_usage \ + "[-q] <[-d] DIR | -x DEVICE>" +#define mountpoint_full_usage \ + "mountpoint checks if the directory is a mountpoint\n\n" \ + "Options:\n" \ + " -q Be more quiet\n" \ + " -d Print major/minor device number of the filesystem\n" \ + " -x Print major/minor device number of the blockdevice" +#define mountpoint_example_usage \ + "$ mountpoint /proc\n" \ + "/proc is not a mountpoint\n" \ + "$ mountpoint /sys\n" \ + "/sys is a mountpoint\n" + +#define mt_trivial_usage \ + "[-f device] opcode value" +#define mt_full_usage \ + "Control magnetic tape drive operation\n" \ + "\nAvailable Opcodes:\n\n" \ + "bsf bsfm bsr bss datacompression drvbuffer eof eom erase\n" \ + "fsf fsfm fsr fss load lock mkpart nop offline ras1 ras2\n" \ + "ras3 reset retension rewind rewoffline seek setblk setdensity\n" \ + "setpart tell unload unlock weof wset" + +#define mv_trivial_usage \ + "[OPTION]... SOURCE DEST\n" \ + "or: mv [OPTION]... SOURCE... DIRECTORY" +#define mv_full_usage \ + "Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\n" \ + "Options:\n" \ + " -f don't prompt before overwriting\n" \ + " -i interactive, prompt before overwrite" +#define mv_example_usage \ + "$ mv /tmp/foo /bin/bar\n" + +#define nameif_trivial_usage \ + "[-s] [-c FILE] [{IFNAME MACADDR}]" +#define nameif_full_usage \ + "Nameif renames network interface while it in the down state.\n\n" \ + "Options:\n" \ + " -c FILE Use configuration file (default is /etc/mactab)\n" \ + " -s Use syslog (LOCAL0 facility)\n" \ + " IFNAME MACADDR new_interface_name interface_mac_address" +#define nameif_example_usage \ + "$ nameif -s dmz0 00:A0:C9:8C:F6:3F\n" \ + " or\n" \ + "$ nameif -c /etc/my_mactab_file\n" \ + +#if ENABLE_NC_SERVER || ENABLE_NC_EXTRA +#define NC_OPTIONS_STR "\n\nOptions:" +#else +#define NC_OPTIONS_STR +#endif + +#define nc_trivial_usage \ + USE_NC_EXTRA("[-iN] [-wN] ")USE_NC_SERVER("[-l] [-p PORT] ") \ + "["USE_NC_EXTRA("-f FILENAME|")"IPADDR PORTNUM]"USE_NC_EXTRA(" [-e COMMAND]") +#define nc_full_usage \ + "Netcat opens a pipe to IP:port" USE_NC_EXTRA(" or file") \ + NC_OPTIONS_STR \ + USE_NC_EXTRA( \ + "\n -e exec rest of command line after connect\n" \ + " -i SECS delay interval for lines sent\n" \ + " -w SECS timeout for connect\n" \ + " -f file use file (ala /dev/ttyS0) instead of network" \ + ) \ + USE_NC_SERVER( \ + "\n -l listen mode, for inbound connects\n" \ + USE_NC_EXTRA(" (use -l twice with -e for persistent server)\n") \ + " -p PORT local port number" \ + ) + +#define nc_notes_usage "" \ + USE_NC_EXTRA( \ + "To use netcat as a terminal emulator on a serial port:\n\n" \ + "$ stty 115200 -F /dev/ttyS0\n" \ + "$ stty raw -echo -ctlecho && nc -f /dev/ttyS0\n" \ + ) "" + +#define nc_example_usage \ + "$ nc foobar.somedomain.com 25\n" \ + "220 foobar ESMTP Exim 3.12 #1 Sat, 15 Apr 2000 00:03:02 -0600\n" \ + "help\n" \ + "214-Commands supported:\n" \ + "214- HELO EHLO MAIL RCPT DATA AUTH\n" \ + "214 NOOP QUIT RSET HELP\n" \ + "quit\n" \ + "221 foobar closing connection\n" + +#define netstat_trivial_usage \ + "[-laenrtuwx]" +#define netstat_full_usage \ + "Netstat displays Linux networking information.\n\n" \ + "Options:\n" \ + " -l display listening server sockets\n" \ + " -a display all sockets (default: connected)\n" \ + " -e display other/more information\n" \ + " -n don't resolve names\n" \ + " -r display routing table\n" \ + " -t tcp sockets\n" \ + " -u udp sockets\n" \ + " -w raw sockets\n" \ + " -x unix sockets" + +#define nice_trivial_usage \ + "[-n ADJUST] [COMMAND [ARG] ...]" +#define nice_full_usage \ + "Nice runs a program with modified scheduling priority.\n\n" \ + "Options:\n" \ + " -n ADJUST Adjust the scheduling priority by ADJUST" + +#define nmeter_trivial_usage \ + "format_string" +#define nmeter_full_usage \ + "Nmeter monitors your system in real time.\n\n" \ + "Format specifiers:\n" \ + "%Nc or %[cN] monitor CPU. N - bar size, default 10\n" \ + " (displays: S:system U:user N:niced D:iowait I:irq i:softirq)\n" \ + "%[niface] monitor network interface 'iface'\n" \ + "%m monitor allocated memory\n" \ + "%[mf] monitor free memory\n" \ + "%[mt] monitor total memory\n" \ + "%s monitor allocated swap\n" \ + "%f monitor number of used file descriptors\n" \ + "%Ni monitor total/specific IRQ rate\n" \ + "%x monitor context switch rate\n" \ + "%p monitor forks\n" \ + "%[pn] monitor # of processes\n" \ + "%b monitor block io\n" \ + "%Nt show time (with N decimal points)\n" \ + "%Nd milliseconds between updates (default=1000)\n" \ + "%r print instead of at EOL" +#define nmeter_example_usage \ + "nmeter '%250d%t %20c int %i bio %b mem %m forks%p'" + +#define nohup_trivial_usage \ + "COMMAND [ARGS]" +#define nohup_full_usage \ + "run a command immune to hangups, with output to a non-tty" +#define nohup_example_usage \ + "$ nohup make &" + +#define nslookup_trivial_usage \ + "[HOST] [SERVER]" +#define nslookup_full_usage \ + "Queries the nameserver for the IP address of the given HOST\n" \ + "optionally using a specified DNS server" +#define nslookup_example_usage \ + "$ nslookup localhost\n" \ + "Server: default\n" \ + "Address: default\n" \ + "\n" \ + "Name: debian\n" \ + "Address: 127.0.0.1\n" + +#define od_trivial_usage \ + "[-aBbcDdeFfHhIiLlOovXx] [FILE]" +#define od_full_usage \ + "Write an unambiguous representation, octal bytes by default, of FILE\n" \ + "to standard output. With no FILE, or when FILE is -, read standard input." + +#define openvt_trivial_usage \ + " [ARGS...]" +#define openvt_full_usage \ + "Start a command on a new virtual terminal" +#define openvt_example_usage \ + "openvt 2 /bin/ash\n" + +#define passwd_trivial_usage \ + "[OPTION] [name]" +#define passwd_full_usage \ + "Change a user password. If no name is specified,\n" \ + "changes the password for the current user.\n" \ + "Options:\n" \ + " -a Define which algorithm shall be used for the password\n" \ + " (Choices: des, md5, sha1)\n" \ + " -d Delete the password for the specified user account\n" \ + " -l Locks (disables) the specified user account\n" \ + " -u Unlocks (re-enables) the specified user account" + +#define patch_trivial_usage \ + "[-p] [-i ]" +#define patch_full_usage \ + " -p Strip leading components from file names\n" \ + " -i Read instead of stdin" +#define patch_example_usage \ + "$ patch -p1 < example.diff\n" \ + "$ patch -p0 -i example.diff" + +#if (ENABLE_FEATURE_PIDOF_SINGLE || ENABLE_FEATURE_PIDOF_OMIT) +#define USAGE_PIDOF "Options:" +#else +#define USAGE_PIDOF "\n This version of pidof accepts no options." +#endif + +#define pidof_trivial_usage \ + "process-name [OPTION] [process-name ...]" + +#define pidof_full_usage \ + "Lists the PIDs of all processes with names that match the\n" \ + "names on the command line.\n" \ + USAGE_PIDOF \ + USE_FEATURE_PIDOF_SINGLE("\n -s display only a single PID") \ + USE_FEATURE_PIDOF_OMIT("\n -o omit given pid.") \ + USE_FEATURE_PIDOF_OMIT("\n Use %PPID to omit the parent pid of pidof itself") +#define pidof_example_usage \ + "$ pidof init\n" \ + "1\n" \ + USE_FEATURE_PIDOF_OMIT("$ pidof /bin/sh\n20351 5973 5950\n") \ + USE_FEATURE_PIDOF_OMIT("$ pidof /bin/sh -o %PPID\n20351 5950") + +#ifndef CONFIG_FEATURE_FANCY_PING +#define ping_trivial_usage "host" +#define ping_full_usage "Send ICMP ECHO_REQUEST packets to network hosts" +#else +#define ping_trivial_usage \ + "[OPTION]... host" +#define ping_full_usage \ + "Send ICMP ECHO_REQUEST packets to network hosts.\n\n" \ + "Options:\n" \ + " -c COUNT Send only COUNT pings\n" \ + " -s SIZE Send SIZE data bytes in packets (default=56)\n" \ + " -I IPADDR Use IPADDR as source address\n" \ + " -q Quiet mode, only displays output at start\n" \ + " and when finished" +#endif +#define ping_example_usage \ + "$ ping localhost\n" \ + "PING slag (127.0.0.1): 56 data bytes\n" \ + "64 bytes from 127.0.0.1: icmp_seq=0 ttl=255 time=20.1 ms\n" \ + "\n" \ + "--- debian ping statistics ---\n" \ + "1 packets transmitted, 1 packets received, 0% packet loss\n" \ + "round-trip min/avg/max = 20.1/20.1/20.1 ms\n" + +#ifndef CONFIG_FEATURE_FANCY_PING6 +#define ping6_trivial_usage "host" +#define ping6_full_usage "Send ICMP ECHO_REQUEST packets to network hosts" +#else +#define ping6_trivial_usage \ + "[OPTION]... host" +#define ping6_full_usage \ + "Send ICMP ECHO_REQUEST packets to network hosts.\n\n" \ + "Options:\n" \ + " -c COUNT Send only COUNT pings\n" \ + " -s SIZE Send SIZE data bytes in packets (default=56)\n" \ + " -q Quiet mode, only displays output at start\n" \ + " and when finished" +#endif +#define ping6_example_usage \ + "$ ping6 ip6-localhost\n" \ + "PING ip6-localhost (::1): 56 data bytes\n" \ + "64 bytes from ::1: icmp6_seq=0 ttl=64 time=20.1 ms\n" \ + "\n" \ + "--- ip6-localhost ping statistics ---\n" \ + "1 packets transmitted, 1 packets received, 0% packet loss\n" \ + "round-trip min/avg/max = 20.1/20.1/20.1 ms\n" + +#define pivot_root_trivial_usage \ + "NEW_ROOT PUT_OLD" +#define pivot_root_full_usage \ + "Move the current root file system to PUT_OLD and make NEW_ROOT\n" \ + "the new root file system." + +#define poweroff_trivial_usage \ + "[-d] [-n] [-f]" +#define poweroff_full_usage \ + "Halt and shut off power.\n" \ + "Options:\n" \ + " -d delay interval for halting\n" \ + " -n no call to sync()\n" \ + " -f force power off (don't go through init)" + +#define printenv_trivial_usage \ + "[VARIABLES...]" +#define printenv_full_usage \ + "print all or part of environment\n\n" \ + "If no environment VARIABLE specified, print them all." + +#define printf_trivial_usage \ + "FORMAT [ARGUMENT...]" +#define printf_full_usage \ + "Formats and prints ARGUMENT(s) according to FORMAT,\n" \ + "Where FORMAT controls the output exactly as in C printf." +#define printf_example_usage \ + "$ printf \"Val=%d\\n\" 5\n" \ + "Val=5\n" + + +#if ENABLE_DESKTOP + +#define ps_trivial_usage \ + "" +#define ps_full_usage \ + "Report process status\n" \ + "\nOptions:" \ + "\n -o col1,col2=header Select columns for display" \ + +#else /* !ENABLE_DESKTOP */ + +#if !defined CONFIG_SELINUX && !ENABLE_FEATURE_PS_WIDE +#define USAGE_PS "\n This version of ps accepts no options." +#else +#define USAGE_PS "\nOptions:" +#endif + +#define ps_trivial_usage \ + "" +#define ps_full_usage \ + "Report process status\n" \ + USAGE_PS \ + USE_SELINUX("\n -c show SE Linux context") \ + USE_FEATURE_PS_WIDE("\n w wide output") + +#endif /* ENABLE_DESKTOP */ + +#define ps_example_usage \ + "$ ps\n" \ + " PID Uid Gid State Command\n" \ + " 1 root root S init\n" \ + " 2 root root S [kflushd]\n" \ + " 3 root root S [kupdate]\n" \ + " 4 root root S [kpiod]\n" \ + " 5 root root S [kswapd]\n" \ + " 742 andersen andersen S [bash]\n" \ + " 743 andersen andersen S -bash\n" \ + " 745 root root S [getty]\n" \ + " 2990 andersen andersen R ps\n" + + +#define pwd_trivial_usage \ + "" +#define pwd_full_usage \ + "Print the full filename of the current working directory." +#define pwd_example_usage \ + "$ pwd\n" \ + "/root\n" + +#define raidautorun_trivial_usage \ + "DEVICE" +#define raidautorun_full_usage \ + "Tells the kernel to automatically search and start RAID arrays" +#define raidautorun_example_usage \ + "$ raidautorun /dev/md0" + +#define rdate_trivial_usage \ + "[-sp] HOST" +#define rdate_full_usage \ + "Get and possibly set the system date and time from a remote HOST.\n\n" \ + "Options:\n" \ + " -s Set the system date and time (default)\n" \ + " -p Print the date and time" + +#define readahead_trivial_usage \ + "[FILE]..." +#define readahead_full_usage \ + "Preloads FILE(s) in RAM cache so that subsequent reads for those" \ + "files do not block on disk I/O." + +#define readlink_trivial_usage \ + USE_FEATURE_READLINK_FOLLOW("[-f] ") "FILE" +#define readlink_full_usage \ + "Displays the value of a symbolic link." \ + USE_FEATURE_READLINK_FOLLOW("\n\nOptions:\n" \ + " -f canonicalize by following all symlinks") + +#define readprofile_trivial_usage \ + "[OPTIONS]..." +#define readprofile_full_usage \ + "Options:\n" \ + " -m (default: /boot/System.map)\n" \ + " -p (default: /proc/profile)\n" \ + " -M set the profiling multiplier to \n" \ + " -i print only info about the sampling step\n" \ + " -v print verbose data\n" \ + " -a print all symbols, even if count is 0\n" \ + " -b print individual histogram-bin counts\n" \ + " -s print individual counters within functions\n" \ + " -r reset all the counters (root only)\n" \ + " -n disable byte order auto-detection" + +#define realpath_trivial_usage \ + "pathname ..." +#define realpath_full_usage \ + "Returns the absolute pathnames of given argument." + +#define reboot_trivial_usage \ + "[-d] [-n] [-f]" +#define reboot_full_usage \ + "Reboot the system.\n" \ + "Options:\n" \ + " -d delay interval for rebooting\n" \ + " -n no call to sync()\n" \ + " -f force reboot (don't go through init)" + +#define renice_trivial_usage \ + "{{-n INCREMENT} | PRIORITY} [[ -p | -g | -u ] ID ...]" +#define renice_full_usage \ + "Changes priority of running processes.\n\n" \ + "Options:\n" \ + " -n adjusts current nice value (smaller is faster)\n" \ + " -p process id(s) (default)\n" \ + " -g process group id(s)\n" \ + " -u process user name(s) and/or id(s)" + +#define reset_trivial_usage \ + "" +#define reset_full_usage \ + "Resets the screen." + +#define resize_trivial_usage \ + "" +#define resize_full_usage \ + "Resizes the screen." + +#define rm_trivial_usage \ + "[OPTION]... FILE..." +#define rm_full_usage \ + "Remove (unlink) the FILE(s). You may use '--' to\n" \ + "indicate that all following arguments are non-options.\n\n" \ + "Options:\n" \ + " -i always prompt before removing each destination\n" \ + " -f remove existing destinations, never prompt\n" \ + " -r or -R remove the contents of directories recursively" +#define rm_example_usage \ + "$ rm -rf /tmp/foo\n" + +#define rmdir_trivial_usage \ + "[OPTION]... DIRECTORY..." +#define rmdir_full_usage \ + "Remove the DIRECTORY(ies), if they are empty." +#define rmdir_example_usage \ + "# rmdir /tmp/foo\n" + +#define rmmod_trivial_usage \ + "[OPTION]... [MODULE]..." +#define rmmod_full_usage \ + "Unloads the specified kernel modules from the kernel.\n\n" \ + "Options:\n" \ + " -a Remove all unused modules (recursively)" +#define rmmod_example_usage \ + "$ rmmod tulip\n" + +#define route_trivial_usage \ + "[{add|del|delete}]" +#define route_full_usage \ + "Edit the kernel's routing tables.\n\n" \ + "Options:\n" \ + " -n Dont resolve names\n" \ + " -e Display other/more information\n" \ + " -A inet" USE_FEATURE_IPV6("{6}") " Select address family" + +#define rpm_trivial_usage \ + "-i -q[ildc]p package.rpm" +#define rpm_full_usage \ + "Manipulates RPM packages" \ + "\n\nOptions:" \ + "\n -i Install package" \ + "\n -q Query package" \ + "\n -p Query uninstalled package" \ + "\n -i Show information" \ + "\n -l List contents" \ + "\n -d List documents" \ + "\n -c List config files" + +#define rpm2cpio_trivial_usage \ + "package.rpm" +#define rpm2cpio_full_usage \ + "Outputs a cpio archive of the rpm file." + +#define run_parts_trivial_usage \ + "[-t] [-a ARG] [-u MASK] DIRECTORY" +#define run_parts_full_usage \ + "Run a bunch of scripts in a directory.\n\n" \ + "Options:\n" \ + " -t Prints what would be run, but does not actually run anything\n" \ + " -a ARG Pass ARG as an argument for every program invoked\n" \ + " -u MASK Set the umask to MASK before executing every program" + +#define runlevel_trivial_usage \ + "[utmp]" +#define runlevel_full_usage \ + "Find the current and previous system runlevel.\n\n" \ + "If no utmp file exists or if no runlevel record can be found,\n" \ + "runlevel prints \"unknown\"" +#define runlevel_example_usage \ + "$ runlevel /var/run/utmp\n" \ + "N 2" + +#define runsv_trivial_usage \ + "dir" +#define runsv_full_usage \ + "Start and monitor a service and optionally an appendant log service." + +#define runsvdir_trivial_usage \ + "[-P] dir" +#define runsvdir_full_usage \ + "Start a runsv process for each subdirectory." + +#define rx_trivial_usage \ + "FILE" +#define rx_full_usage \ + "Receive a file using the xmodem protocol." +#define rx_example_usage \ + "$ rx /tmp/foo\n" + +#define sed_trivial_usage \ + "[-efinr] pattern [files...]" +#define sed_full_usage \ + "Options:\n" \ + " -e script add the script to the commands to be executed\n" \ + " -f scriptfile add script-file contents to the\n" \ + " commands to be executed\n" \ + " -i edit files in-place\n" \ + " -n suppress automatic printing of pattern space\n" \ + " -r use extended regular expression syntax\n" \ + "\n" \ + "If no -e or -f is given, the first non-option argument is taken as the sed\n" \ + "script to interpret. All remaining arguments are names of input files; if no\n" \ + "input files are specified, then the standard input is read. Source files\n" \ + "will not be modified unless -i option is given." + +#define sed_example_usage \ + "$ echo \"foo\" | sed -e 's/f[a-zA-Z]o/bar/g'\n" \ + "bar\n" + +#define seq_trivial_usage \ + "[first [increment]] last" +#define seq_full_usage \ + "Print numbers from FIRST to LAST, in steps of INCREMENT.\n" \ + "FIRST, INCREMENT default to 1\n" \ + "Arguments:\n" \ + " LAST\n" \ + " FIRST LAST\n" \ + " FIRST INCREMENT LAST" + +#define setconsole_trivial_usage \ + "[-r|--reset] [DEVICE]" +#define setconsole_full_usage \ + "Redirects system console output to DEVICE (default: /dev/tty).\n\n" \ + "Options:\n" \ + " -r Reset output to /dev/console." + +#define setkeycodes_trivial_usage \ + "SCANCODE KEYCODE ..." +#define setkeycodes_full_usage \ + "Set entries into the kernel's scancode-to-keycode map,\n" \ + "allowing unusual keyboards to generate usable keycodes.\n\n" \ + "SCANCODE may be either xx or e0xx (hexadecimal),\n" \ + "and KEYCODE is given in decimal" +#define setkeycodes_example_usage \ + "$ setkeycodes e030 127\n" + +#define setlogcons_trivial_usage \ + "N" +#define setlogcons_full_usage \ + "Redirects the kernel output to console N (0 for current)." + +#define setsid_trivial_usage \ + "program [arg ...]" +#define setsid_full_usage \ + "Runs any program in a new session by calling setsid() before\n" \ + "exec'ing the rest of its arguments. See setsid(2) for details." + +#define lash_trivial_usage \ + "[FILE]...\n" \ + "or: sh -c command [args]..." +#define lash_full_usage \ + "The BusyBox LAme SHell (command interpreter)" +#define lash_notes_usage \ + "This command does not yet have proper documentation.\n\n" \ + "Use lash just as you would use any other shell. It properly handles pipes,\n" \ + "redirects, job control, can be used as the shell for scripts, and has a\n" \ + "sufficient set of builtins to do what is needed. It does not (yet) support\n" \ + "Bourne Shell syntax. If you need things like \"if-then-else\", \"while\", and such\n" \ + "use ash or bash. If you just need a very simple and extremely small shell,\n" \ + "this will do the job." + +#define last_trivial_usage \ + "" +#define last_full_usage \ + "Shows listing of the last users that logged into the system" + +#define sha1sum_trivial_usage \ + "[OPTION] [FILEs...]" \ + USE_FEATURE_MD5_SHA1_SUM_CHECK("\n or: sha1sum [OPTION] -c [FILE]") +#define sha1sum_full_usage \ + "Print" USE_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " SHA1 checksums.\n\n" \ + "Options:\n" \ + "With no FILE, or when FILE is -, read standard input." \ + USE_FEATURE_MD5_SHA1_SUM_CHECK("\n\n" \ + " -c check SHA1 sums against given list\n" \ + "\nThe following two options are useful only when verifying checksums:\n" \ + " -s don't output anything, status code shows success\n" \ + " -w warn about improperly formatted SHA1 checksum lines") + +#define sleep_trivial_usage \ + USE_FEATURE_FANCY_SLEEP("[") "N" USE_FEATURE_FANCY_SLEEP("]...") +#define sleep_full_usage \ + SKIP_FEATURE_FANCY_SLEEP("Pause for N seconds.") \ + USE_FEATURE_FANCY_SLEEP( \ + "Pause for a time equal to the total of the args given, where each arg can\n" \ + " have an optional suffix of (s)econds, (m)inutes, (h)ours, or (d)ays.") +#define sleep_example_usage \ + "$ sleep 2\n" \ + "[2 second delay results]\n" \ + USE_FEATURE_FANCY_SLEEP("$ sleep 1d 3h 22m 8s\n" \ + "[98528 second delay results]\n") + +#define sort_trivial_usage \ + "[-nru" USE_FEATURE_SORT_BIG("gMcszbdfimSTokt] [-o outfile] [-k start[.offset][opts][,end[.offset][opts]] [-t char") "] [FILE]..." +#define sort_full_usage \ + "Sorts lines of text in the specified files\n\n" \ + "Options:\n" \ + USE_FEATURE_SORT_BIG( \ + " -b ignore leading blanks\n" \ + " -c check whether input is sorted\n" \ + " -d dictionary order (blank or alphanumeric only)\n" \ + " -f ignore case\n" \ + " -g general numerical sort\n" \ + " -i ignore unprintable characters\n" \ + " -k specify sort key\n" \ + " -M sort month\n" \ + ) \ + " -n sort numbers\n" \ + USE_FEATURE_SORT_BIG( \ + " -o output to file\n" \ + " -k sort by key\n" \ + " -t use key separator other than whitespace\n" \ + ) \ + " -r reverse sort order\n" \ + USE_FEATURE_SORT_BIG(" -s stable (don't sort ties alphabetically)\n") \ + " -u suppress duplicate lines" \ + USE_FEATURE_SORT_BIG("\n -z input terminated by nulls, not newlines\n") \ + USE_FEATURE_SORT_BIG(" -mST ignored for GNU compatibility") \ + "" +#define sort_example_usage \ + "$ echo -e \"e\\nf\\nb\\nd\\nc\\na\" | sort\n" \ + "a\n" \ + "b\n" \ + "c\n" \ + "d\n" \ + "e\n" \ + "f\n" \ + USE_FEATURE_SORT_BIG( \ + "$ echo -e \"c 3\\nb 2\\nd 2\" | $SORT -k 2,2n -k 1,1r\n" \ + "d 2\n" \ + "b 2\n" \ + "c 3\n" \ + ) \ + "" + +#define start_stop_daemon_trivial_usage \ + "[OPTIONS] [--start|--stop] ... [-- arguments...]" +#define start_stop_daemon_full_usage \ + "Program to start and stop services." \ + "\n\nOptions:" \ + "\n -S|--start start" \ + "\n -K|--stop stop" \ + "\n -a|--startas starts process specified by pathname" \ + "\n -b|--background force process into background" \ + "\n -u|--user | stop this user's processes" \ + "\n -x|--exec program to either start or check" \ + "\n -m|--make-pidfile create the -p file and enter pid in it" \ + "\n -n|--name stop processes with this name" \ + "\n -p|--pidfile save or load pid using a pid-file" \ + "\n -q|--quiet be quiet" \ +USE_FEATURE_START_STOP_DAEMON_FANCY( \ + "\n -o|--oknodo exit status 0 if nothing done" \ + "\n -v|--verbose be verbose" \ + "\n -N|--nicelevel add N to process's nice level" \ +) \ + "\n -s|--signal signal to send (default TERM)" \ + "\n -U|--chuid | start process with this name" + +#define stat_trivial_usage \ + "[OPTION] FILE..." +#define stat_full_usage \ + "display file (default) or filesystem status.\n\n" \ + "Options:\n" \ + USE_FEATURE_STAT_FORMAT(" -c fmt use the specified format\n") \ + " -f display filesystem status\n" \ + " -L,-l dereference links\n" \ + " -t display info in terse form\n" \ + USE_FEATURE_STAT_FORMAT( \ + "\nValid format sequences for files:\n" \ + " %a Access rights in octal\n" \ + " %A Access rights in human readable form\n" \ + " %b Number of blocks allocated (see %B)\n" \ + " %B The size in bytes of each block reported by %b\n" \ + " %d Device number in decimal\n" \ + " %D Device number in hex\n" \ + " %f Raw mode in hex\n" \ + " %F File type\n" \ + " %g Group ID of owner\n" \ + " %G Group name of owner\n" \ + " %h Number of hard links\n" \ + " %i Inode number\n" \ + " %n File name\n" \ + " %N Quoted file name with dereference if symbolic link\n" \ + " %o I/O block size\n" \ + " %s Total size, in bytes\n" \ + " %t Major device type in hex\n" \ + " %T Minor device type in hex\n" \ + " %u User ID of owner\n" \ + " %U User name of owner\n" \ + " %x Time of last access\n" \ + " %X Time of last access as seconds since Epoch\n" \ + " %y Time of last modification\n" \ + " %Y Time of last modification as seconds since Epoch\n" \ + " %z Time of last change\n" \ + " %Z Time of last change as seconds since Epoch\n" \ + "\nValid format sequences for file systems:\n" \ + " %a Free blocks available to non-superuser\n" \ + " %b Total data blocks in file system\n" \ + " %c Total file nodes in file system\n" \ + " %d Free file nodes in file system\n" \ + " %f Free blocks in file system\n" \ + " %i File System ID in hex\n" \ + " %l Maximum length of filenames\n" \ + " %n File name\n" \ + " %s Block size (for faster transfers)\n" \ + " %S Fundamental block size (for block counts)\n" \ + " %t Type in hex\n" \ + " %T Type in human readable form\n" \ + ) + +#define strings_trivial_usage \ + "[-afo] [-n length] [file ... ]" +#define strings_full_usage \ + "Display printable strings in a binary file." \ + "\n\nOptions:" \ + "\n -a Scan the whole files (this is the default)." \ + "\n -f Precede each string with the name of the file where it was found." \ + "\n -n N Specifies that at least N characters forms a sequence (default 4)" \ + "\n -o Each string is preceded by its decimal offset in the file" + +#define stty_trivial_usage \ + "[-a|g] [-F DEVICE] [SETTING]..." +#define stty_full_usage \ + "Without arguments, prints baud rate, line discipline," \ + "\nand deviations from stty sane." \ + "\n\nOptions:" \ + "\n -F DEVICE open device instead of stdin" \ + "\n -a print all current settings in human-readable form" \ + "\n -g print in stty-readable form" \ + "\n [SETTING] see manpage" + +#define su_trivial_usage \ + "[OPTION]... [-] [username]" +#define su_full_usage \ + "Change user id or become root.\n" \ + "Options:\n" \ + " -p, -m Preserve environment" \ + "\n -c Command to pass to 'sh -c'" \ + "\n -s Shell to use instead of default shell" + +#define sulogin_trivial_usage \ + "[OPTION]... [tty-device]" +#define sulogin_full_usage \ + "Single user login\n" \ + "Options:\n" \ + " -t Timeout" + +#define sum_trivial_usage \ + "[rs] [files...]" +#define sum_full_usage \ + "checksum and count the blocks in a file\n\n" \ + "Options:\n" \ + " -r use BSD sum algorithm (1K blocks)\n" \ + " -s use System V sum algorithm (512byte blocks)" + +#define sv_trivial_usage \ + "[-v] [-w sec] command service..." +#define sv_full_usage \ + "Report the current status and control the state of services " \ + "monitored by the runsv supervisor." + +#define svlogd_trivial_usage \ + "[-ttv] [-r c] [-R abc] [-l len] [-b buflen] dir..." +#define svlogd_full_usage \ + "Continuously read log data from standard input, optionally " \ + "filter log messages, and write the data to one or more automatically " \ + "rotated logs." + +#define swapoff_trivial_usage \ + "[-a] [DEVICE]" +#define swapoff_full_usage \ + "Stop swapping virtual memory pages on DEVICE.\n\n" \ + "Options:\n" \ + " -a Stop swapping on all swap devices" + +#define swapon_trivial_usage \ + "[-a] [DEVICE]" +#define swapon_full_usage \ + "Start swapping virtual memory pages on DEVICE.\n\n" \ + "Options:\n" \ + " -a Start swapping on all swap devices" + +#define switch_root_trivial_usage \ + "[-c /dev/console] NEW_ROOT NEW_INIT [ARGUMENTS_TO_INIT]" +#define switch_root_full_usage \ + "Use from PID 1 under initramfs to free initramfs, chroot to NEW_ROOT,\n" \ + "and exec NEW_INIT.\n\n" \ + "Options:\n" \ + " -c Redirect console to device on new root" + +#define sync_trivial_usage \ + "" +#define sync_full_usage \ + "Write all buffered filesystem blocks to disk." + +#define sysctl_trivial_usage \ + "[OPTIONS]... [VALUE]..." +#define sysctl_full_usage \ + "configure kernel parameters at runtime\n\n" \ + "Options:\n" \ + " -n Use this option to disable printing of the key name when printing values\n" \ + " -w Use this option when you want to change a sysctl setting\n" \ + " -p Load in sysctl settings from the file specified or /etc/sysctl.conf if none given\n" \ + " -a Display all values currently available\n" \ + " -A Display all values currently available in table form" +#define sysctl_example_usage \ + "sysctl [-n] variable ...\n" \ + "sysctl [-n] -w variable=value ...\n" \ + "sysctl [-n] -a\n" \ + "sysctl [-n] -p (default /etc/sysctl.conf)\n" \ + "sysctl [-n] -A\n" + +#define syslogd_trivial_usage \ + "[OPTION]..." +#define syslogd_full_usage \ + "Linux system and kernel logging utility.\n" \ + "Note that this version of syslogd ignores /etc/syslog.conf.\n\n" \ + "Options:\n" \ + " -m MIN Minutes between MARK lines (default=20, 0=off)\n" \ + " -n Run as a foreground process\n" \ + " -O FILE Use an alternate log file (default=/var/log/messages)\n" \ + " -l n Sets the local log level of messages to n\n" \ + " -S Make logging output smaller" \ + USE_FEATURE_ROTATE_LOGFILE( \ + "\n -s SIZE Max size (KB) before rotate (default=200KB, 0=off)\n" \ + " -b NUM Number of rotated logs to keep (default=1, max=99, 0=purge)") \ + USE_FEATURE_REMOTE_LOG( \ + "\n -R HOST[:PORT] Log to IP or hostname on PORT (default PORT=514/UDP)\n" \ + " -L Log locally and via network logging (default is network only)") \ + USE_FEATURE_IPC_SYSLOG( \ + "\n -C [size(KiB)] Log to a circular buffer (read the buffer using logread)") +#define syslogd_example_usage \ + "$ syslogd -R masterlog:514\n" \ + "$ syslogd -R 192.168.1.1:601\n" + +#define tail_trivial_usage \ + "[OPTION]... [FILE]..." +#define tail_full_usage \ + "Print last 10 lines of each FILE to standard output.\n" \ + "With more than one FILE, precede each with a header giving the\n" \ + "file name. With no FILE, or when FILE is -, read standard input.\n\n" \ + "Options:\n" \ + USE_FEATURE_FANCY_TAIL(" -c N[kbm] output the last N bytes\n") \ + " -n N[kbm] print last N lines instead of last 10\n" \ + " -f output data as the file grows" \ + USE_FEATURE_FANCY_TAIL( "\n -q never output headers giving file names\n" \ + " -s SEC wait SEC seconds between reads with -f\n" \ + " -v always output headers giving file names\n\n" \ + "If the first character of N (bytes or lines) is a '+', output begins with\n" \ + "the Nth item from the start of each file, otherwise, print the last N items\n" \ + "in the file. N bytes may be suffixed by k (x1024), b (x512), or m (1024^2)." ) +#define tail_example_usage \ + "$ tail -n 1 /etc/resolv.conf\n" \ + "nameserver 10.0.0.1\n" + +#define tar_trivial_usage \ + "-[" USE_FEATURE_TAR_CREATE("c") USE_FEATURE_TAR_GZIP("z") \ + USE_FEATURE_TAR_BZIP2("j") USE_FEATURE_TAR_LZMA("a") \ + USE_FEATURE_TAR_COMPRESS("Z") "xtvO] " \ + USE_FEATURE_TAR_FROM("[-X FILE] ") \ + "[-f TARFILE] [-C DIR] [FILE(s)] ..." +#define tar_full_usage \ + "Create, extract, or list files from a tar file.\n\n" \ + "Options:\n" \ + USE_FEATURE_TAR_CREATE(" c create\n") \ + " x extract\n" \ + " t list\n" \ + "\nArchive format selection:\n" \ + USE_FEATURE_TAR_GZIP(" z Filter the archive through gzip\n") \ + USE_FEATURE_TAR_BZIP2(" j Filter the archive through bzip2\n") \ + USE_FEATURE_TAR_LZMA(" a Filter the archive through lzma\n") \ + USE_FEATURE_TAR_COMPRESS(" Z Filter the archive through compress\n") \ + "\nFile selection:\n" \ + " f name of TARFILE or \"-\" for stdin\n" \ + " O extract to stdout\n" \ + USE_FEATURE_TAR_FROM( \ + " exclude file to exclude\n" \ + " X file with names to exclude\n" \ + ) \ + " C change to directory DIR before operation\n" \ + " v verbosely list files processed" +#define tar_example_usage \ + "$ zcat /tmp/tarball.tar.gz | tar -xf -\n" \ + "$ tar -cf /tmp/tarball.tar /usr/local\n" + +#define taskset_trivial_usage \ + "[OPTIONS] [mask] [pid | command [arg]...]" +#define taskset_full_usage \ + "Set or get CPU affinity.\n\n" \ + "Options:\n" \ + " -p operate on an existing PID" +#define taskset_example_usage \ + "$ taskset 0x7 ./dgemm_test&\n" \ + "$ taskset -p 0x1 $!\n" \ + "pid 4790's current affinity mask: 7\n" \ + "pid 4790's new affinity mask: 1\n" \ + "$ taskset 0x7 /bin/sh -c './taskset -p 0x1 $$'\n" \ + "pid 6671's current affinity mask: 1\n" \ + "pid 6671's new affinity mask: 1\n" \ + "$ taskset -p 1\n" + "pid 1's current affinity mask: 3\n" + +#define tee_trivial_usage \ + "[OPTION]... [FILE]..." +#define tee_full_usage \ + "Copy standard input to each FILE, and also to standard output.\n\n" \ + "Options:\n" \ + " -a append to the given FILEs, do not overwrite\n" \ + " -i ignore interrupt signals (SIGINT)" +#define tee_example_usage \ + "$ echo \"Hello\" | tee /tmp/foo\n" \ + "$ cat /tmp/foo\n" \ + "Hello\n" + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +#define telnet_trivial_usage \ + "[-a] [-l USER] HOST [PORT]" +#define telnet_full_usage \ + "Telnet is used to establish interactive communication with another\n" \ + "computer over a network using the TELNET protocol.\n\n" \ + "Options:\n" \ + " -a Attempt an automatic login with the USER variable\n" \ + " -l USER Attempt an automatic login with the USER argument\n" \ + " HOST The official name, alias or the IP address of the\n" \ + " remote host.\n" \ + " PORT The remote port number to connect to. If it is not\n" \ + " specified, the default telnet (23) port is used." +#else +#define telnet_trivial_usage \ + "HOST [PORT]" +#define telnet_full_usage \ + "Telnet is used to establish interactive communication with another\n" \ + "computer over a network using the TELNET protocol." +#endif + +#ifdef CONFIG_FEATURE_TELNETD_STANDALONE +#define telnetd_trivial_usage \ + "[OPTION]" +#define telnetd_full_usage \ + "Telnetd listens for incoming TELNET connections on PORT.\n" \ + "Options:\n" \ + " -p PORT listen for connections on PORT (default 23)\n" \ + " -l LOGIN exec LOGIN on connect\n" \ + " -f issue_file Display issue_file instead of /etc/issue\n" \ + " -F Foreground mode\n" \ + " -i Inetd mode" +#else +#define telnetd_trivial_usage \ + "[OPTION]" +#define telnetd_full_usage \ + "Telnetd uses incoming TELNET connections via inetd.\n" \ + "Options:\n" \ + " -l LOGIN exec LOGIN on connect\n" \ + " -f issue_file Display issue_file instead of /etc/issue" +#endif + +#define test_trivial_usage \ + "EXPRESSION\n or [ EXPRESSION ]" +#define test_full_usage \ + "Checks file types and compares values returning an exit\n" \ + "code determined by the value of EXPRESSION." +#define test_example_usage \ + "$ test 1 -eq 2\n" \ + "$ echo $?\n" \ + "1\n" \ + "$ test 1 -eq 1\n" \ + "$ echo $?\n" \ + "0\n" \ + "$ [ -d /etc ]\n" \ + "$ echo $?\n" \ + "0\n" \ + "$ [ -d /junk ]\n" \ + "$ echo $?\n" \ + "1\n" + +#define tftp_trivial_usage \ + "[OPTION]... HOST [PORT]" +#define tftp_full_usage \ + "Transfers a file from/to a tftp server using \"octet\" mode.\n\n" \ + "Options:\n" \ + " -l FILE Local FILE\n" \ + " -r FILE Remote FILE" \ + USE_FEATURE_TFTP_GET( \ + "\n -g Get file" \ + ) \ + USE_FEATURE_TFTP_PUT( \ + "\n -p Put file" \ + ) \ + USE_FEATURE_TFTP_BLOCKSIZE( \ + "\n -b SIZE Transfer blocks of SIZE octets" \ + ) +#define time_trivial_usage \ + "[OPTION]... COMMAND [ARGS...]" +#define time_full_usage \ + "Runs the program COMMAND with arguments ARGS. When COMMAND finishes,\n" \ + "COMMAND's resource usage information is displayed\n\n" \ + "Options:\n" \ + " -v Displays verbose resource usage information" + +#define top_trivial_usage \ + "[-b] [-n count] [-d seconds]" +#define top_full_usage \ + "top provides a view of process activity in real time.\n" \ + "It reads the status of all processes from /proc each \n" \ + "and shows the status for however many processes will fit on the screen." + +#define touch_trivial_usage \ + "[-c] FILE [FILE ...]" +#define touch_full_usage \ + "Update the last-modified date on the given FILE[s].\n\n" \ + "Options:\n" \ + " -c Do not create any files" +#define touch_example_usage \ + "$ ls -l /tmp/foo\n" \ + "/bin/ls: /tmp/foo: No such file or directory\n" \ + "$ touch /tmp/foo\n" \ + "$ ls -l /tmp/foo\n" \ + "-rw-rw-r-- 1 andersen andersen 0 Apr 15 01:11 /tmp/foo\n" + +#define tr_trivial_usage \ + "[-cds] STRING1 [STRING2]" +#define tr_full_usage \ + "Translate, squeeze, and/or delete characters from\n" \ + "standard input, writing to standard output.\n\n" \ + "Options:\n" \ + " -c take complement of STRING1\n" \ + " -d delete input characters coded STRING1\n" \ + " -s squeeze multiple output characters of STRING2 into one character" +#define tr_example_usage \ + "$ echo \"gdkkn vnqkc\" | tr [a-y] [b-z]\n" \ + "hello world\n" + +#define traceroute_trivial_usage \ + "[-FIldnrv] [-f 1st_ttl] [-m max_ttl] [-p port#] [-q nqueries]\n" \ + " [-s src_addr] [-t tos] [-w wait] [-g gateway] [-i iface]\n" \ + " [-z pausemsecs] host [data size]" +#define traceroute_full_usage \ + "trace the route ip packets follow going to \"host\"\n" \ + "Options:\n" \ + " -F Set the don't fragment bit\n" \ + " -I Use ICMP ECHO instead of UDP datagrams\n" \ + " -l Display the ttl value of the returned packet\n" \ + " -d Set SO_DEBUG options to socket\n" \ + " -n Print hop addresses numerically rather than symbolically\n" \ + " -r Bypass the normal routing tables and send directly to a host\n" \ + " -v Verbose output\n" \ + " -m max_ttl Set the max time-to-live (max number of hops)\n" \ + " -p port# Set the base UDP port number used in probes\n" \ + " (default is 33434)\n" \ + " -q nqueries Set the number of probes per 'ttl' to nqueries\n" \ + " (default is 3)\n" \ + " -s src_addr Use the following IP address as the source address\n" \ + " -t tos Set the type-of-service in probe packets to the following value\n" \ + " (default 0)\n" \ + " -w wait Set the time (in seconds) to wait for a response to a probe\n" \ + " (default 3 sec)\n" \ + " -g Specify a loose source route gateway (8 maximum)" + + +#define true_trivial_usage \ + "" +#define true_full_usage \ + "Return an exit code of TRUE (0)." +#define true_example_usage \ + "$ true\n" \ + "$ echo $?\n" \ + "0\n" + +#define tty_trivial_usage \ + "" +#define tty_full_usage \ + "Print the file name of the terminal connected to standard input.\n\n" \ + "Options:\n" \ + " -s print nothing, only return an exit status" +#define tty_example_usage \ + "$ tty\n" \ + "/dev/tty2\n" + +#define tune2fs_trivial_usage \ + "[-c max-mounts-count] [-e errors-behavior] [-g group] " \ + "[-i interval[d|m|w]] [-j] [-J journal-options] [-l] [-s sparse-flag] " \ + "[-m reserved-blocks-percent] [-o [^]mount-options[,...]] " \ + "[-r reserved-blocks-count] [-u user] [-C mount-count] " \ + "[-L volume-label] [-M last-mounted-dir] [-O [^]feature[,...]] " \ + "[-T last-check-time] [-U UUID] device" +#define tune2fs_full_usage \ + "Adjust filesystem options on ext[23] filesystems." + +#define udhcpc_trivial_usage \ + "[-Cfbnqtv] [-c CID] [-V VCLS] [-H HOSTNAME] [-i INTERFACE]\n[-p pidfile] [-r IP] [-s script]" +#define udhcpc_full_usage \ + " -V,--vendorclass=CLASSID Set vendor class identifier\n" \ + " -i,--interface=INTERFACE Interface to use (default: eth0)\n" \ + " -H,-h,--hostname=HOSTNAME Client hostname\n" \ + " -c,--clientid=CLIENTID Set client identifier\n" \ + " -C,--clientid-none Suppress default client identifier\n" \ + " -p,--pidfile=file Store process ID of daemon in file\n" \ + " -r,--request=IP IP address to request (default: none)\n" \ + " -s,--script=file Run file at dhcp events (default: /usr/share/udhcpc/default.script)\n" \ + " -t,--retries=NUM Send up to NUM request packets\n"\ + " -f,--foreground Do not fork after getting lease\n" \ + " -b,--background Fork to background if lease cannot be immediately negotiated\n" \ + " -n,--now Exit with failure if lease cannot be immediately negotiated\n" \ + " -q,--quit Quit after obtaining lease\n" \ + " -R,--release Release IP on quit\n" \ + " -v,--version Display version" \ + +#define udhcpd_trivial_usage \ + "[configfile]\n" \ + +#define udhcpd_full_usage \ + "" + +#define umount_trivial_usage \ + "[flags] FILESYSTEM|DIRECTORY" +#define umount_full_usage \ + "Unmount file systems\n" \ + "\nFlags:\n" " -a Unmount all file systems" \ + USE_FEATURE_MTAB_SUPPORT(" in /etc/mtab\n -n Don't erase /etc/mtab entries") \ + "\n -r Try to remount devices as read-only if mount is busy" \ + "\n -l Lazy umount (detach filesystem)" \ + "\n -f Force umount (i.e., unreachable NFS server)" \ + USE_FEATURE_MOUNT_LOOP("\n -D Do not free loop device (if a loop device has been used)") +#define umount_example_usage \ + "$ umount /dev/hdc1\n" + +#define uname_trivial_usage \ + "[OPTION]..." +#define uname_full_usage \ + "Print certain system information. With no OPTION, same as -s.\n\n" \ + "Options:\n" \ + " -a print all information\n" \ + " -m the machine (hardware) type\n" \ + " -n print the machine's network node hostname\n" \ + " -r print the operating system release\n" \ + " -s print the operating system name\n" \ + " -p print the host processor type\n" \ + " -v print the operating system version" +#define uname_example_usage \ + "$ uname -a\n" \ + "Linux debian 2.4.23 #2 Tue Dec 23 17:09:10 MST 2003 i686 GNU/Linux\n" + +#define uncompress_trivial_usage \ + "[-c] [-f] [ name ... ]" +#define uncompress_full_usage \ + "Uncompress .Z file[s]\n" \ + "Options:\n" \ + " -c extract to stdout\n" \ + " -f force overwrite an existing file" + +#define uniq_trivial_usage \ + "[-fscdu]... [INPUT [OUTPUT]]" +#define uniq_full_usage \ + "Discard all but one of successive identical lines from INPUT\n" \ + "(or standard input), writing to OUTPUT (or standard output).\n\n" \ + "Options:\n" \ + " -c prefix lines by the number of occurrences\n" \ + " -d only print duplicate lines\n" \ + " -u only print unique lines\n" \ + " -f N skip the first N fields\n" \ + " -s N skip the first N chars (after any skipped fields)" +#define uniq_example_usage \ + "$ echo -e \"a\\na\\nb\\nc\\nc\\na\" | sort | uniq\n" \ + "a\n" \ + "b\n" \ + "c\n" + +#define unix2dos_trivial_usage \ + "[option] [FILE]" +#define unix2dos_full_usage \ + "Converts FILE from unix format to dos format. When no option\n" \ + "is given, the input is converted to the opposite output format.\n" \ + "When no file is given, uses stdin for input and stdout for output.\n" \ + "Options:\n" \ + " -u output will be in UNIX format\n" \ + " -d output will be in DOS format" + +#define unzip_trivial_usage \ + "[-opts[modifiers]] file[.zip] [list] [-x xlist] [-d exdir]" +#define unzip_full_usage \ + "Extracts files from ZIP archives.\n\n" \ + "Options:\n" \ + " -l list archive contents (short form)\n" \ + " -n never overwrite existing files (default)\n" \ + " -o overwrite files without prompting\n" \ + " -p send output to stdout\n" \ + " -q be quiet\n" \ + " -x exclude these files\n" \ + " -d extract files into this directory" + +#define uptime_trivial_usage \ + "" +#define uptime_full_usage \ + "Display the time since the last boot." +#define uptime_example_usage \ + "$ uptime\n" \ + " 1:55pm up 2:30, load average: 0.09, 0.04, 0.00\n" + +#define usleep_trivial_usage \ + "N" +#define usleep_full_usage \ + "Pause for N microseconds." +#define usleep_example_usage \ + "$ usleep 1000000\n" \ + "[pauses for 1 second]\n" + +#define uudecode_trivial_usage \ + "[FILE]..." +#define uudecode_full_usage \ + "Uudecode a file that is uuencoded.\n\n" \ + "Options:\n" \ + " -o FILE direct output to FILE" +#define uudecode_example_usage \ + "$ uudecode -o busybox busybox.uu\n" \ + "$ ls -l busybox\n" \ + "-rwxr-xr-x 1 ams ams 245264 Jun 7 21:35 busybox\n" + +#define uuencode_trivial_usage \ + "[OPTION] [INFILE] REMOTEFILE" +#define uuencode_full_usage \ + "Uuencode a file.\n\n" \ + "Options:\n" \ + " -m use base64 encoding per RFC1521" +#define uuencode_example_usage \ + "$ uuencode busybox busybox\n" \ + "begin 755 busybox\n" \ + "\n" \ + "$ uudecode busybox busybox > busybox.uu\n" \ + "$\n" + +#define vconfig_trivial_usage \ + "COMMAND [OPTIONS] ..." +#define vconfig_full_usage \ + "vconfig lets you create and remove virtual ethernet devices.\n\n" \ + "Options:\n" \ + " add [interface-name] [vlan_id]\n" \ + " rem [vlan-name]\n" \ + " set_flag [interface-name] [flag-num] [0 | 1]\n" \ + " set_egress_map [vlan-name] [skb_priority] [vlan_qos]\n" \ + " set_ingress_map [vlan-name] [skb_priority] [vlan_qos]\n" \ + " set_name_type [name-type]" + +#define vi_trivial_usage \ + "[OPTION] [FILE]..." +#define vi_full_usage \ + "edit FILE.\n\n" \ + "Options:\n" \ + " -R Read-only- do not write to the file" + +#define vlock_trivial_usage \ + "[OPTIONS]" +#define vlock_full_usage \ + "Lock a virtual terminal. A password is required to unlock\n" \ + "Options:\n" \ + " -a Lock all VTs" + +#define watch_trivial_usage \ + "[-n ] [-t] COMMAND..." +#define watch_full_usage \ + "Executes a program periodically\n\n" \ + "Options:\n" \ + " -n Loop period in seconds - default is 2\n" + " -t Don't print header" +#define watch_example_usage \ + "$ watch date\n" \ + "Mon Dec 17 10:31:40 GMT 2000\n" \ + "Mon Dec 17 10:31:42 GMT 2000\n" \ + "Mon Dec 17 10:31:44 GMT 2000" + +#define watchdog_trivial_usage \ + "[-t ] [-F] DEV" +#define watchdog_full_usage \ + "Periodically write to watchdog device DEV.\n" \ + "Options:\n" \ + " -t Timer period in seconds - default is 30\n" \ + " -F Stay in the foreground and don't fork" + +#define wc_trivial_usage \ + "[OPTION]... [FILE]..." +#define wc_full_usage \ + "Print line, word, and byte counts for each FILE, and a total line if\n" \ + "more than one FILE is specified. With no FILE, read standard input.\n\n" \ + "Options:\n" \ + " -c print the byte counts\n" \ + " -l print the newline counts\n" \ + " -L print the length of the longest line\n" \ + " -w print the word counts" +#define wc_example_usage \ + "$ wc /etc/passwd\n" \ + " 31 46 1365 /etc/passwd\n" + +#define wget_trivial_usage \ + "[-c|--continue] [-q|--quiet] [-O|--output-document file]\n" \ + " [--header 'header: value'] [-Y|--proxy on/off] [-P DIR]\n" \ + " [-U|--user-agent agent] url" +#define wget_full_usage \ + "wget retrieves files via HTTP or FTP\n\n" \ + "Options:\n" \ + " -c continue retrieval of aborted transfers\n" \ + " -q quiet mode - do not print\n" \ + " -P Set directory prefix to DIR\n" \ + " -O save to filename ('-' for stdout)\n" \ + " -U adjust 'User-Agent' field\n" \ + " -Y use proxy ('on' or 'off')" + +#define which_trivial_usage \ + "[COMMAND ...]" +#define which_full_usage \ + "Locates a COMMAND." +#define which_example_usage \ + "$ which login\n" \ + "/bin/login\n" + +#define who_trivial_usage \ + " " +#define who_full_usage \ + "Prints the current user names and related information" + +#define whoami_trivial_usage \ + "" +#define whoami_full_usage \ + "Prints the user name associated with the current effective user id." + +#define xargs_trivial_usage \ + "[OPTIONS] [COMMAND] [ARGS...]" +#define xargs_full_usage \ + "Executes COMMAND on every item given by standard input.\n\n" \ + "Options:\n" \ + USE_FEATURE_XARGS_SUPPORT_CONFIRMATION(" -p Prompt the user about whether to run each command\n") \ + " -r Do not run command for empty read lines\n" \ + USE_FEATURE_XARGS_SUPPORT_TERMOPT(" -x Exit if the size is exceeded\n") \ + USE_FEATURE_XARGS_SUPPORT_ZERO_TERM(" -0 Input filenames are terminated by a null character\n") \ + " -t Print the command line on stderr before executing it" +#define xargs_example_usage \ + "$ ls | xargs gzip\n" \ + "$ find . -name '*.c' -print | xargs rm\n" + +#define yes_trivial_usage \ + "[OPTION]... [STRING]..." +#define yes_full_usage \ + "Repeatedly outputs a line with all specified STRING(s), or 'y'." + +#define zcat_trivial_usage \ + "FILE" +#define zcat_full_usage \ + "Uncompress to stdout." + +#define zcip_trivial_usage \ + "[OPTIONS] ifname script" +#define zcip_full_usage \ + "zcip manages a ZeroConf IPv4 link-local address.\n" \ + "Options:\n" \ + " -f foreground mode\n" \ + " -q quit after address (no daemon)\n" \ + " -r 169.254.x.x request this address first\n" \ + " -v verbose" + +#endif /* __BB_USAGE_H__ */ diff --git a/include/xatonum.h b/include/xatonum.h new file mode 100644 index 000000000..585d84623 --- /dev/null +++ b/include/xatonum.h @@ -0,0 +1,157 @@ +/* vi: set sw=4 ts=4: */ +/* + * ascii-to-numbers implementations for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +/* Provides extern declarations of functions */ +#define DECLARE_STR_CONV(type, T, UT) \ +\ +unsigned type xstrto##UT##_range_sfx(const char *str, int b, unsigned type l, unsigned type u, const struct suffix_mult *sfx); \ +unsigned type xstrto##UT##_range(const char *str, int b, unsigned type l, unsigned type u); \ +unsigned type xstrto##UT##_sfx(const char *str, int b, const struct suffix_mult *sfx); \ +unsigned type xstrto##UT(const char *str, int b); \ +unsigned type xato##UT##_range_sfx(const char *str, unsigned type l, unsigned type u, const struct suffix_mult *sfx); \ +unsigned type xato##UT##_range(const char *str, unsigned type l, unsigned type u); \ +unsigned type xato##UT##_sfx(const char *str, const struct suffix_mult *sfx); \ +unsigned type xato##UT(const char *str); \ +type xstrto##T##_range_sfx(const char *str, int b, type l, type u, const struct suffix_mult *sfx) ;\ +type xstrto##T##_range(const char *str, int b, type l, type u); \ +type xato##T##_range_sfx(const char *str, type l, type u, const struct suffix_mult *sfx); \ +type xato##T##_range(const char *str, type l, type u); \ +type xato##T##_sfx(const char *str, const struct suffix_mult *sfx); \ +type xato##T(const char *str); \ + +/* Unsigned long long functions always exist */ +DECLARE_STR_CONV(long long, ll, ull) + + +/* Provides extern inline definitions of functions */ +/* (useful for mapping them to the type of the same width) */ +#define DEFINE_EQUIV_STR_CONV(narrow, N, W, UN, UW) \ +\ +extern inline \ +unsigned narrow xstrto##UN##_range_sfx(const char *str, int b, unsigned narrow l, unsigned narrow u, const struct suffix_mult *sfx) \ +{ return xstrto##UW##_range_sfx(str, b, l, u, sfx); } \ +extern inline \ +unsigned narrow xstrto##UN##_range(const char *str, int b, unsigned narrow l, unsigned narrow u) \ +{ return xstrto##UW##_range(str, b, l, u); } \ +extern inline \ +unsigned narrow xstrto##UN##_sfx(const char *str, int b, const struct suffix_mult *sfx) \ +{ return xstrto##UW##_sfx(str, b, sfx); } \ +extern inline \ +unsigned narrow xstrto##UN(const char *str, int b) \ +{ return xstrto##UW(str, b); } \ +extern inline \ +unsigned narrow xato##UN##_range_sfx(const char *str, unsigned narrow l, unsigned narrow u, const struct suffix_mult *sfx) \ +{ return xato##UW##_range_sfx(str, l, u, sfx); } \ +extern inline \ +unsigned narrow xato##UN##_range(const char *str, unsigned narrow l, unsigned narrow u) \ +{ return xato##UW##_range(str, l, u); } \ +extern inline \ +unsigned narrow xato##UN##_sfx(const char *str, const struct suffix_mult *sfx) \ +{ return xato##UW##_sfx(str, sfx); } \ +extern inline \ +unsigned narrow xato##UN(const char *str) \ +{ return xato##UW(str); } \ +extern inline \ +narrow xstrto##N##_range_sfx(const char *str, int b, narrow l, narrow u, const struct suffix_mult *sfx) \ +{ return xstrto##W##_range_sfx(str, b, l, u, sfx); } \ +extern inline \ +narrow xstrto##N##_range(const char *str, int b, narrow l, narrow u) \ +{ return xstrto##W##_range(str, b, l, u); } \ +extern inline \ +narrow xato##N##_range_sfx(const char *str, narrow l, narrow u, const struct suffix_mult *sfx) \ +{ return xato##W##_range_sfx(str, l, u, sfx); } \ +extern inline \ +narrow xato##N##_range(const char *str, narrow l, narrow u) \ +{ return xato##W##_range(str, l, u); } \ +extern inline \ +narrow xato##N##_sfx(const char *str, const struct suffix_mult *sfx) \ +{ return xato##W##_sfx(str, sfx); } \ +extern inline \ +narrow xato##N(const char *str) \ +{ return xato##W(str); } \ + +/* If long == long long, then just map them one-to-one */ +#if ULONG_MAX == ULLONG_MAX +DEFINE_EQUIV_STR_CONV(long, l, ll, ul, ull) +#else +/* Else provide extern defs */ +DECLARE_STR_CONV(long, l, ul) +#endif + +/* Same for int -> [long] long */ +#if UINT_MAX == ULLONG_MAX +DEFINE_EQUIV_STR_CONV(int, i, ll, u, ull) +#elif UINT_MAX == ULONG_MAX +DEFINE_EQUIV_STR_CONV(int, i, l, u, ul) +#else +DECLARE_STR_CONV(int, i, u) +#endif + +/* Specialized */ + +int BUG_xatou32_unimplemented(void); +extern inline uint32_t xatou32(const char *numstr) +{ + if (UINT_MAX == 0xffffffff) + return xatou(numstr); + if (ULONG_MAX == 0xffffffff) + return xatoul(numstr); + return BUG_xatou32_unimplemented(); +} + +/* Non-aborting kind of convertors */ + +unsigned long long bb_strtoull(const char *arg, char **endp, int base); +long long bb_strtoll(const char *arg, char **endp, int base); + +#if ULONG_MAX == ULLONG_MAX +extern inline +unsigned long bb_strtoul(const char *arg, char **endp, int base) +{ return bb_strtoull(arg, endp, base); } +extern inline +unsigned long bb_strtol(const char *arg, char **endp, int base) +{ return bb_strtoll(arg, endp, base); } +#else +unsigned long bb_strtoul(const char *arg, char **endp, int base); +long bb_strtol(const char *arg, char **endp, int base); +#endif + +#if UINT_MAX == ULLONG_MAX +extern inline +unsigned long bb_strtou(const char *arg, char **endp, int base) +{ return bb_strtoull(arg, endp, base); } +extern inline +unsigned long bb_strtoi(const char *arg, char **endp, int base) +{ return bb_strtoll(arg, endp, base); } +#elif UINT_MAX == ULONG_MAX +extern inline +unsigned long bb_strtou(const char *arg, char **endp, int base) +{ return bb_strtoul(arg, endp, base); } +extern inline +unsigned long bb_strtoi(const char *arg, char **endp, int base) +{ return bb_strtol(arg, endp, base); } +#else +unsigned long bb_strtou(const char *arg, char **endp, int base); +long bb_strtoi(const char *arg, char **endp, int base); +#endif + +int BUG_bb_strtou32_unimplemented(void); +extern inline +uint32_t bb_strtou32(const char *arg, char **endp, int base) +{ + if (sizeof(uint32_t) == sizeof(unsigned)) + return bb_strtou(arg, endp, base); + if (sizeof(uint32_t) == sizeof(unsigned long)) + return bb_strtoul(arg, endp, base); + return BUG_bb_strtou32_unimplemented(); +} + +/* Floating point */ + +/* double bb_strtod(const char *arg, char **endp); */ diff --git a/include/xregex.h b/include/xregex.h new file mode 100644 index 000000000..4185818a8 --- /dev/null +++ b/include/xregex.h @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +/* + * Busybox xregcomp utility routine. This isn't in libbb.h because the + * C library we're linking against may not support regex.h. + * + * Based in part on code from sash, Copyright (c) 1999 by David I. Bell + * Permission has been granted to redistribute this code under the GPL. + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ +#ifndef __BB_REGEX__ +#define __BB_REGEX__ + +#include +extern void xregcomp(regex_t *preg, const char *regex, int cflags); + +#endif diff --git a/init/Config.in b/init/Config.in new file mode 100644 index 000000000..c0ad5263d --- /dev/null +++ b/init/Config.in @@ -0,0 +1,84 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Init Utilities" + +config INIT + bool "init" + default n + select FEATURE_SYSLOG + help + init is the first program run when the system boots. + +config DEBUG_INIT + bool "debugging aid" + default n + depends on INIT + help + Turn this on to disable all the dangerous + rebooting stuff when debugging. + +config FEATURE_USE_INITTAB + bool "Support reading an inittab file" + default y + depends on INIT + help + Allow init to read an inittab file when the system boot. + +config FEATURE_INIT_SCTTY + bool "Support running commands with a controlling-tty" + default n + depends on INIT + help + If this option is enabled a command starting with hyphen (-) + is run in its own session (setsid(2)) and possibly with a + controlling tty (TIOCSCTTY). This is not the traditional init + behavour, but is often what you want in an embedded system where + the console is only accessed during development or for maintenance. + +config FEATURE_EXTRA_QUIET + bool "Be _extra_ quiet on boot" + default y + depends on INIT + help + Prevent init from logging some messages to the console during boot. + +config FEATURE_INIT_COREDUMPS + bool "Support dumping core for child processes (debugging only)" + default n + depends on INIT + help + If this option is enabled and the file /.init_enable_core + exists, then init will call setrlimit() to allow unlimited + core file sizes. If this option is disabled, processes + will not generate any core files. + + + +config FEATURE_INITRD + bool "Support running init from within an initrd (not initramfs)" + default y + depends on INIT + help + Legacy support for running init under the old-style initrd. Allows + the name linuxrc to act as init, and it doesn't assume init is PID 1. + + This does not apply to initramfs, which runs /init as PID 1 and + requires no special support. + +config HALT + bool "poweroff, halt, and reboot" + default y + help + Stop all processes and either halt, reboot, or power off the system. + +config MESG + bool "mesg" + default y + help + Mesg controls access to your terminal by others. It is typically + used to allow or disallow other users to write to your terminal + +endmenu diff --git a/init/Kbuild b/init/Kbuild new file mode 100644 index 000000000..e99360241 --- /dev/null +++ b/init/Kbuild @@ -0,0 +1,12 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_HALT) += halt.o +lib-$(CONFIG_INIT) += init.o +lib-$(CONFIG_MESG) += mesg.o +lib-$(CONFIG_INIT) += init_shared.o +lib-$(CONFIG_HALT) += init_shared.o diff --git a/init/halt.c b/init/halt.c new file mode 100644 index 000000000..a6cf48bbe --- /dev/null +++ b/init/halt.c @@ -0,0 +1,58 @@ +/* vi: set sw=4 ts=4: */ +/* + * Poweroff reboot and halt, oh my. + * + * Copyright 2006 by Rob Landley + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + +int halt_main(int argc, char *argv[]) +{ + static const int magic[] = { +#ifdef RB_HALT_SYSTEM +RB_HALT_SYSTEM, +#elif defined RB_HALT +RB_HALT, +#endif +#ifdef RB_POWER_OFF +RB_POWER_OFF, +#elif defined RB_POWERDOWN +RB_POWERDOWN, +#endif +RB_AUTOBOOT + }; + static const int signals[] = { SIGUSR1, SIGUSR2, SIGTERM }; + + char *delay; + int which, flags, rc = 1; + + /* Figure out which applet we're running */ + for (which = 0; "hpr"[which] != *applet_name; which++); + + /* Parse and handle arguments */ + flags = getopt32(argc, argv, "d:nf", &delay); + if (flags & 1) sleep(xatou(delay)); + if (!(flags & 2)) sync(); + + /* Perform action. */ + if (ENABLE_INIT && !(flags & 4)) { + if (ENABLE_FEATURE_INITRD) { + pid_t *pidlist = find_pid_by_name("linuxrc"); + if (pidlist[0] > 0) + rc = kill(pidlist[0], signals[which]); + if (ENABLE_FEATURE_CLEAN_UP) + free(pidlist); + } + if (rc) + rc = kill(1, signals[which]); + } else + rc = reboot(magic[which]); + + if (rc) + bb_error_msg("no"); + return rc; +} diff --git a/init/init.c b/init/init.c new file mode 100644 index 000000000..213a5c149 --- /dev/null +++ b/init/init.c @@ -0,0 +1,1131 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini init implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens . + * Copyright (C) 1999-2004 by Erik Andersen + * Adjusted by so many folks, it's impossible to keep track. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include +#include +#include +#include + +#include "init_shared.h" + +#ifdef CONFIG_SYSLOGD +# include +#endif + +#define INIT_BUFFS_SIZE 256 + +/* From */ +struct vt_stat { + unsigned short v_active; /* active vt */ + unsigned short v_signal; /* signal to send */ + unsigned short v_state; /* vt bitmask */ +}; +enum { VT_GETSTATE = 0x5603 }; /* get global vt state info */ + +/* From */ +struct serial_struct { + int type; + int line; + unsigned int port; + int irq; + int flags; + int xmit_fifo_size; + int custom_divisor; + int baud_base; + unsigned short close_delay; + char io_type; + char reserved_char[1]; + int hub6; + unsigned short closing_wait; /* time to wait before closing */ + unsigned short closing_wait2; /* no longer used... */ + unsigned char *iomem_base; + unsigned short iomem_reg_shift; + unsigned int port_high; + unsigned long iomap_base; /* cookie passed into ioremap */ + int reserved[1]; +}; + +#ifndef _PATH_STDPATH +#define _PATH_STDPATH "/usr/bin:/bin:/usr/sbin:/sbin" +#endif + +#if defined CONFIG_FEATURE_INIT_COREDUMPS +/* + * When a file named CORE_ENABLE_FLAG_FILE exists, setrlimit is called + * before processes are spawned to set core file size as unlimited. + * This is for debugging only. Don't use this is production, unless + * you want core dumps lying about.... + */ +#define CORE_ENABLE_FLAG_FILE "/.init_enable_core" +#include +#endif + +#define INITTAB "/etc/inittab" /* inittab file location */ +#ifndef INIT_SCRIPT +#define INIT_SCRIPT "/etc/init.d/rcS" /* Default sysinit script. */ +#endif + +#define MAXENV 16 /* Number of env. vars */ + +#define CONSOLE_BUFF_SIZE 32 + +/* Allowed init action types */ +#define SYSINIT 0x001 +#define RESPAWN 0x002 +#define ASKFIRST 0x004 +#define WAIT 0x008 +#define ONCE 0x010 +#define CTRLALTDEL 0x020 +#define SHUTDOWN 0x040 +#define RESTART 0x080 + +/* A mapping between "inittab" action name strings and action type codes. */ +struct init_action_type { + const char *name; + int action; +}; + +static const struct init_action_type actions[] = { + {"sysinit", SYSINIT}, + {"respawn", RESPAWN}, + {"askfirst", ASKFIRST}, + {"wait", WAIT}, + {"once", ONCE}, + {"ctrlaltdel", CTRLALTDEL}, + {"shutdown", SHUTDOWN}, + {"restart", RESTART}, + {0, 0} +}; + +/* Set up a linked list of init_actions, to be read from inittab */ +struct init_action { + pid_t pid; + char command[INIT_BUFFS_SIZE]; + char terminal[CONSOLE_BUFF_SIZE]; + struct init_action *next; + int action; +}; + +/* Static variables */ +static struct init_action *init_action_list = NULL; +static char console[CONSOLE_BUFF_SIZE] = CONSOLE_DEV; + +#ifndef CONFIG_SYSLOGD +static char *log_console = VC_5; +#endif +#if !ENABLE_DEBUG_INIT +static sig_atomic_t got_cont = 0; +#endif + +enum { + LOG = 0x1, + CONSOLE = 0x2, + +#if defined CONFIG_FEATURE_EXTRA_QUIET + MAYBE_CONSOLE = 0x0, +#else + MAYBE_CONSOLE = CONSOLE, +#endif + +#ifndef RB_HALT_SYSTEM + RB_HALT_SYSTEM = 0xcdef0123, /* FIXME: this overflows enum */ + RB_ENABLE_CAD = 0x89abcdef, + RB_DISABLE_CAD = 0, + RB_POWER_OFF = 0x4321fedc, + RB_AUTOBOOT = 0x01234567, +#endif +}; + +static const char * const environment[] = { + "HOME=/", + "PATH=" _PATH_STDPATH, + "SHELL=/bin/sh", + "USER=root", + NULL +}; + +/* Function prototypes */ +static void delete_init_action(struct init_action *a); +static int waitfor(const struct init_action *a, pid_t pid); +#if !ENABLE_DEBUG_INIT +static void shutdown_signal(int sig); +#endif + +static void loop_forever(void) +{ + while (1) + sleep(1); +} + +/* Print a message to the specified device. + * Device may be bitwise-or'd from LOG | CONSOLE */ +#if ENABLE_DEBUG_INIT +#define messageD message +#else +#define messageD(...) do {;} while(0); +#endif +static void message(int device, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); +static void message(int device, const char *fmt, ...) +{ + va_list arguments; + int l; + RESERVE_CONFIG_BUFFER(msg, 1024); +#ifndef CONFIG_SYSLOGD + static int log_fd = -1; +#endif + + msg[0] = '\r'; + va_start(arguments, fmt); + l = vsnprintf(msg + 1, 1024 - 2, fmt, arguments) + 1; + va_end(arguments); + +#ifdef CONFIG_SYSLOGD + /* Log the message to syslogd */ + if (device & LOG) { + /* don`t out "\r\n" */ + openlog(applet_name, 0, LOG_DAEMON); + syslog(LOG_INFO, "%s", msg + 1); + closelog(); + } + + msg[l++] = '\n'; + msg[l] = 0; +#else + + msg[l++] = '\n'; + msg[l] = 0; + /* Take full control of the log tty, and never close it. + * It's mine, all mine! Muhahahaha! */ + if (log_fd < 0) { + if ((log_fd = device_open(log_console, O_RDWR | O_NONBLOCK | O_NOCTTY)) < 0) { + log_fd = -2; + bb_error_msg("bummer, can't write to log on %s!", log_console); + device = CONSOLE; + } else { + fcntl(log_fd, F_SETFD, FD_CLOEXEC); + } + } + if ((device & LOG) && (log_fd >= 0)) { + full_write(log_fd, msg, l); + } +#endif + + if (device & CONSOLE) { + int fd = device_open(CONSOLE_DEV, + O_WRONLY | O_NOCTTY | O_NONBLOCK); + /* Always send console messages to /dev/console so people will see them. */ + if (fd >= 0) { + full_write(fd, msg, l); + close(fd); +#if ENABLE_DEBUG_INIT + /* all descriptors may be closed */ + } else { + bb_error_msg("bummer, can't print: "); + va_start(arguments, fmt); + vfprintf(stderr, fmt, arguments); + va_end(arguments); +#endif + } + } + RELEASE_CONFIG_BUFFER(msg); +} + +/* Set terminal settings to reasonable defaults */ +static void set_term(void) +{ + struct termios tty; + + tcgetattr(STDIN_FILENO, &tty); + + /* set control chars */ + tty.c_cc[VINTR] = 3; /* C-c */ + tty.c_cc[VQUIT] = 28; /* C-\ */ + tty.c_cc[VERASE] = 127; /* C-? */ + tty.c_cc[VKILL] = 21; /* C-u */ + tty.c_cc[VEOF] = 4; /* C-d */ + tty.c_cc[VSTART] = 17; /* C-q */ + tty.c_cc[VSTOP] = 19; /* C-s */ + tty.c_cc[VSUSP] = 26; /* C-z */ + + /* use line dicipline 0 */ + tty.c_line = 0; + + /* Make it be sane */ + tty.c_cflag &= CBAUD | CBAUDEX | CSIZE | CSTOPB | PARENB | PARODD; + tty.c_cflag |= CREAD | HUPCL | CLOCAL; + + + /* input modes */ + tty.c_iflag = ICRNL | IXON | IXOFF; + + /* output modes */ + tty.c_oflag = OPOST | ONLCR; + + /* local modes */ + tty.c_lflag = + ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN; + + tcsetattr(STDIN_FILENO, TCSANOW, &tty); +} + +static void console_init(void) +{ + int fd; + int tried = 0; + struct vt_stat vt; + struct serial_struct sr; + char *s; + + if ((s = getenv("CONSOLE")) != NULL || (s = getenv("console")) != NULL) { + safe_strncpy(console, s, sizeof(console)); + } else { + /* 2.2 kernels: identify the real console backend and try to use it */ + if (ioctl(0, TIOCGSERIAL, &sr) == 0) { + /* this is a serial console */ + snprintf(console, sizeof(console) - 1, SC_FORMAT, sr.line); + } else if (ioctl(0, VT_GETSTATE, &vt) == 0) { + /* this is linux virtual tty */ + snprintf(console, sizeof(console) - 1, VC_FORMAT, vt.v_active); + } else { + safe_strncpy(console, CONSOLE_DEV, sizeof(console)); + tried++; + } + } + + while ((fd = open(console, O_RDONLY | O_NONBLOCK)) < 0 && tried < 2) { + /* Can't open selected console -- try + logical system console and VT_MASTER */ + safe_strncpy(console, (tried == 0 ? CONSOLE_DEV : CURRENT_VC), + sizeof(console)); + tried++; + } + if (fd < 0) { + /* Perhaps we should panic here? */ +#ifndef CONFIG_SYSLOGD + log_console = +#endif + safe_strncpy(console, bb_dev_null, sizeof(console)); + } else { + s = getenv("TERM"); + /* check for serial console */ + if (ioctl(fd, TIOCGSERIAL, &sr) == 0) { + /* Force the TERM setting to vt102 for serial console -- + * if TERM is set to linux (the default) */ + if (s == NULL || strcmp(s, "linux") == 0) + putenv("TERM=vt102"); +#ifndef CONFIG_SYSLOGD + log_console = console; +#endif + } else { + if (s == NULL) + putenv("TERM=linux"); + } + close(fd); + } + messageD(LOG, "console=%s", console); +} + +static void fixup_argv(int argc, char **argv, char *new_argv0) +{ + int len; + + /* Fix up argv[0] to be certain we claim to be init */ + len = strlen(argv[0]); + memset(argv[0], 0, len); + safe_strncpy(argv[0], new_argv0, len + 1); + + /* Wipe argv[1]-argv[N] so they don't clutter the ps listing */ + len = 1; + while (argc > len) { + memset(argv[len], 0, strlen(argv[len])); + len++; + } +} + +/* Open the new terminal device */ +static void open_new_terminal(const char * const device, const int fail) { + struct stat sb; + + if ((device_open(device, O_RDWR)) < 0) { + if (stat(device, &sb) != 0) { + message(LOG | CONSOLE, "device '%s' does not exist.", device); + } else { + message(LOG | CONSOLE, "Bummer, can't open %s", device); + } + if (fail) + _exit(1); + /* else */ +#if !ENABLE_DEBUG_INIT + shutdown_signal(SIGUSR1); +#else + _exit(2); +#endif + } +} + +static pid_t run(const struct init_action *a) +{ + int i; + pid_t pid; + char *s, *tmpCmd, *cmd[INIT_BUFFS_SIZE], *cmdpath; + char buf[INIT_BUFFS_SIZE + 6]; /* INIT_BUFFS_SIZE+strlen("exec ")+1 */ + sigset_t nmask, omask; + static const char press_enter[] = +#ifdef CUSTOMIZED_BANNER +#include CUSTOMIZED_BANNER +#endif + "\nPlease press Enter to activate this console. "; + + /* Block sigchild while forking. */ + sigemptyset(&nmask); + sigaddset(&nmask, SIGCHLD); + sigprocmask(SIG_BLOCK, &nmask, &omask); + + if ((pid = fork()) == 0) { + + /* Clean up */ + close(0); + close(1); + close(2); + sigprocmask(SIG_SETMASK, &omask, NULL); + + /* Reset signal handlers that were set by the parent process */ + signal(SIGUSR1, SIG_DFL); + signal(SIGUSR2, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGCONT, SIG_DFL); + signal(SIGSTOP, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + + /* Create a new session and make ourself the process + * group leader */ + setsid(); + + /* Open the new terminal device */ + open_new_terminal(a->terminal, 1); + + /* Make sure the terminal will act fairly normal for us */ + set_term(); + /* Setup stdout, stderr for the new process so + * they point to the supplied terminal */ + dup(0); + dup(0); + + /* If the init Action requires us to wait, then force the + * supplied terminal to be the controlling tty. */ + if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) { + + /* Now fork off another process to just hang around */ + if ((pid = fork()) < 0) { + message(LOG | CONSOLE, "Can't fork!"); + _exit(1); + } + + if (pid > 0) { + + /* We are the parent -- wait till the child is done */ + signal(SIGINT, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGCHLD, SIG_DFL); + + waitfor(NULL, pid); + /* See if stealing the controlling tty back is necessary */ + if (tcgetpgrp(0) != getpid()) + _exit(0); + + /* Use a temporary process to steal the controlling tty. */ + if ((pid = fork()) < 0) { + message(LOG | CONSOLE, "Can't fork!"); + _exit(1); + } + if (pid == 0) { + setsid(); + ioctl(0, TIOCSCTTY, 1); + _exit(0); + } + waitfor(NULL, pid); + _exit(0); + } + + /* Now fall though to actually execute things */ + } + + /* See if any special /bin/sh requiring characters are present */ + if (strpbrk(a->command, "~`!$^&*()=|\\{}[];\"'<>?") != NULL) { + cmd[0] = (char *)DEFAULT_SHELL; + cmd[1] = "-c"; + cmd[2] = strcat(strcpy(buf, "exec "), a->command); + cmd[3] = NULL; + } else { + /* Convert command (char*) into cmd (char**, one word per string) */ + strcpy(buf, a->command); + s = buf; + for (tmpCmd = buf, i = 0; (tmpCmd = strsep(&s, " \t")) != NULL;) { + if (*tmpCmd != '\0') { + cmd[i] = tmpCmd; + i++; + } + } + cmd[i] = NULL; + } + + cmdpath = cmd[0]; + + /* + Interactive shells want to see a dash in argv[0]. This + typically is handled by login, argv will be setup this + way if a dash appears at the front of the command path + (like "-/bin/sh"). + */ + + if (*cmdpath == '-') { + + /* skip over the dash */ + ++cmdpath; + + /* find the last component in the command pathname */ + s = bb_get_last_path_component(cmdpath); + + /* make a new argv[0] */ + if ((cmd[0] = malloc(strlen(s) + 2)) == NULL) { + message(LOG | CONSOLE, bb_msg_memory_exhausted); + cmd[0] = cmdpath; + } else { + cmd[0][0] = '-'; + strcpy(cmd[0] + 1, s); + } +#ifdef CONFIG_FEATURE_INIT_SCTTY + /* Establish this process as session leader and + * (attempt) to make the tty (if any) a controlling tty. + */ + (void) setsid(); + (void) ioctl(0, TIOCSCTTY, 0/*don't steal it*/); +#endif + } + +#if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__) + if (a->action & ASKFIRST) { + char c; + /* + * Save memory by not exec-ing anything large (like a shell) + * before the user wants it. This is critical if swap is not + * enabled and the system has low memory. Generally this will + * be run on the second virtual console, and the first will + * be allowed to start a shell or whatever an init script + * specifies. + */ + messageD(LOG, "Waiting for enter to start '%s'" + "(pid %d, terminal %s)\n", + cmdpath, getpid(), a->terminal); + full_write(1, press_enter, sizeof(press_enter) - 1); + while(read(0, &c, 1) == 1 && c != '\n') + ; + } +#endif + + /* Log the process name and args */ + message(LOG, "Starting pid %d, console %s: '%s'", + getpid(), a->terminal, cmdpath); + +#if defined CONFIG_FEATURE_INIT_COREDUMPS + { + struct stat sb; + if (stat(CORE_ENABLE_FLAG_FILE, &sb) == 0) { + struct rlimit limit; + + limit.rlim_cur = RLIM_INFINITY; + limit.rlim_max = RLIM_INFINITY; + setrlimit(RLIMIT_CORE, &limit); + } + } +#endif + + /* Now run it. The new program will take over this PID, + * so nothing further in init.c should be run. */ + execv(cmdpath, cmd); + + /* We're still here? Some error happened. */ + message(LOG | CONSOLE, "Bummer, cannot run '%s': %m", cmdpath); + _exit(-1); + } + sigprocmask(SIG_SETMASK, &omask, NULL); + return pid; +} + +static int waitfor(const struct init_action *a, pid_t pid) +{ + int runpid; + int status, wpid; + + runpid = (NULL == a)? pid : run(a); + while (1) { + wpid = waitpid(runpid,&status,0); + if (wpid == runpid) + break; + if (wpid == -1 && errno == ECHILD) { + /* we missed its termination */ + break; + } + /* FIXME other errors should maybe trigger an error, but allow + * the program to continue */ + } + return wpid; +} + +/* Run all commands of a particular type */ +static void run_actions(int action) +{ + struct init_action *a, *tmp; + + for (a = init_action_list; a; a = tmp) { + tmp = a->next; + if (a->action == action) { + if (access(a->terminal, R_OK | W_OK)) { + delete_init_action(a); + } else if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) { + waitfor(a, 0); + delete_init_action(a); + } else if (a->action & ONCE) { + run(a); + delete_init_action(a); + } else if (a->action & (RESPAWN | ASKFIRST)) { + /* Only run stuff with pid==0. If they have + * a pid, that means it is still running */ + if (a->pid == 0) { + a->pid = run(a); + } + } + } + } +} + +#if !ENABLE_DEBUG_INIT +static void init_reboot(unsigned long magic) +{ + pid_t pid; + /* We have to fork here, since the kernel calls do_exit(0) in + * linux/kernel/sys.c, which can cause the machine to panic when + * the init process is killed.... */ + if ((pid = fork()) == 0) { + reboot(magic); + _exit(0); + } + waitpid (pid, NULL, 0); +} + +static void shutdown_system(void) +{ + sigset_t block_signals; + + /* run everything to be run at "shutdown". This is done _prior_ + * to killing everything, in case people wish to use scripts to + * shut things down gracefully... */ + run_actions(SHUTDOWN); + + /* first disable all our signals */ + sigemptyset(&block_signals); + sigaddset(&block_signals, SIGHUP); + sigaddset(&block_signals, SIGQUIT); + sigaddset(&block_signals, SIGCHLD); + sigaddset(&block_signals, SIGUSR1); + sigaddset(&block_signals, SIGUSR2); + sigaddset(&block_signals, SIGINT); + sigaddset(&block_signals, SIGTERM); + sigaddset(&block_signals, SIGCONT); + sigaddset(&block_signals, SIGSTOP); + sigaddset(&block_signals, SIGTSTP); + sigprocmask(SIG_BLOCK, &block_signals, NULL); + + /* Allow Ctrl-Alt-Del to reboot system. */ + init_reboot(RB_ENABLE_CAD); + + message(CONSOLE | LOG, "The system is going down NOW !!"); + sync(); + + /* Send signals to every process _except_ pid 1 */ + message(CONSOLE | LOG, init_sending_format, "TERM"); + kill(-1, SIGTERM); + sleep(1); + sync(); + + message(CONSOLE | LOG, init_sending_format, "KILL"); + kill(-1, SIGKILL); + sleep(1); + + sync(); +} + +static void exec_signal(int sig ATTRIBUTE_UNUSED) +{ + struct init_action *a, *tmp; + sigset_t unblock_signals; + + for (a = init_action_list; a; a = tmp) { + tmp = a->next; + if (a->action & RESTART) { + shutdown_system(); + + /* unblock all signals, blocked in shutdown_system() */ + sigemptyset(&unblock_signals); + sigaddset(&unblock_signals, SIGHUP); + sigaddset(&unblock_signals, SIGQUIT); + sigaddset(&unblock_signals, SIGCHLD); + sigaddset(&unblock_signals, SIGUSR1); + sigaddset(&unblock_signals, SIGUSR2); + sigaddset(&unblock_signals, SIGINT); + sigaddset(&unblock_signals, SIGTERM); + sigaddset(&unblock_signals, SIGCONT); + sigaddset(&unblock_signals, SIGSTOP); + sigaddset(&unblock_signals, SIGTSTP); + sigprocmask(SIG_UNBLOCK, &unblock_signals, NULL); + + /* Close whatever files are open. */ + close(0); + close(1); + close(2); + + /* Open the new terminal device */ + open_new_terminal(a->terminal, 0); + + /* Make sure the terminal will act fairly normal for us */ + set_term(); + /* Setup stdout, stderr on the supplied terminal */ + dup(0); + dup(0); + + messageD(CONSOLE | LOG, "Trying to re-exec %s", a->command); + execl(a->command, a->command, NULL); + + message(CONSOLE | LOG, "exec of '%s' failed: %m", + a->command); + sync(); + sleep(2); + init_reboot(RB_HALT_SYSTEM); + loop_forever(); + } + } +} + +static void shutdown_signal(int sig) +{ + char *m; + int rb; + + shutdown_system(); + + if (sig == SIGTERM) { + m = "reboot"; + rb = RB_AUTOBOOT; + } else if (sig == SIGUSR2) { + m = "poweroff"; + rb = RB_POWER_OFF; + } else { + m = "halt"; + rb = RB_HALT_SYSTEM; + } + message(CONSOLE | LOG, "Requesting system %s.", m); + sync(); + + /* allow time for last message to reach serial console */ + sleep(2); + + init_reboot(rb); + + loop_forever(); +} + +static void ctrlaltdel_signal(int sig ATTRIBUTE_UNUSED) +{ + run_actions(CTRLALTDEL); +} + +/* The SIGSTOP & SIGTSTP handler */ +static void stop_handler(int sig ATTRIBUTE_UNUSED) +{ + int saved_errno = errno; + + got_cont = 0; + while (!got_cont) + pause(); + got_cont = 0; + errno = saved_errno; +} + +/* The SIGCONT handler */ +static void cont_handler(int sig ATTRIBUTE_UNUSED) +{ + got_cont = 1; +} + +#endif /* ! ENABLE_DEBUG_INIT */ + +static void new_init_action(int action, const char *command, const char *cons) +{ + struct init_action *new_action, *a, *last; + + if (*cons == '\0') + cons = console; + + if (strcmp(cons, bb_dev_null) == 0 && (action & ASKFIRST)) + return; + + new_action = calloc((size_t) (1), sizeof(struct init_action)); + if (!new_action) { + message(LOG | CONSOLE, "Memory allocation failure"); + loop_forever(); + } + + /* Append to the end of the list */ + for (a = last = init_action_list; a; a = a->next) { + /* don't enter action if it's already in the list, + * but do overwrite existing actions */ + if ((strcmp(a->command, command) == 0) && + (strcmp(a->terminal, cons) ==0)) { + a->action = action; + free(new_action); + return; + } + last = a; + } + if (last) { + last->next = new_action; + } else { + init_action_list = new_action; + } + strcpy(new_action->command, command); + new_action->action = action; + strcpy(new_action->terminal, cons); + messageD(LOG|CONSOLE, "command='%s' action='%d' terminal='%s'\n", + new_action->command, new_action->action, new_action->terminal); +} + +static void delete_init_action(struct init_action *action) +{ + struct init_action *a, *b = NULL; + + for (a = init_action_list; a; b = a, a = a->next) { + if (a == action) { + if (b == NULL) { + init_action_list = a->next; + } else { + b->next = a->next; + } + free(a); + break; + } + } +} + +/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined, + * then parse_inittab() simply adds in some default + * actions(i.e., runs INIT_SCRIPT and then starts a pair + * of "askfirst" shells). If CONFIG_FEATURE_USE_INITTAB + * _is_ defined, but /etc/inittab is missing, this + * results in the same set of default behaviors. + */ +static void parse_inittab(void) +{ +#ifdef CONFIG_FEATURE_USE_INITTAB + FILE *file; + char buf[INIT_BUFFS_SIZE], lineAsRead[INIT_BUFFS_SIZE]; + char tmpConsole[CONSOLE_BUFF_SIZE]; + char *id, *runlev, *action, *command, *eol; + const struct init_action_type *a = actions; + + + file = fopen(INITTAB, "r"); + if (file == NULL) { + /* No inittab file -- set up some default behavior */ +#endif + /* Reboot on Ctrl-Alt-Del */ + new_init_action(CTRLALTDEL, "/sbin/reboot", ""); + /* Umount all filesystems on halt/reboot */ + new_init_action(SHUTDOWN, "/bin/umount -a -r", ""); + /* Swapoff on halt/reboot */ + if(ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "/sbin/swapoff -a", ""); + /* Prepare to restart init when a HUP is received */ + new_init_action(RESTART, "/sbin/init", ""); + /* Askfirst shell on tty1-4 */ + new_init_action(ASKFIRST, bb_default_login_shell, ""); + new_init_action(ASKFIRST, bb_default_login_shell, VC_2); + new_init_action(ASKFIRST, bb_default_login_shell, VC_3); + new_init_action(ASKFIRST, bb_default_login_shell, VC_4); + /* sysinit */ + new_init_action(SYSINIT, INIT_SCRIPT, ""); + + return; +#ifdef CONFIG_FEATURE_USE_INITTAB + } + + while (fgets(buf, INIT_BUFFS_SIZE, file) != NULL) { + /* Skip leading spaces */ + for (id = buf; *id == ' ' || *id == '\t'; id++); + + /* Skip the line if it's a comment */ + if (*id == '#' || *id == '\n') + continue; + + /* Trim the trailing \n */ + eol = strrchr(id, '\n'); + if (eol != NULL) + *eol = '\0'; + + /* Keep a copy around for posterity's sake (and error msgs) */ + strcpy(lineAsRead, buf); + + /* Separate the ID field from the runlevels */ + runlev = strchr(id, ':'); + if (runlev == NULL || *(runlev + 1) == '\0') { + message(LOG | CONSOLE, "Bad inittab entry: %s", lineAsRead); + continue; + } else { + *runlev = '\0'; + ++runlev; + } + + /* Separate the runlevels from the action */ + action = strchr(runlev, ':'); + if (action == NULL || *(action + 1) == '\0') { + message(LOG | CONSOLE, "Bad inittab entry: %s", lineAsRead); + continue; + } else { + *action = '\0'; + ++action; + } + + /* Separate the action from the command */ + command = strchr(action, ':'); + if (command == NULL || *(command + 1) == '\0') { + message(LOG | CONSOLE, "Bad inittab entry: %s", lineAsRead); + continue; + } else { + *command = '\0'; + ++command; + } + + /* Ok, now process it */ + for (a = actions; a->name != 0; a++) { + if (strcmp(a->name, action) == 0) { + if (*id != '\0') { + if(strncmp(id, "/dev/", 5) == 0) + id += 5; + strcpy(tmpConsole, "/dev/"); + safe_strncpy(tmpConsole + 5, id, + CONSOLE_BUFF_SIZE - 5); + id = tmpConsole; + } + new_init_action(a->action, command, id); + break; + } + } + if (a->name == 0) { + /* Choke on an unknown action */ + message(LOG | CONSOLE, "Bad inittab entry: %s", lineAsRead); + } + } + fclose(file); + return; +#endif /* CONFIG_FEATURE_USE_INITTAB */ +} + +#ifdef CONFIG_FEATURE_USE_INITTAB +static void reload_signal(int sig ATTRIBUTE_UNUSED) +{ + struct init_action *a, *tmp; + + message(LOG, "Reloading /etc/inittab"); + + /* disable old entrys */ + for (a = init_action_list; a; a = a->next ) { + a->action = ONCE; + } + + parse_inittab(); + + /* remove unused entrys */ + for (a = init_action_list; a; a = tmp) { + tmp = a->next; + if (a->action & (ONCE | SYSINIT | WAIT ) && + a->pid == 0 ) { + delete_init_action(a); + } + } + run_actions(RESPAWN); + return; +} +#endif /* CONFIG_FEATURE_USE_INITTAB */ + +int init_main(int argc, char **argv) +{ + struct init_action *a; + pid_t wpid; + + if (argc > 1 && !strcmp(argv[1], "-q")) { + return kill(1,SIGHUP); + } +#if !ENABLE_DEBUG_INIT + /* Expect to be invoked as init with PID=1 or be invoked as linuxrc */ + if (getpid() != 1 && + (!ENABLE_FEATURE_INITRD || !strstr(applet_name, "linuxrc"))) + { + bb_show_usage(); + } + /* Set up sig handlers -- be sure to + * clear all of these in run() */ + signal(SIGHUP, exec_signal); + signal(SIGQUIT, exec_signal); + signal(SIGUSR1, shutdown_signal); + signal(SIGUSR2, shutdown_signal); + signal(SIGINT, ctrlaltdel_signal); + signal(SIGTERM, shutdown_signal); + signal(SIGCONT, cont_handler); + signal(SIGSTOP, stop_handler); + signal(SIGTSTP, stop_handler); + + /* Turn off rebooting via CTL-ALT-DEL -- we get a + * SIGINT on CAD so we can shut things down gracefully... */ + init_reboot(RB_DISABLE_CAD); +#endif + + /* Figure out where the default console should be */ + console_init(); + + /* Close whatever files are open, and reset the console. */ + close(0); + close(1); + close(2); + + if (device_open(console, O_RDWR | O_NOCTTY) == 0) { + set_term(); + close(0); + } + + chdir("/"); + setsid(); + { + const char * const *e; + /* Make sure environs is set to something sane */ + for(e = environment; *e; e++) + putenv((char *) *e); + } + + if (argc > 1) setenv("RUNLEVEL", argv[1], 1); + + /* Hello world */ + message(MAYBE_CONSOLE | LOG, "init started: %s", bb_msg_full_version); + + /* Make sure there is enough memory to do something useful. */ + if (ENABLE_SWAPONOFF) { + struct sysinfo info; + + if (!sysinfo(&info) && + (info.mem_unit ? : 1) * (long long)info.totalram < 1024*1024) + { + message(CONSOLE,"Low memory: forcing swapon."); + /* swapon -a requires /proc typically */ + new_init_action(SYSINIT, "/bin/mount -t proc proc /proc", ""); + /* Try to turn on swap */ + new_init_action(SYSINIT, "/sbin/swapon -a", ""); + run_actions(SYSINIT); /* wait and removing */ + } + } + + /* Check if we are supposed to be in single user mode */ + if (argc > 1 && (!strcmp(argv[1], "single") || + !strcmp(argv[1], "-s") || !strcmp(argv[1], "1"))) { + /* Start a shell on console */ + new_init_action(RESPAWN, bb_default_login_shell, ""); + } else { + /* Not in single user mode -- see what inittab says */ + + /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined, + * then parse_inittab() simply adds in some default + * actions(i.e., runs INIT_SCRIPT and then starts a pair + * of "askfirst" shells */ + parse_inittab(); + } + +#ifdef CONFIG_SELINUX + if (getenv("SELINUX_INIT") == NULL) { + int enforce = 0; + + putenv("SELINUX_INIT=YES"); + if (selinux_init_load_policy(&enforce) == 0) { + execv(argv[0], argv); + } else if (enforce > 0) { + /* SELinux in enforcing mode but load_policy failed */ + /* At this point, we probably can't open /dev/console, so log() won't work */ + message(CONSOLE,"Unable to load SELinux Policy. Machine is in enforcing mode. Halting now."); + exit(1); + } + } +#endif /* CONFIG_SELINUX */ + + /* Make the command line just say "init" -- thats all, nothing else */ + fixup_argv(argc, argv, "init"); + + /* Now run everything that needs to be run */ + + /* First run the sysinit command */ + run_actions(SYSINIT); + + /* Next run anything that wants to block */ + run_actions(WAIT); + + /* Next run anything to be run only once */ + run_actions(ONCE); + +#ifdef CONFIG_FEATURE_USE_INITTAB + /* Redefine SIGHUP to reread /etc/inittab */ + signal(SIGHUP, reload_signal); +#else + signal(SIGHUP, SIG_IGN); +#endif /* CONFIG_FEATURE_USE_INITTAB */ + + + /* Now run the looping stuff for the rest of forever */ + while (1) { + /* run the respawn stuff */ + run_actions(RESPAWN); + + /* run the askfirst stuff */ + run_actions(ASKFIRST); + + /* Don't consume all CPU time -- sleep a bit */ + sleep(1); + + /* Wait for a child process to exit */ + wpid = wait(NULL); + while (wpid > 0) { + /* Find out who died and clean up their corpse */ + for (a = init_action_list; a; a = a->next) { + if (a->pid == wpid) { + /* Set the pid to 0 so that the process gets + * restarted by run_actions() */ + a->pid = 0; + message(LOG, "Process '%s' (pid %d) exited. " + "Scheduling it for restart.", + a->command, wpid); + } + } + /* see if anyone else is waiting to be reaped */ + wpid = waitpid(-1, NULL, WNOHANG); + } + } +} diff --git a/init/init_shared.c b/init/init_shared.c new file mode 100644 index 000000000..47480fc21 --- /dev/null +++ b/init/init_shared.c @@ -0,0 +1,63 @@ +/* vi: set sw=4 ts=4: */ +/* + * Stuff shared between init, reboot, halt, and poweroff + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include "init_shared.h" + +const char * const init_sending_format = "Sending SIG%s to all processes."; +#ifndef CONFIG_INIT +const char * const bb_shutdown_format = "\r%s\n"; +int bb_shutdown_system(unsigned long magic) +{ + int pri = LOG_KERN|LOG_NOTICE|LOG_FACMASK; + const char *message; + + /* Don't kill ourself */ + signal(SIGTERM,SIG_IGN); + signal(SIGHUP,SIG_IGN); + bb_setpgrp; + + /* Allow Ctrl-Alt-Del to reboot system. */ +#ifndef RB_ENABLE_CAD +#define RB_ENABLE_CAD 0x89abcdef +#endif + reboot(RB_ENABLE_CAD); + + openlog(applet_name, 0, pri); + + message = "\nThe system is going down NOW !!"; + syslog(pri, "%s", message); + printf(bb_shutdown_format, message); + + sync(); + + /* Send signals to every process _except_ pid 1 */ + message = "TERM"; + syslog(pri, init_sending_format, message); + printf(bb_shutdown_format, message); + + kill(-1, SIGTERM); + sleep(1); + sync(); + + message = "KILL"; + syslog(pri, init_sending_format, message); + printf(bb_shutdown_format, message); + + kill(-1, SIGKILL); + sleep(1); + + sync(); + + reboot(magic); + return 0; /* Shrug */ +} +#endif diff --git a/init/init_shared.h b/init/init_shared.h new file mode 100644 index 000000000..6df8de484 --- /dev/null +++ b/init/init_shared.h @@ -0,0 +1,10 @@ +/* vi: set sw=4 ts=4: */ +/* + * Helper functions shared by init et al. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +extern int kill_init(int sig); +extern int bb_shutdown_system(unsigned long magic); +extern const char * const init_sending_format; + diff --git a/init/mesg.c b/init/mesg.c new file mode 100644 index 000000000..7e47644c3 --- /dev/null +++ b/init/mesg.c @@ -0,0 +1,47 @@ +/* vi: set sw=4 ts=4: */ +/* + * mesg implementation for busybox + * + * Copyright (c) 2002 Manuel Novoa III + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include +#include + +#ifdef USE_TTY_GROUP +#define S_IWGRP_OR_S_IWOTH S_IWGRP +#else +#define S_IWGRP_OR_S_IWOTH (S_IWGRP | S_IWOTH) +#endif + +int mesg_main(int argc, char *argv[]) +{ + struct stat sb; + char *tty; + char c = 0; + + if ((--argc == 0) + || ((argc == 1) && (((c = **++argv) == 'y') || (c == 'n')))) { + if ((tty = ttyname(STDERR_FILENO)) == NULL) { + tty = "ttyname"; + } else if (stat(tty, &sb) == 0) { + if (argc == 0) { + puts(((sb.st_mode & (S_IWGRP | S_IWOTH)) == + 0) ? "is n" : "is y"); + return EXIT_SUCCESS; + } + if (chmod + (tty, + (c == + 'y') ? sb.st_mode | (S_IWGRP_OR_S_IWOTH) : sb. + st_mode & ~(S_IWGRP | S_IWOTH)) == 0) { + return EXIT_SUCCESS; + } + } + bb_perror_msg_and_die("%s", tty); + } + bb_show_usage(); +} diff --git a/libbb/Config.in b/libbb/Config.in new file mode 100644 index 000000000..92ee55cbc --- /dev/null +++ b/libbb/Config.in @@ -0,0 +1,29 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Busybox Library Tuning" + +config PASSWORD_MINLEN + int "Minimum password length" + default 6 + range 5 32 + help + Minimum allowable password length. + +config MD5_SIZE_VS_SPEED + int " MD5: Trade Bytes for Speed" + default 2 + range 0 3 + help + Trade binary size versus speed for the md5sum algorithm. + Approximate values running uClibc and hashing + linux-2.4.4.tar.bz2 were: + user times (sec) text size (386) + 0 (fastest) 1.1 6144 + 1 1.4 5392 + 2 3.0 5088 + 3 (smallest) 5.1 4912 + +endmenu diff --git a/libbb/Kbuild b/libbb/Kbuild new file mode 100644 index 000000000..c15615302 --- /dev/null +++ b/libbb/Kbuild @@ -0,0 +1,117 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= + +lib-y += ask_confirmation.o +lib-y += bb_askpass.o +lib-y += bb_do_delay.o +lib-y += bb_pwd.o +lib-y += bb_strtonum.o +lib-y += change_identity.o +lib-y += chomp.o +lib-y += compare_string_array.o +lib-y += concat_path_file.o +lib-y += concat_subpath_file.o +lib-y += copy_file.o +lib-y += copyfd.o +lib-y += crc32.o +lib-y += create_icmp6_socket.o +lib-y += create_icmp_socket.o +lib-y += default_error_retval.o +lib-y += device_open.o +lib-y += dump.o +lib-y += error_msg.o +lib-y += error_msg_and_die.o +lib-y += execable.o +lib-y += fclose_nonstdin.o +lib-y += fflush_stdout_and_exit.o +lib-y += fgets_str.o +lib-y += find_pid_by_name.o +lib-y += find_root_device.o +lib-y += full_write.o +lib-y += get_console.o +lib-y += get_last_path_component.o +lib-y += get_line_from_file.o +lib-y += getopt32.o +lib-y += herror_msg.o +lib-y += herror_msg_and_die.o +lib-y += human_readable.o +lib-y += inet_common.o +lib-y += info_msg.o +lib-y += inode_hash.o +lib-y += isdirectory.o +lib-y += kernel_version.o +lib-y += last_char_is.o +lib-y += llist.o +lib-y += login.o +lib-y += make_directory.o +lib-y += makedev.o +lib-y += md5.o +lib-y += messages.o +lib-y += mode_string.o +lib-y += mtab_file.o +lib-y += obscure.o +lib-y += parse_mode.o +lib-y += perror_msg.o +lib-y += perror_msg_and_die.o +lib-y += perror_nomsg.o +lib-y += perror_nomsg_and_die.o +lib-y += process_escape_sequence.o +lib-y += procps.o +lib-y += read.o +lib-y += recursive_action.o +lib-y += remove_file.o +lib-y += restricted_shell.o +lib-y += run_shell.o +lib-y += safe_strncpy.o +lib-y += safe_write.o +lib-y += setup_environment.o +lib-y += sha1.o +lib-y += simplify_path.o +lib-y += skip_whitespace.o +lib-y += speed_table.o +lib-y += trim.o +lib-y += u_signal_names.o +lib-y += uuencode.o +lib-y += vdprintf.o +lib-y += verror_msg.o +lib-y += vfork_daemon_rexec.o +lib-y += vherror_msg.o +lib-y += vinfo_msg.o +lib-y += vperror_msg.o +lib-y += warn_ignoring_args.o +lib-y += wfopen.o +lib-y += wfopen_input.o +lib-y += xatonum.o +lib-y += xconnect.o +lib-y += xfuncs.o +lib-y += xgetcwd.o +lib-y += xgethostbyname.o +lib-y += xgethostbyname2.o +lib-y += xreadlink.o + +# conditionally compiled objects: +lib-$(CONFIG_FEATURE_MOUNT_LOOP) += loop.o +lib-$(CONFIG_LOSETUP) += loop.o +lib-$(CONFIG_FEATURE_MTAB_SUPPORT) += mtab.o +lib-$(CONFIG_PASSWD) += pw_encrypt.o +lib-$(CONFIG_SULOGIN) += pw_encrypt.o +lib-$(CONFIG_FEATURE_HTTPD_AUTH_MD5) += pw_encrypt.o +lib-$(CONFIG_VLOCK) += correct_password.o +lib-$(CONFIG_SU) += correct_password.o +lib-$(CONFIG_LOGIN) += correct_password.o +lib-$(CONFIG_DF) += find_mount_point.o +lib-$(CONFIG_EJECT) += find_mount_point.o + +# We shouldn't build xregcomp.c if we don't need it - this ensures we don't +# require regex.h to be in the include dir even if we don't need it thereby +# allowing us to build busybox even if uclibc regex support is disabled. + +lib-$(CONFIG_AWK) += xregcomp.o +lib-$(CONFIG_SED) += xregcomp.o +lib-$(CONFIG_LESS) += xregcomp.o +lib-$(CONFIG_DEVFSD) += xregcomp.o diff --git a/libbb/README b/libbb/README new file mode 100644 index 000000000..4f28f7e34 --- /dev/null +++ b/libbb/README @@ -0,0 +1,11 @@ +Please see the LICENSE file for copyright information (GPLv2) + +libbb is BusyBox's utility library. All of this stuff used to be stuffed into +a single file named utility.c. When I split utility.c to create libbb, some of +the very oldest stuff ended up without their original copyright and licensing +information (which is now lost in the mists of time). If you see something +that you wrote that is mis-attributed, do let me know so we can fix that up. + + Erik Andersen + + diff --git a/libbb/ask_confirmation.c b/libbb/ask_confirmation.c new file mode 100644 index 000000000..4642fa036 --- /dev/null +++ b/libbb/ask_confirmation.c @@ -0,0 +1,36 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_ask_confirmation implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Read a line from stdin. If the first non-whitespace char is 'y' or 'Y', + * return 1. Otherwise return 0. + */ + +#include +#include +#include "libbb.h" + +int bb_ask_confirmation(void) +{ + int retval = 0; + int first = 1; + int c; + + while (((c = getchar()) != EOF) && (c != '\n')) { + /* Make sure we get the actual function call for isspace, + * as speed is not critical here. */ + if (first && !(isspace)(c)) { + --first; + if ((c == 'y') || (c == 'Y')) { + ++retval; + } + } + } + + return retval; +} diff --git a/libbb/bb_askpass.c b/libbb/bb_askpass.c new file mode 100644 index 000000000..097a0a290 --- /dev/null +++ b/libbb/bb_askpass.c @@ -0,0 +1,75 @@ +/* vi: set sw=4 ts=4: */ +/* + * Ask for a password + * I use a static buffer in this function. Plan accordingly. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "libbb.h" + +/* do nothing signal handler */ +static void askpass_timeout(int ATTRIBUTE_UNUSED ignore) +{ +} + +char *bb_askpass(int timeout, const char * prompt) +{ + static char passwd[64]; + + char *ret; + int i; + struct sigaction sa; + struct termios old, new; + + tcgetattr(STDIN_FILENO, &old); + tcflush(STDIN_FILENO, TCIFLUSH); + + memset(passwd, 0, sizeof(passwd)); + + fputs(prompt, stdout); + fflush(stdout); + + tcgetattr(STDIN_FILENO, &new); + new.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY); + new.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP); + tcsetattr(STDIN_FILENO, TCSANOW, &new); + + if (timeout) { + sa.sa_flags = 0; + sa.sa_handler = askpass_timeout; + sigaction(SIGALRM, &sa, NULL); + alarm(timeout); + } + + ret = NULL; + if (read(STDIN_FILENO, passwd, sizeof(passwd)-1) > 0) { + ret = passwd; + i = 0; + /* Last byte is guaranteed to be 0 + (read did not overwrite it) */ + do { + if (passwd[i] == '\r' || passwd[i] == '\n') + passwd[i] = '\0'; + } while (passwd[i++]); + } + + if (timeout) { + alarm(0); + } + + tcsetattr(STDIN_FILENO, TCSANOW, &old); + puts(""); + fflush(stdout); + return ret; +} diff --git a/libbb/bb_do_delay.c b/libbb/bb_do_delay.c new file mode 100644 index 000000000..e14b67a19 --- /dev/null +++ b/libbb/bb_do_delay.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * Busybox utility routines. + * + * Copyright (C) 2005 by Tito Ragusa + * + * Licensed under the GPL v2, see the file LICENSE in this tarball. + */ + +#include +#include +#include "libbb.h" + +void bb_do_delay(int seconds) +{ + time_t start, now; + + time(&start); + now = start; + while (difftime(now, start) < seconds) { + sleep(seconds); + time(&now); + } +} diff --git a/libbb/bb_pwd.c b/libbb/bb_pwd.c new file mode 100644 index 000000000..b5125b0f4 --- /dev/null +++ b/libbb/bb_pwd.c @@ -0,0 +1,130 @@ +/* vi: set sw=4 ts=4: */ +/* + * password utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include +#include +#include +#include "libbb.h" + + /* + * if bufsize is > 0 char *buffer cannot be set to NULL. + * If idname is not NULL it is written on the static + * allocated buffer (and a pointer to it is returned). + * if idname is NULL, id as string is written to the static + * allocated buffer and NULL is returned. + * if bufsize is = 0 char *buffer can be set to NULL. + * If idname exists a pointer to it is returned, + * else NULL is returned. + * if bufsize is < 0 char *buffer can be set to NULL. + * If idname exists a pointer to it is returned, + * else an error message is printed and the program exits. + */ + +/* internal function for bb_getpwuid and bb_getgrgid */ +static char * bb_getug(char *buffer, char *idname, long id, int bufsize, char prefix) +{ + if (bufsize > 0 ) { + assert(buffer!=NULL); + if(idname) { + return safe_strncpy(buffer, idname, bufsize); + } + snprintf(buffer, bufsize, "%ld", id); + } else if (bufsize < 0 && !idname) { + bb_error_msg_and_die("unknown %cid %ld", prefix, id); + } + return idname; +} + + /* Hacked by Tito Ragusa (c) 2004 to make it more + * flexible : + * + * if bufsize is > 0 char *group cannot be set to NULL. + * On success groupname is written on static allocated buffer + * group (and a pointer to it is returned). + * On failure gid as string is written to static allocated + * buffer group and NULL is returned. + * if bufsize is = 0 char *group can be set to NULL. + * On success groupname is returned. + * On failure NULL is returned. + * if bufsize is < 0 char *group can be set to NULL. + * On success groupname is returned. + * On failure an error message is printed and + * the program exits. + */ + +/* gets a groupname given a gid */ +char * bb_getgrgid(char *group, long gid, int bufsize) +{ + struct group *mygroup = getgrgid(gid); + + return bb_getug(group, (mygroup) ? + mygroup->gr_name : (char *)mygroup, gid, bufsize, 'g'); +} + +/* returns a gid given a group name */ +long bb_xgetgrnam(const char *name) +{ + struct group *mygroup; + + mygroup = getgrnam(name); + if (mygroup==NULL) + bb_error_msg_and_die("unknown group name: %s", name); + + return mygroup->gr_gid; +} + +/* returns a uid given a username */ +long bb_xgetpwnam(const char *name) +{ + struct passwd *myuser; + + myuser = getpwnam(name); + if (myuser==NULL) + bb_error_msg_and_die("unknown user name: %s", name); + + return myuser->pw_uid; +} + + /* Hacked by Tito Ragusa (c) 2004 to make it more + * flexible : + * + * if bufsize is > 0 char *name cannot be set to NULL. + * On success username is written on the static allocated + * buffer name (and a pointer to it is returned). + * On failure uid as string is written to the static + * allocated buffer name and NULL is returned. + * if bufsize is = 0 char *name can be set to NULL. + * On success username is returned. + * On failure NULL is returned. + * if bufsize is < 0 char *name can be set to NULL + * On success username is returned. + * On failure an error message is printed and + * the program exits. + */ + +/* gets a username given a uid */ +char * bb_getpwuid(char *name, long uid, int bufsize) +{ + struct passwd *myuser = getpwuid(uid); + + return bb_getug(name, myuser ? myuser->pw_name : (char *)myuser, + uid, bufsize, 'u'); +} + +unsigned long get_ug_id(const char *s, + long (*__bb_getxxnam)(const char *)) +{ + unsigned long r; + + r = bb_strtoul(s, NULL, 10); + if (errno) + r = __bb_getxxnam(s); + + return r; +} diff --git a/libbb/bb_strtonum.c b/libbb/bb_strtonum.c new file mode 100644 index 000000000..6fbd1f87d --- /dev/null +++ b/libbb/bb_strtonum.c @@ -0,0 +1,155 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* On exit: errno = 0 only if there was non-empty, '\0' terminated value + * errno = EINVAL if value was not '\0' terminated, but othervise ok + * Return value is still valid, caller should just check whether end[0] + * is a valid terminating char for particular case. OTOH, if caller + * requires '\0' terminated input, [s]he can just check errno == 0. + * errno = ERANGE if value had alphanumeric terminating char ("1234abcg"). + * errno = ERANGE if value is out of range, missing, etc. + * errno = ERANGE if value had minus sign for strtouXX (even "-0" is not ok ) + */ + +static unsigned long long ret_ERANGE(void) +{ + errno = ERANGE; /* this ain't as small as it looks (on glibc) */ + return ULLONG_MAX; +} + +static unsigned long long handle_errors(unsigned long long v, char **endp, char *endptr) +{ + if (endp) *endp = endptr; + + /* Check for the weird "feature": + * a "-" string is apparently a valid "number" for strto[u]l[l]! + * It returns zero and errno is 0! :( */ + if (endptr[-1] == '-') + return ret_ERANGE(); + + /* errno is already set to ERANGE by strtoXXX if value overflowed */ + if (endptr[0]) { + /* "1234abcg" or out-of-range? */ + if (isalnum(endptr[0]) || errno) + return ret_ERANGE(); + /* good number, just suspicious terminator */ + errno = EINVAL; + } + return v; +} + + +unsigned long long bb_strtoull(const char *arg, char **endp, int base) +{ + unsigned long long v; + char *endptr; + + /* strtoul(" -4200000000") returns 94967296, errno 0 (!) */ + /* I don't think that this is right. Preventing this... */ + if (!isalnum(arg[0])) return ret_ERANGE(); + + /* not 100% correct for lib func, but convenient for the caller */ + errno = 0; + v = strtoull(arg, &endptr, base); + return handle_errors(v, endp, endptr); +} + +long long bb_strtoll(const char *arg, char **endp, int base) +{ + unsigned long long v; + char *endptr; + + if (arg[0] != '-' && !isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtoll(arg, &endptr, base); + return handle_errors(v, endp, endptr); +} + +#if ULONG_MAX != ULLONG_MAX +unsigned long bb_strtoul(const char *arg, char **endp, int base) +{ + unsigned long v; + char *endptr; + + if (!isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtoul(arg, &endptr, base); + return handle_errors(v, endp, endptr); +} + +long bb_strtol(const char *arg, char **endp, int base) +{ + long v; + char *endptr; + + if (arg[0] != '-' && !isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtol(arg, &endptr, base); + return handle_errors(v, endp, endptr); +} +#endif + +#if UINT_MAX != ULONG_MAX +unsigned bb_strtou(const char *arg, char **endp, int base) +{ + unsigned long v; + char *endptr; + + if (!isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtoul(arg, &endptr, base); + if (v > UINT_MAX) return ret_ERANGE(); + return handle_errors(v, endp, endptr); +} + +int bb_strtoi(const char *arg, char **endp, int base) +{ + long v; + char *endptr; + + if (arg[0] != '-' && !isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtol(arg, &endptr, base); + if (v > INT_MAX) return ret_ERANGE(); + if (v < INT_MIN) return ret_ERANGE(); + return handle_errors(v, endp, endptr); +} +#endif + +/* Floating point */ + +#if 0 + +#include /* just for HUGE_VAL */ +#define NOT_DIGIT(a) (((unsigned char)(a-'0')) > 9) +double bb_strtod(const char *arg, char **endp) +{ + double v; + char *endptr; + + if (arg[0] != '-' && NOT_DIGIT(arg[0])) goto err; + errno = 0; + v = strtod(arg, &endptr); + if (endp) *endp = endptr; + if (endptr[0]) { + /* "1234abcg" or out-of-range? */ + if (isalnum(endptr[0]) || errno) { + err: + errno = ERANGE; + return HUGE_VAL; + } + /* good number, just suspicious terminator */ + errno = EINVAL; + } + return v; +} + +#endif diff --git a/libbb/change_identity.c b/libbb/change_identity.c new file mode 100644 index 000000000..3f888f523 --- /dev/null +++ b/libbb/change_identity.c @@ -0,0 +1,51 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "libbb.h" + + +/* Become the user and group(s) specified by PW. */ +const char *change_identity_e2str(const struct passwd *pw) +{ + if (initgroups(pw->pw_name, pw->pw_gid) == -1) + return "cannot set groups"; + endgrent(); /* ?? */ + xsetgid(pw->pw_gid); + xsetuid(pw->pw_uid); + return NULL; +} + +void change_identity(const struct passwd *pw) +{ + const char *err_msg = change_identity_e2str(pw); + + if (err_msg) + bb_perror_msg_and_die("%s", err_msg); +} diff --git a/libbb/chomp.c b/libbb/chomp.c new file mode 100644 index 000000000..eab770760 --- /dev/null +++ b/libbb/chomp.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +void chomp(char *s) +{ + char *lc = last_char_is(s, '\n'); + + if (lc) + *lc = 0; +} diff --git a/libbb/compare_string_array.c b/libbb/compare_string_array.c new file mode 100644 index 000000000..d15578ca3 --- /dev/null +++ b/libbb/compare_string_array.c @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* returns the array index of the string */ +/* (index of first match is returned, or -1) */ +int index_in_str_array(const char * const string_array[], const char *key) +{ + int i; + + for (i = 0; string_array[i] != 0; i++) { + if (strcmp(string_array[i], key) == 0) { + return i; + } + } + return -1; +} + +/* returns the array index of the string, even if it matches only a beginning */ +/* (index of first match is returned, or -1) */ +int index_in_substr_array(const char * const string_array[], const char *key) +{ + int i; + int len = strlen(key); + if (!len) + return -1; + + for (i = 0; string_array[i] != 0; i++) { + if (strncmp(string_array[i], key, len) == 0) { + return i; + } + } + return -1; +} diff --git a/libbb/concat_path_file.c b/libbb/concat_path_file.c new file mode 100644 index 000000000..9aae601a4 --- /dev/null +++ b/libbb/concat_path_file.c @@ -0,0 +1,27 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* concatenate path and file name to new allocation buffer, + * not adding '/' if path name already has '/' +*/ + +#include "libbb.h" + +char *concat_path_file(const char *path, const char *filename) +{ + char *lc; + + if (!path) + path = ""; + lc = last_char_is(path, '/'); + while (*filename == '/') + filename++; + return xasprintf("%s%s%s", path, (lc==NULL ? "/" : ""), filename); +} diff --git a/libbb/concat_subpath_file.c b/libbb/concat_subpath_file.c new file mode 100644 index 000000000..6ebc01bf5 --- /dev/null +++ b/libbb/concat_subpath_file.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) (C) 2003 Vladimir Oleynik + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + This function make special for recursive actions with usage + concat_path_file(path, filename) + and skiping "." and ".." directory entries +*/ + +#include "libbb.h" + +char *concat_subpath_file(const char *path, const char *f) +{ + if(f && *f == '.' && (!f[1] || (f[1] == '.' && !f[2]))) + return NULL; + return concat_path_file(path, f); +} diff --git a/libbb/copy_file.c b/libbb/copy_file.c new file mode 100644 index 000000000..0135831fe --- /dev/null +++ b/libbb/copy_file.c @@ -0,0 +1,282 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini copy_file implementation for busybox + * + * Copyright (C) 2001 by Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "libbb.h" + +static int retry_overwrite(const char *dest, int flags) +{ + if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) { + fprintf(stderr, "'%s' exists\n", dest); + return -1; + } + if (flags & FILEUTILS_INTERACTIVE) { + fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest); + if (!bb_ask_confirmation()) + return 0; // not allowed to overwrite + } + if (unlink(dest) < 0) { + bb_perror_msg("cannot remove '%s'", dest); + return -1; // error + } + return 1; // ok (to try again) +} + +int copy_file(const char *source, const char *dest, int flags) +{ + struct stat source_stat; + struct stat dest_stat; + int status = 0; + signed char dest_exists = 0; + signed char ovr; + +#define FLAGS_DEREF (flags & FILEUTILS_DEREFERENCE) + + if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) { + // This may be a dangling symlink. + // Making [sym]links to dangling symlinks works, so... + if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) + goto make_links; + bb_perror_msg("cannot stat '%s'", source); + return -1; + } + + if (lstat(dest, &dest_stat) < 0) { + if (errno != ENOENT) { + bb_perror_msg("cannot stat '%s'", dest); + return -1; + } + } else { + if (source_stat.st_dev == dest_stat.st_dev + && source_stat.st_ino == dest_stat.st_ino + ) { + bb_error_msg("'%s' and '%s' are the same file", source, dest); + return -1; + } + dest_exists = 1; + } + + if (S_ISDIR(source_stat.st_mode)) { + DIR *dp; + struct dirent *d; + mode_t saved_umask = 0; + + if (!(flags & FILEUTILS_RECUR)) { + bb_error_msg("omitting directory '%s'", source); + return -1; + } + + /* Create DEST. */ + if (dest_exists) { + if (!S_ISDIR(dest_stat.st_mode)) { + bb_error_msg("target '%s' is not a directory", dest); + return -1; + } + } else { + mode_t mode; + saved_umask = umask(0); + + mode = source_stat.st_mode; + if (!(flags & FILEUTILS_PRESERVE_STATUS)) + mode = source_stat.st_mode & ~saved_umask; + mode |= S_IRWXU; + + if (mkdir(dest, mode) < 0) { + umask(saved_umask); + bb_perror_msg("cannot create directory '%s'", dest); + return -1; + } + + umask(saved_umask); + } + + /* Recursively copy files in SOURCE. */ + dp = opendir(source); + if (dp == NULL) { + status = -1; + goto preserve_status; + } + + while ((d = readdir(dp)) != NULL) { + char *new_source, *new_dest; + + new_source = concat_subpath_file(source, d->d_name); + if (new_source == NULL) + continue; + new_dest = concat_path_file(dest, d->d_name); + if (copy_file(new_source, new_dest, flags) < 0) + status = -1; + free(new_source); + free(new_dest); + } + closedir(dp); + + if (!dest_exists + && chmod(dest, source_stat.st_mode & ~saved_umask) < 0 + ) { + bb_perror_msg("cannot change permissions of '%s'", dest); + status = -1; + } + + } else if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) { + int (*lf)(const char *oldpath, const char *newpath); + make_links: + // Hmm... maybe + // if (DEREF && MAKE_SOFTLINK) source = realpath(source) ? + // (but realpath returns NULL on dangling symlinks...) + lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link; + if (lf(source, dest) < 0) { + ovr = retry_overwrite(dest, flags); + if (ovr <= 0) + return ovr; + if (lf(source, dest) < 0) { + bb_perror_msg("cannot create link '%s'", dest); + return -1; + } + } + return 0; + + } else if (S_ISREG(source_stat.st_mode) + // Huh? DEREF uses stat, which never returns links IIRC... + || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) + ) { + int src_fd; + int dst_fd; + if (ENABLE_FEATURE_PRESERVE_HARDLINKS) { + char *link_name; + + if (!FLAGS_DEREF + && is_in_ino_dev_hashtable(&source_stat, &link_name) + ) { + if (link(link_name, dest) < 0) { + ovr = retry_overwrite(dest, flags); + if (ovr <= 0) + return ovr; + if (link(link_name, dest) < 0) { + bb_perror_msg("cannot create link '%s'", dest); + return -1; + } + } + return 0; + } + // TODO: probably is_in_.. and add_to_... + // can be combined: find_or_add_... + add_to_ino_dev_hashtable(&source_stat, dest); + } + + src_fd = open(source, O_RDONLY); + if (src_fd == -1) { + bb_perror_msg("cannot open '%s'", source); + return -1; + } + + // POSIX: if exists and -i, ask (w/o -i assume yes). + // Then open w/o EXCL. + // If open still fails and -f, try unlink, then try open again. + // Result: a mess: + // If dest is a softlink, we overwrite softlink's destination! + // (or fail, if it points to dir/nonexistent location/etc). + // This is strange, but POSIX-correct. + // coreutils cp has --remove-destination to override this... + dst_fd = open(dest, (flags & FILEUTILS_INTERACTIVE) + ? O_WRONLY|O_CREAT|O_TRUNC|O_EXCL + : O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode); + if (dst_fd == -1) { + // We would not do POSIX insanity. -i asks, + // then _unlinks_ the offender. Presto. + // Or else we will end up having 3 open()s! + ovr = retry_overwrite(dest, flags); + if (ovr <= 0) { + close(src_fd); + return ovr; + } + dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode); + if (dst_fd == -1) { + bb_perror_msg("cannot open '%s'", dest); + close(src_fd); + return -1; + } + } + + if (bb_copyfd_eof(src_fd, dst_fd) == -1) + status = -1; + if (close(dst_fd) < 0) { + bb_perror_msg("cannot close '%s'", dest); + status = -1; + } + if (close(src_fd) < 0) { + bb_perror_msg("cannot close '%s'", source); + status = -1; + } + + } else if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode) + || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode) + || S_ISLNK(source_stat.st_mode) + ) { + // We are lazy here, a bit lax with races... + if (dest_exists) { + ovr = retry_overwrite(dest, flags); + if (ovr <= 0) + return ovr; + } + if (S_ISFIFO(source_stat.st_mode)) { + if (mkfifo(dest, source_stat.st_mode) < 0) { + bb_perror_msg("cannot create fifo '%s'", dest); + return -1; + } + } else if (S_ISLNK(source_stat.st_mode)) { + char *lpath; + + lpath = xreadlink(source); + if (symlink(lpath, dest) < 0) { + bb_perror_msg("cannot create symlink '%s'", dest); + free(lpath); + return -1; + } + free(lpath); + + if (flags & FILEUTILS_PRESERVE_STATUS) + if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0) + bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest); + + return 0; + + } else { + if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) { + bb_perror_msg("cannot create '%s'", dest); + return -1; + } + } + } else { + bb_error_msg("internal error: unrecognized file type"); + return -1; + } + + preserve_status: + + if (flags & FILEUTILS_PRESERVE_STATUS + /* Cannot happen: */ + /* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */ + ) { + struct utimbuf times; + + times.actime = source_stat.st_atime; + times.modtime = source_stat.st_mtime; + if (utime(dest, ×) < 0) + bb_perror_msg("cannot preserve %s of '%s'", "times", dest); + if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) { + source_stat.st_mode &= ~(S_ISUID | S_ISGID); + bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest); + } + if (chmod(dest, source_stat.st_mode) < 0) + bb_perror_msg("cannot preserve %s of '%s'", "permissions", dest); + } + + return status; +} diff --git a/libbb/copyfd.c b/libbb/copyfd.c new file mode 100644 index 000000000..c6b886647 --- /dev/null +++ b/libbb/copyfd.c @@ -0,0 +1,86 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2005 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include + +#include "libbb.h" + + +#if BUFSIZ < 4096 +#undef BUFSIZ +#define BUFSIZ 4096 +#endif + + +static off_t bb_full_fd_action(int src_fd, int dst_fd, off_t size) +{ + int status = -1; + off_t total = 0; + RESERVE_CONFIG_BUFFER(buffer, BUFSIZ); + + if (src_fd < 0) goto out; + + if (!size) { + size = BUFSIZ; + status = 1; /* copy until eof */ + } + + while (1) { + ssize_t rd; + + rd = safe_read(src_fd, buffer, size > BUFSIZ ? BUFSIZ : size); + + if (!rd) { /* eof - all done. */ + status = 0; + break; + } + if (rd < 0) { + bb_perror_msg(bb_msg_read_error); + break; + } + /* dst_fd == -1 is a fake, else... */ + if (dst_fd >= 0) { + ssize_t wr = full_write(dst_fd, buffer, rd); + if (wr < rd) { + bb_perror_msg(bb_msg_write_error); + break; + } + } + total += rd; + if (status < 0) { + size -= rd; + if (!size) { + status = 0; + break; + } + } + } + +out: + RELEASE_CONFIG_BUFFER(buffer); + + return status ? -1 : total; +} + + +off_t bb_copyfd_size(int fd1, int fd2, off_t size) +{ + if (size) { + return bb_full_fd_action(fd1, fd2, size); + } + return 0; +} + +off_t bb_copyfd_eof(int fd1, int fd2) +{ + return bb_full_fd_action(fd1, fd2, 0); +} diff --git a/libbb/correct_password.c b/libbb/correct_password.c new file mode 100644 index 000000000..fd7e0b56c --- /dev/null +++ b/libbb/correct_password.c @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "libbb.h" + +/* Ask the user for a password. + Return 1 if the user gives the correct password for entry PW, + 0 if not. Return 1 without asking for a password if run by UID 0 + or if PW has an empty password. */ + +int correct_password(const struct passwd *pw) +{ + char *unencrypted, *encrypted, *correct; + +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + if (!strcmp(pw->pw_passwd, "x") || !strcmp(pw->pw_passwd, "*")) { + struct spwd *sp = getspnam(pw->pw_name); + + if (!sp) + bb_error_msg_and_die("no valid shadow password"); + + correct = sp->sp_pwdp; + } + else +#endif + correct = pw->pw_passwd; + + if (!correct || correct[0] == '\0') + return 1; + + unencrypted = bb_askpass(0, "Password: "); + if (!unencrypted) { + return 0; + } + encrypted = crypt(unencrypted, correct); + memset(unencrypted, 0, strlen(unencrypted)); + return (!strcmp(encrypted, correct)) ? 1 : 0; +} diff --git a/libbb/crc32.c b/libbb/crc32.c new file mode 100644 index 000000000..1e4a57e8a --- /dev/null +++ b/libbb/crc32.c @@ -0,0 +1,39 @@ +/* vi: set sw=4 ts=4: */ +/* + * CRC32 table fill function + * Copyright (C) 2006 by Rob Sullivan + * (I can't really claim much credit however, as the algorithm is + * very well-known) + * + * The following function creates a CRC32 table depending on whether + * a big-endian (0x04c11db7) or little-endian (0xedb88320) CRC32 is + * required. Admittedly, there are other CRC32 polynomials floating + * around, but Busybox doesn't use them. + * + * endian = 1: big-endian + * endian = 0: little-endian + */ + +#include "libbb.h" + +uint32_t *crc32_filltable(int endian) +{ + + uint32_t *crc_table = xmalloc(256 * sizeof(uint32_t)); + uint32_t polynomial = endian ? 0x04c11db7 : 0xedb88320; + uint32_t c; + int i, j; + + for (i = 0; i < 256; i++) { + c = endian ? (i << 24) : i; + for (j = 8; j; j--) { + if (endian) + c = (c&0x80000000) ? ((c << 1) ^ polynomial) : (c << 1); + else + c = (c&1) ? ((c >> 1) ^ polynomial) : (c >> 1); + } + *crc_table++ = c; + } + + return crc_table - 256; +} diff --git a/libbb/create_icmp6_socket.c b/libbb/create_icmp6_socket.c new file mode 100644 index 000000000..c3d1b5578 --- /dev/null +++ b/libbb/create_icmp6_socket.c @@ -0,0 +1,39 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * create raw socket for icmp (IPv6 version) protocol test permission + * and drop root privileges if running setuid + * + */ + +#include +#include +#include +#include +#include +#include "libbb.h" + +#ifdef CONFIG_FEATURE_IPV6 +int create_icmp6_socket(void) +{ + struct protoent *proto; + int sock; + + proto = getprotobyname("ipv6-icmp"); + /* if getprotobyname failed, just silently force + * proto->p_proto to have the correct value for "ipv6-icmp" */ + if ((sock = socket(AF_INET6, SOCK_RAW, + (proto ? proto->p_proto : IPPROTO_ICMPV6))) < 0) { + if (errno == EPERM) + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + else + bb_perror_msg_and_die(bb_msg_can_not_create_raw_socket); + } + + /* drop root privs if running setuid */ + xsetuid(getuid()); + + return sock; +} +#endif diff --git a/libbb/create_icmp_socket.c b/libbb/create_icmp_socket.c new file mode 100644 index 000000000..431c4d8a7 --- /dev/null +++ b/libbb/create_icmp_socket.c @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * create raw socket for icmp protocol test permission + * and drop root privileges if running setuid + * + */ + +#include +#include +#include +#include +#include +#include "libbb.h" + +int create_icmp_socket(void) +{ + struct protoent *proto; + int sock; + + proto = getprotobyname("icmp"); + /* if getprotobyname failed, just silently force + * proto->p_proto to have the correct value for "icmp" */ + if ((sock = socket(AF_INET, SOCK_RAW, + (proto ? proto->p_proto : 1))) < 0) { /* 1 == ICMP */ + if (errno == EPERM) + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + else + bb_perror_msg_and_die(bb_msg_can_not_create_raw_socket); + } + + /* drop root privs if running setuid */ + xsetuid(getuid()); + + return sock; +} diff --git a/libbb/default_error_retval.c b/libbb/default_error_retval.c new file mode 100644 index 000000000..f4e46a4b5 --- /dev/null +++ b/libbb/default_error_retval.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Seems silly to copyright a global variable. ;-) Oh well. + * + * At least one applet (cmp) returns a value different from the typical + * EXIT_FAILURE values (1) when an error occurs. So, make it configurable + * by the applet. I suppose we could use a wrapper function to set it, but + * that too seems silly. + */ + +#include +#include "libbb.h" + +int xfunc_error_retval = EXIT_FAILURE; diff --git a/libbb/device_open.c b/libbb/device_open.c new file mode 100644 index 000000000..c788b6982 --- /dev/null +++ b/libbb/device_open.c @@ -0,0 +1,33 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "libbb.h" + + +/* try to open up the specified device */ +int device_open(const char *device, int mode) +{ + int m, f, fd = -1; + + m = mode | O_NONBLOCK; + + /* Retry up to 5 times */ + /* TODO: explain why it can't be considered insane */ + for (f = 0; f < 5; f++) + if ((fd = open(device, m, 0600)) >= 0) + break; + if (fd < 0) + return fd; + /* Reset original flags. */ + if (m != mode) + fcntl(fd, F_SETFL, mode); + return fd; +} diff --git a/libbb/dump.c b/libbb/dump.c new file mode 100644 index 000000000..10710894e --- /dev/null +++ b/libbb/dump.c @@ -0,0 +1,803 @@ +/* vi: set sw=4 ts=4: */ +/* + * Support code for the hexdump and od applets, + * based on code from util-linux v 2.11l + * + * Copyright (c) 1989 + * The Regents of the University of California. All rights reserved. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Original copyright notice is retained at the end of this file. + */ + +#include "libbb.h" +#include "dump.h" + +enum _vflag bb_dump_vflag = FIRST; +FS *bb_dump_fshead; /* head of format strings */ +static FU *endfu; +static char **_argv; +static off_t savaddress; /* saved address/offset in stream */ +static off_t eaddress; /* end address */ +static off_t address; /* address/offset in stream */ +off_t bb_dump_skip; /* bytes to skip */ +static int exitval; /* final exit value */ +int bb_dump_blocksize; /* data block size */ +int bb_dump_length = -1; /* max bytes to read */ + +static const char index_str[] = ".#-+ 0123456789"; + +static const char size_conv_str[] = +"\x1\x4\x4\x4\x4\x4\x4\x8\x8\x8\x8\010cdiouxXeEfgG"; + +static const char lcc[] = "diouxX"; + +int bb_dump_size(FS * fs) +{ + FU *fu; + int bcnt, cur_size; + char *fmt; + const char *p; + int prec; + + /* figure out the data block bb_dump_size needed for each format unit */ + for (cur_size = 0, fu = fs->nextfu; fu; fu = fu->nextfu) { + if (fu->bcnt) { + cur_size += fu->bcnt * fu->reps; + continue; + } + for (bcnt = prec = 0, fmt = fu->fmt; *fmt; ++fmt) { + if (*fmt != '%') + continue; + /* + * bb_dump_skip any special chars -- save precision in + * case it's a %s format. + */ + while (strchr(index_str + 1, *++fmt)); + if (*fmt == '.' && isdigit(*++fmt)) { + prec = atoi(fmt); + while (isdigit(*++fmt)); + } + if (!(p = strchr(size_conv_str + 12, *fmt))) { + if (*fmt == 's') { + bcnt += prec; + } else if (*fmt == '_') { + ++fmt; + if ((*fmt == 'c') || (*fmt == 'p') || (*fmt == 'u')) { + bcnt += 1; + } + } + } else { + bcnt += size_conv_str[p - (size_conv_str + 12)]; + } + } + cur_size += bcnt * fu->reps; + } + return cur_size; +} + +static void rewrite(FS * fs) +{ + enum { NOTOKAY, USEBCNT, USEPREC } sokay; + PR *pr, **nextpr = NULL; + FU *fu; + char *p1, *p2, *p3; + char savech, *fmtp; + const char *byte_count_str; + int nconv, prec = 0; + + for (fu = fs->nextfu; fu; fu = fu->nextfu) { + /* + * break each format unit into print units; each + * conversion character gets its own. + */ + for (nconv = 0, fmtp = fu->fmt; *fmtp; nextpr = &pr->nextpr) { + /* NOSTRICT */ + /* DBU:[dvae@cray.com] calloc so that forward ptrs start out NULL*/ + pr = xzalloc(sizeof(PR)); + if (!fu->nextpr) + fu->nextpr = pr; + /* ignore nextpr -- its unused inside the loop and is + * uninitialized 1st time thru. + */ + + /* bb_dump_skip preceding text and up to the next % sign */ + for (p1 = fmtp; *p1 && *p1 != '%'; ++p1); + + /* only text in the string */ + if (!*p1) { + pr->fmt = fmtp; + pr->flags = F_TEXT; + break; + } + + /* + * get precision for %s -- if have a byte count, don't + * need it. + */ + if (fu->bcnt) { + sokay = USEBCNT; + /* bb_dump_skip to conversion character */ + for (++p1; strchr(index_str, *p1); ++p1); + } else { + /* bb_dump_skip any special chars, field width */ + while (strchr(index_str + 1, *++p1)); + if (*p1 == '.' && isdigit(*++p1)) { + sokay = USEPREC; + prec = atoi(p1); + while (isdigit(*++p1)); + } else + sokay = NOTOKAY; + } + + p2 = p1 + 1; /* set end pointer */ + + /* + * figure out the byte count for each conversion; + * rewrite the format as necessary, set up blank- + * pbb_dump_adding for end of data. + */ + + if (*p1 == 'c') { + pr->flags = F_CHAR; + DO_BYTE_COUNT_1: + byte_count_str = "\001"; + DO_BYTE_COUNT: + if (fu->bcnt) { + do { + if (fu->bcnt == *byte_count_str) { + break; + } + } while (*++byte_count_str); + } + /* Unlike the original, output the remainder of the format string. */ + if (!*byte_count_str) { + bb_error_msg_and_die("bad byte count for conversion character %s", p1); + } + pr->bcnt = *byte_count_str; + } else if (*p1 == 'l') { + ++p2; + ++p1; + DO_INT_CONV: + { + const char *e; + if (!(e = strchr(lcc, *p1))) { + goto DO_BAD_CONV_CHAR; + } + pr->flags = F_INT; + if (e > lcc + 1) { + pr->flags = F_UINT; + } + byte_count_str = "\004\002\001"; + goto DO_BYTE_COUNT; + } + /* NOTREACHED */ + } else if (strchr(lcc, *p1)) { + goto DO_INT_CONV; + } else if (strchr("eEfgG", *p1)) { + pr->flags = F_DBL; + byte_count_str = "\010\004"; + goto DO_BYTE_COUNT; + } else if (*p1 == 's') { + pr->flags = F_STR; + if (sokay == USEBCNT) { + pr->bcnt = fu->bcnt; + } else if (sokay == USEPREC) { + pr->bcnt = prec; + } else { /* NOTOKAY */ + bb_error_msg_and_die("%%s requires a precision or a byte count"); + } + } else if (*p1 == '_') { + ++p2; + switch (p1[1]) { + case 'A': + endfu = fu; + fu->flags |= F_IGNORE; + /* FALLTHROUGH */ + case 'a': + pr->flags = F_ADDRESS; + ++p2; + if ((p1[2] != 'd') && (p1[2] != 'o') && (p1[2] != 'x')) { + goto DO_BAD_CONV_CHAR; + } + *p1 = p1[2]; + break; + case 'c': + pr->flags = F_C; + /* *p1 = 'c'; set in conv_c */ + goto DO_BYTE_COUNT_1; + case 'p': + pr->flags = F_P; + *p1 = 'c'; + goto DO_BYTE_COUNT_1; + case 'u': + pr->flags = F_U; + /* *p1 = 'c'; set in conv_u */ + goto DO_BYTE_COUNT_1; + default: + goto DO_BAD_CONV_CHAR; + } + } else { + DO_BAD_CONV_CHAR: + bb_error_msg_and_die("bad conversion character %%%s", p1); + } + + /* + * copy to PR format string, set conversion character + * pointer, update original. + */ + savech = *p2; + p1[1] = '\0'; + pr->fmt = xstrdup(fmtp); + *p2 = savech; + pr->cchar = pr->fmt + (p1 - fmtp); + + /* DBU:[dave@cray.com] w/o this, trailing fmt text, space is lost. + * Skip subsequent text and up to the next % sign and tack the + * additional text onto fmt: eg. if fmt is "%x is a HEX number", + * we lose the " is a HEX number" part of fmt. + */ + for (p3 = p2; *p3 && *p3 != '%'; p3++); + if (p3 > p2) + { + savech = *p3; + *p3 = '\0'; + pr->fmt = xrealloc(pr->fmt, strlen(pr->fmt)+(p3-p2)+1); + strcat(pr->fmt, p2); + *p3 = savech; + p2 = p3; + } + + fmtp = p2; + + /* only one conversion character if byte count */ + if (!(pr->flags & F_ADDRESS) && fu->bcnt && nconv++) { + bb_error_msg_and_die("byte count with multiple conversion characters"); + } + } + /* + * if format unit byte count not specified, figure it out + * so can adjust rep count later. + */ + if (!fu->bcnt) + for (pr = fu->nextpr; pr; pr = pr->nextpr) + fu->bcnt += pr->bcnt; + } + /* + * if the format string interprets any data at all, and it's + * not the same as the bb_dump_blocksize, and its last format unit + * interprets any data at all, and has no iteration count, + * repeat it as necessary. + * + * if, rep count is greater than 1, no trailing whitespace + * gets output from the last iteration of the format unit. + */ + for (fu = fs->nextfu;; fu = fu->nextfu) { + if (!fu->nextfu && fs->bcnt < bb_dump_blocksize && + !(fu->flags & F_SETREP) && fu->bcnt) + fu->reps += (bb_dump_blocksize - fs->bcnt) / fu->bcnt; + if (fu->reps > 1) { + for (pr = fu->nextpr;; pr = pr->nextpr) + if (!pr->nextpr) + break; + for (p1 = pr->fmt, p2 = NULL; *p1; ++p1) + p2 = isspace(*p1) ? p1 : NULL; + if (p2) + pr->nospace = p2; + } + if (!fu->nextfu) + break; + } +} + +static void do_skip(char *fname, int statok) +{ + struct stat sbuf; + + if (statok) { + if (fstat(STDIN_FILENO, &sbuf)) { + bb_perror_msg_and_die("%s", fname); + } + if ((!(S_ISCHR(sbuf.st_mode) || + S_ISBLK(sbuf.st_mode) || + S_ISFIFO(sbuf.st_mode))) && bb_dump_skip >= sbuf.st_size) { + /* If bb_dump_size valid and bb_dump_skip >= size */ + bb_dump_skip -= sbuf.st_size; + address += sbuf.st_size; + return; + } + } + if (fseek(stdin, bb_dump_skip, SEEK_SET)) { + bb_perror_msg_and_die("%s", fname); + } + savaddress = address += bb_dump_skip; + bb_dump_skip = 0; +} + +static int next(char **argv) +{ + static int done; + int statok; + + if (argv) { + _argv = argv; + return 1; + } + for (;;) { + if (*_argv) { + if (!(freopen(*_argv, "r", stdin))) { + bb_perror_msg("%s", *_argv); + exitval = 1; + ++_argv; + continue; + } + statok = done = 1; + } else { + if (done++) + return 0; + statok = 0; + } + if (bb_dump_skip) + do_skip(statok ? *_argv : "stdin", statok); + if (*_argv) + ++_argv; + if (!bb_dump_skip) + return 1; + } + /* NOTREACHED */ +} + +static unsigned char *get(void) +{ + static int ateof = 1; + static unsigned char *curp=NULL, *savp; /*DBU:[dave@cray.com]initialize curp */ + int n; + int need, nread; + unsigned char *tmpp; + + if (!curp) { + address = (off_t)0; /*DBU:[dave@cray.com] initialize,initialize..*/ + curp = (unsigned char *) xmalloc(bb_dump_blocksize); + savp = (unsigned char *) xmalloc(bb_dump_blocksize); + } else { + tmpp = curp; + curp = savp; + savp = tmpp; + address = savaddress += bb_dump_blocksize; + } + for (need = bb_dump_blocksize, nread = 0;;) { + /* + * if read the right number of bytes, or at EOF for one file, + * and no other files are available, zero-pad the rest of the + * block and set the end flag. + */ + if (!bb_dump_length || (ateof && !next((char **) NULL))) { + if (need == bb_dump_blocksize) { + return NULL; + } + if (bb_dump_vflag != ALL && !memcmp(curp, savp, nread)) { + if (bb_dump_vflag != DUP) { + puts("*"); + } + return NULL; + } + memset((char *) curp + nread, 0, need); + eaddress = address + nread; + return curp; + } + n = fread((char *) curp + nread, sizeof(unsigned char), + bb_dump_length == -1 ? need : MIN(bb_dump_length, need), stdin); + if (!n) { + if (ferror(stdin)) { + bb_perror_msg("%s", _argv[-1]); + } + ateof = 1; + continue; + } + ateof = 0; + if (bb_dump_length != -1) { + bb_dump_length -= n; + } + if (!(need -= n)) { + if (bb_dump_vflag == ALL || bb_dump_vflag == FIRST + || memcmp(curp, savp, bb_dump_blocksize)) { + if (bb_dump_vflag == DUP || bb_dump_vflag == FIRST) { + bb_dump_vflag = WAIT; + } + return curp; + } + if (bb_dump_vflag == WAIT) { + puts("*"); + } + bb_dump_vflag = DUP; + address = savaddress += bb_dump_blocksize; + need = bb_dump_blocksize; + nread = 0; + } else { + nread += n; + } + } +} + +static void bpad(PR * pr) +{ + char *p1, *p2; + + /* + * remove all conversion flags; '-' is the only one valid + * with %s, and it's not useful here. + */ + pr->flags = F_BPAD; + *pr->cchar = 's'; + for (p1 = pr->fmt; *p1 != '%'; ++p1); + for (p2 = ++p1; *p1 && strchr(" -0+#", *p1); ++p1) + if (pr->nospace) pr->nospace--; + while ((*p2++ = *p1++) != 0); +} + +static const char conv_str[] = + "\0\\0\0" + "\007\\a\0" /* \a */ + "\b\\b\0" + "\f\\b\0" + "\n\\n\0" + "\r\\r\0" + "\t\\t\0" + "\v\\v\0" + "\0"; + + +static void conv_c(PR * pr, unsigned char * p) +{ + const char *str = conv_str; + char buf[10]; + + do { + if (*p == *str) { + ++str; + goto strpr; + } + str += 4; + } while (*str); + + if (isprint(*p)) { + *pr->cchar = 'c'; + (void) printf(pr->fmt, *p); + } else { + sprintf(buf, "%03o", (int) *p); + str = buf; + strpr: + *pr->cchar = 's'; + printf(pr->fmt, str); + } +} + +static void conv_u(PR * pr, unsigned char * p) +{ + static const char list[] = + "nul\0soh\0stx\0etx\0eot\0enq\0ack\0bel\0" + "bs\0_ht\0_lf\0_vt\0_ff\0_cr\0_so\0_si\0_" + "dle\0dcl\0dc2\0dc3\0dc4\0nak\0syn\0etb\0" + "can\0em\0_sub\0esc\0fs\0_gs\0_rs\0_us"; + + /* od used nl, not lf */ + if (*p <= 0x1f) { + *pr->cchar = 's'; + printf(pr->fmt, list + (4 * (int)*p)); + } else if (*p == 0x7f) { + *pr->cchar = 's'; + printf(pr->fmt, "del"); + } else if (isprint(*p)) { + *pr->cchar = 'c'; + printf(pr->fmt, *p); + } else { + *pr->cchar = 'x'; + printf(pr->fmt, (int) *p); + } +} + +static void display(void) +{ +/* extern FU *endfu; */ + FS *fs; + FU *fu; + PR *pr; + int cnt; + unsigned char *bp; + + off_t saveaddress; + unsigned char savech = 0, *savebp; + + while ((bp = get()) != NULL) { + for (fs = bb_dump_fshead, savebp = bp, saveaddress = address; fs; + fs = fs->nextfs, bp = savebp, address = saveaddress) { + for (fu = fs->nextfu; fu; fu = fu->nextfu) { + if (fu->flags & F_IGNORE) { + break; + } + for (cnt = fu->reps; cnt; --cnt) { + for (pr = fu->nextpr; pr; address += pr->bcnt, + bp += pr->bcnt, pr = pr->nextpr) { + if (eaddress && address >= eaddress && + !(pr->flags & (F_TEXT | F_BPAD))) { + bpad(pr); + } + if (cnt == 1 && pr->nospace) { + savech = *pr->nospace; + *pr->nospace = '\0'; + } +/* PRINT; */ + switch (pr->flags) { + case F_ADDRESS: + printf(pr->fmt, (unsigned int) address); + break; + case F_BPAD: + printf(pr->fmt, ""); + break; + case F_C: + conv_c(pr, bp); + break; + case F_CHAR: + printf(pr->fmt, *bp); + break; + case F_DBL:{ + double dval; + float fval; + + switch (pr->bcnt) { + case 4: + memmove((char *) &fval, (char *) bp, + sizeof(fval)); + printf(pr->fmt, fval); + break; + case 8: + memmove((char *) &dval, (char *) bp, + sizeof(dval)); + printf(pr->fmt, dval); + break; + } + break; + } + case F_INT:{ + int ival; + short sval; + + switch (pr->bcnt) { + case 1: + printf(pr->fmt, (int) *bp); + break; + case 2: + memmove((char *) &sval, (char *) bp, + sizeof(sval)); + printf(pr->fmt, (int) sval); + break; + case 4: + memmove((char *) &ival, (char *) bp, + sizeof(ival)); + printf(pr->fmt, ival); + break; + } + break; + } + case F_P: + printf(pr->fmt, isprint(*bp) ? *bp : '.'); + break; + case F_STR: + printf(pr->fmt, (char *) bp); + break; + case F_TEXT: + printf(pr->fmt); + break; + case F_U: + conv_u(pr, bp); + break; + case F_UINT:{ + unsigned int ival; + unsigned short sval; + + switch (pr->bcnt) { + case 1: + printf(pr->fmt, (unsigned int) * bp); + break; + case 2: + memmove((char *) &sval, (char *) bp, + sizeof(sval)); + printf(pr->fmt, (unsigned int) sval); + break; + case 4: + memmove((char *) &ival, (char *) bp, + sizeof(ival)); + printf(pr->fmt, ival); + break; + } + break; + } + } + if (cnt == 1 && pr->nospace) { + *pr->nospace = savech; + } + } + } + } + } + } + if (endfu) { + /* + * if eaddress not set, error or file bb_dump_size was multiple of + * bb_dump_blocksize, and no partial block ever found. + */ + if (!eaddress) { + if (!address) { + return; + } + eaddress = address; + } + for (pr = endfu->nextpr; pr; pr = pr->nextpr) { + switch (pr->flags) { + case F_ADDRESS: + (void) printf(pr->fmt, (unsigned int) eaddress); + break; + case F_TEXT: + (void) printf(pr->fmt); + break; + } + } + } +} + +int bb_dump_dump(char **argv) +{ + FS *tfs; + + /* figure out the data block bb_dump_size */ + for (bb_dump_blocksize = 0, tfs = bb_dump_fshead; tfs; tfs = tfs->nextfs) { + tfs->bcnt = bb_dump_size(tfs); + if (bb_dump_blocksize < tfs->bcnt) { + bb_dump_blocksize = tfs->bcnt; + } + } + /* rewrite the rules, do syntax checking */ + for (tfs = bb_dump_fshead; tfs; tfs = tfs->nextfs) { + rewrite(tfs); + } + + next(argv); + display(); + + return exitval; +} + +void bb_dump_add(const char *fmt) +{ + const char *p; + char *p1; + char *p2; + static FS **nextfs; + FS *tfs; + FU *tfu, **nextfu; + const char *savep; + + /* start new linked list of format units */ + tfs = xzalloc(sizeof(FS)); /*DBU:[dave@cray.com] start out NULL */ + if (!bb_dump_fshead) { + bb_dump_fshead = tfs; + } else { + *nextfs = tfs; + } + nextfs = &tfs->nextfs; + nextfu = &tfs->nextfu; + + /* take the format string and break it up into format units */ + for (p = fmt;;) { + /* bb_dump_skip leading white space */ + p = skip_whitespace(p); + if (!*p) { + break; + } + + /* allocate a new format unit and link it in */ + /* NOSTRICT */ + /* DBU:[dave@cray.com] calloc so that forward pointers start out NULL */ + tfu = xzalloc(sizeof(FU)); + *nextfu = tfu; + nextfu = &tfu->nextfu; + tfu->reps = 1; + + /* if leading digit, repetition count */ + if (isdigit(*p)) { + for (savep = p; isdigit(*p); ++p); + if (!isspace(*p) && *p != '/') { + bb_error_msg_and_die("bad format {%s}", fmt); + } + /* may overwrite either white space or slash */ + tfu->reps = atoi(savep); + tfu->flags = F_SETREP; + /* bb_dump_skip trailing white space */ + p = skip_whitespace(++p); + } + + /* bb_dump_skip slash and trailing white space */ + if (*p == '/') { + p = skip_whitespace(++p); + } + + /* byte count */ + if (isdigit(*p)) { + for (savep = p; isdigit(*p); ++p); + if (!isspace(*p)) { + bb_error_msg_and_die("bad format {%s}", fmt); + } + tfu->bcnt = atoi(savep); + /* bb_dump_skip trailing white space */ + p = skip_whitespace(++p); + } + + /* format */ + if (*p != '"') { + bb_error_msg_and_die("bad format {%s}", fmt); + } + for (savep = ++p; *p != '"';) { + if (*p++ == 0) { + bb_error_msg_and_die("bad format {%s}", fmt); + } + } + tfu->fmt = xmalloc(p - savep + 1); + strncpy(tfu->fmt, savep, p - savep); + tfu->fmt[p - savep] = '\0'; +/* escape(tfu->fmt); */ + + p1 = tfu->fmt; + + /* alphabetic escape sequences have to be done in place */ + for (p2 = p1;; ++p1, ++p2) { + if (!*p1) { + *p2 = *p1; + break; + } + if (*p1 == '\\') { + const char *cs = conv_str + 4; + ++p1; + *p2 = *p1; + do { + if (*p1 == cs[2]) { + *p2 = cs[0]; + break; + } + cs += 4; + } while (*cs); + } + } + + p++; + } +} + +/* + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/libbb/error_msg.c b/libbb/error_msg.c new file mode 100644 index 000000000..b2141f3a2 --- /dev/null +++ b/libbb/error_msg.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + +void bb_error_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_verror_msg(s, p, NULL); + va_end(p); +} diff --git a/libbb/error_msg_and_die.c b/libbb/error_msg_and_die.c new file mode 100644 index 000000000..10d953513 --- /dev/null +++ b/libbb/error_msg_and_die.c @@ -0,0 +1,28 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + +int die_sleep; + +void bb_error_msg_and_die(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_verror_msg(s, p, NULL); + va_end(p); + if (die_sleep) + sleep(die_sleep); + exit(xfunc_error_retval); +} diff --git a/libbb/execable.c b/libbb/execable.c new file mode 100644 index 000000000..817c06736 --- /dev/null +++ b/libbb/execable.c @@ -0,0 +1,61 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 2006 Gabriel Somlo + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* check if path points to an executable file; + * return 1 if found; + * return 0 otherwise; + */ +int execable_file(const char *name) +{ + struct stat s; + return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode)); +} + +/* search $PATH for an executable file; + * return allocated string containing full path if found; + * return NULL otherwise; + */ +char *find_execable(const char *filename) +{ + char *path, *p, *n; + + p = path = xstrdup(getenv("PATH")); + while (p) { + n = strchr(p, ':'); + if (n) + *n++ = '\0'; + if (*p != '\0') { /* it's not a PATH="foo::bar" situation */ + p = concat_path_file(p, filename); + if (execable_file(p)) { + free(path); + return p; + } + free(p); + } + p = n; + } + free(path); + return NULL; +} + +/* search $PATH for an executable file; + * return 1 if found; + * return 0 otherwise; + */ +int exists_execable(const char *filename) +{ + char *ret = find_execable(filename); + if (ret) { + free(ret); + return 1; + } + return 0; +} diff --git a/libbb/fclose_nonstdin.c b/libbb/fclose_nonstdin.c new file mode 100644 index 000000000..88e8474f2 --- /dev/null +++ b/libbb/fclose_nonstdin.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * fclose_nonstdin implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* A number of standard utilities can accept multiple command line args + * of '-' for stdin, according to SUSv3. So we encapsulate the check + * here to save a little space. + */ + +#include "libbb.h" + +int fclose_if_not_stdin(FILE *f) +{ + if (f != stdin) { + return fclose(f); + } + return 0; +} diff --git a/libbb/fflush_stdout_and_exit.c b/libbb/fflush_stdout_and_exit.c new file mode 100644 index 000000000..456ce9513 --- /dev/null +++ b/libbb/fflush_stdout_and_exit.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * fflush_stdout_and_exit implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Attempt to fflush(stdout), and exit with an error code if stdout is + * in an error state. + */ + +#include "libbb.h" + +void fflush_stdout_and_exit(int retval) +{ + if (fflush(stdout)) { + retval = xfunc_error_retval; + } + if (die_sleep) + sleep(die_sleep); + exit(retval); +} diff --git a/libbb/fgets_str.c b/libbb/fgets_str.c new file mode 100644 index 000000000..1bc6c3b1c --- /dev/null +++ b/libbb/fgets_str.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* Read up to (and including) TERMINATING_STRING from FILE and return it. + * Return NULL on EOF. */ + +char *xmalloc_fgets_str(FILE *file, const char *terminating_string) +{ + char *linebuf = NULL; + const int term_length = strlen(terminating_string); + int end_string_offset; + int linebufsz = 0; + int idx = 0; + int ch; + + while (1) { + ch = fgetc(file); + if (ch == EOF) { + free(linebuf); + return NULL; + } + + /* grow the line buffer as necessary */ + while (idx > linebufsz - 2) { + linebufsz += 200; + linebuf = xrealloc(linebuf, linebufsz); + } + + linebuf[idx] = ch; + idx++; + + /* Check for terminating string */ + end_string_offset = idx - term_length; + if (end_string_offset > 0 + && memcmp(&linebuf[end_string_offset], terminating_string, term_length) == 0 + ) { + idx -= term_length; + break; + } + } + linebuf = xrealloc(linebuf, idx + 1); + linebuf[idx] = '\0'; + return linebuf; +} diff --git a/libbb/find_mount_point.c b/libbb/find_mount_point.c new file mode 100644 index 000000000..cb00b9806 --- /dev/null +++ b/libbb/find_mount_point.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +/* + * Given a block device, find the mount table entry if that block device + * is mounted. + * + * Given any other file (or directory), find the mount table entry for its + * filesystem. + */ +struct mntent *find_mount_point(const char *name, const char *table) +{ + struct stat s; + dev_t mountDevice; + FILE *mountTable; + struct mntent *mountEntry; + + if (stat(name, &s) != 0) + return 0; + + if ((s.st_mode & S_IFMT) == S_IFBLK) + mountDevice = s.st_rdev; + else + mountDevice = s.st_dev; + + + mountTable = setmntent(table ? table : bb_path_mtab_file, "r"); + if (!mountTable) + return 0; + + while ((mountEntry = getmntent(mountTable)) != 0) { + if (strcmp(name, mountEntry->mnt_dir) == 0 + || strcmp(name, mountEntry->mnt_fsname) == 0 + ) { /* String match. */ + break; + } + if (stat(mountEntry->mnt_fsname, &s) == 0 && s.st_rdev == mountDevice) /* Match the device. */ + break; + if (stat(mountEntry->mnt_dir, &s) == 0 && s.st_dev == mountDevice) /* Match the directory's mount point. */ + break; + } + endmntent(mountTable); + return mountEntry; +} diff --git a/libbb/find_pid_by_name.c b/libbb/find_pid_by_name.c new file mode 100644 index 000000000..e98616940 --- /dev/null +++ b/libbb/find_pid_by_name.c @@ -0,0 +1,55 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +/* find_pid_by_name() + * + * Modified by Vladimir Oleynik for use with libbb/procps.c + * This finds the pid of the specified process. + * Currently, it's implemented by rummaging through + * the proc filesystem. + * + * Returns a list of all matching PIDs + * It is the caller's duty to free the returned pidlist. + */ +pid_t* find_pid_by_name(const char* procName) +{ + pid_t* pidList; + int i = 0; + procps_status_t* p = NULL; + + pidList = xmalloc(sizeof(*pidList)); + while ((p = procps_scan(p, PSSCAN_PID|PSSCAN_COMM))) { + if (strncmp(p->comm, procName, sizeof(p->comm)-1) == 0) { + pidList = xrealloc(pidList, sizeof(*pidList) * (i+2)); + pidList[i++] = p->pid; + } + } + + pidList[i] = 0; + return pidList; +} + +pid_t *pidlist_reverse(pid_t *pidList) +{ + int i = 0; + while (pidList[i]) + i++; + if (--i >= 0) { + pid_t k; + int j; + for (j = 0; i > j; i--, j++) { + k = pidList[i]; + pidList[i] = pidList[j]; + pidList[j] = k; + } + } + return pidList; +} diff --git a/libbb/find_root_device.c b/libbb/find_root_device.c new file mode 100644 index 000000000..71b79b8d0 --- /dev/null +++ b/libbb/find_root_device.c @@ -0,0 +1,33 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +char *find_block_device(char *path) +{ + DIR *dir; + struct dirent *entry; + struct stat st; + dev_t dev; + char *retpath=NULL; + + if(stat(path, &st) || !(dir = opendir("/dev"))) return NULL; + dev = (st.st_mode & S_IFMT) == S_IFBLK ? st.st_rdev : st.st_dev; + while((entry = readdir(dir)) != NULL) { + char devpath[PATH_MAX]; + sprintf(devpath,"/dev/%s", entry->d_name); + if(!stat(devpath, &st) && S_ISBLK(st.st_mode) && st.st_rdev == dev) { + retpath = xstrdup(devpath); + break; + } + } + closedir(dir); + + return retpath; +} diff --git a/libbb/full_write.c b/libbb/full_write.c new file mode 100644 index 000000000..bceac6de5 --- /dev/null +++ b/libbb/full_write.c @@ -0,0 +1,38 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "libbb.h" + +/* + * Write all of the supplied buffer out to a file. + * This does multiple writes as necessary. + * Returns the amount written, or -1 on an error. + */ +ssize_t full_write(int fd, const void *buf, size_t len) +{ + ssize_t cc; + ssize_t total; + + total = 0; + + while (len) { + cc = safe_write(fd, buf, len); + + if (cc < 0) + return cc; /* write() returns -1 on failure. */ + + total += cc; + buf = ((const char *)buf) + cc; + len -= cc; + } + + return total; +} diff --git a/libbb/get_console.c b/libbb/get_console.c new file mode 100644 index 000000000..52ae3d215 --- /dev/null +++ b/libbb/get_console.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. If you wrote this, please + * acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include +#include "libbb.h" + + + +/* From */ +enum { KDGKBTYPE = 0x4B33 }; /* get keyboard type */ + + +static int open_a_console(const char *fnam) +{ + int fd; + + /* try read-write */ + fd = open(fnam, O_RDWR); + + /* if failed, try read-only */ + if (fd < 0 && errno == EACCES) + fd = open(fnam, O_RDONLY); + + /* if failed, try write-only */ + if (fd < 0 && errno == EACCES) + fd = open(fnam, O_WRONLY); + + return fd; +} + +/* + * Get an fd for use with kbd/console ioctls. + * We try several things because opening /dev/console will fail + * if someone else used X (which does a chown on /dev/console). + */ + +int get_console_fd(void) +{ + int fd; + + static const char * const choise_console_names[] = { + CONSOLE_DEV, CURRENT_VC, CURRENT_TTY + }; + + for (fd = 2; fd >= 0; fd--) { + int fd4name; + int choise_fd; + char arg; + + fd4name = open_a_console(choise_console_names[fd]); + chk_std: + choise_fd = fd4name >= 0 ? fd4name : fd; + + arg = 0; + if (ioctl(choise_fd, KDGKBTYPE, &arg) == 0) + return choise_fd; + if(fd4name >= 0) { + close(fd4name); + fd4name = -1; + goto chk_std; + } + } + + bb_error_msg("cannot get file descriptor referring to console"); + return fd; /* total failure */ +} diff --git a/libbb/get_last_path_component.c b/libbb/get_last_path_component.c new file mode 100644 index 000000000..311909726 --- /dev/null +++ b/libbb/get_last_path_component.c @@ -0,0 +1,32 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_get_last_path_component implementation for busybox + * + * Copyright (C) 2001 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +char *bb_get_last_path_component(char *path) +{ + char *first = path; + char *last; + + last = path - 1; + + while (*path) { + if ((*path != '/') && (path > ++last)) { + last = first = path; + } + ++path; + } + + if (*first == '/') { + last = first; + } + last[1] = 0; + + return first; +} diff --git a/libbb/get_line_from_file.c b/libbb/get_line_from_file.c new file mode 100644 index 000000000..3f2c6096e --- /dev/null +++ b/libbb/get_line_from_file.c @@ -0,0 +1,68 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 2005, 2006 Rob Landley + * Copyright (C) 2004 Erik Andersen + * Copyright (C) 2001 Matt Krai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* This function reads an entire line from a text file, up to a newline + * or NUL byte, inclusive. It returns a malloc'ed char * which must be + * stored and free'ed by the caller. If end is null '\n' isn't considered + * end of line. If end isn't null, length of the chunk read is stored in it. */ + +char *bb_get_chunk_from_file(FILE * file, int *end) +{ + int ch; + int idx = 0; + char *linebuf = NULL; + int linebufsz = 0; + + while ((ch = getc(file)) != EOF) { + /* grow the line buffer as necessary */ + if (idx >= linebufsz) { + linebuf = xrealloc(linebuf, linebufsz += 80); + } + linebuf[idx++] = (char) ch; + if (!ch || (end && ch == '\n')) + break; + } + if (end) + *end = idx; + if (linebuf) { + // huh, does fgets discard prior data on error like this? + // I don't think so.... + //if (ferror(file)) { + // free(linebuf); + // return NULL; + //} + linebuf = xrealloc(linebuf, idx+1); + linebuf[idx] = '\0'; + } + return linebuf; +} + +/* Get line, including trailing \n if any */ +char *xmalloc_fgets(FILE * file) +{ + int i; + + return bb_get_chunk_from_file(file, &i); +} + +/* Get line. Remove trailing \n */ +char *xmalloc_getline(FILE * file) +{ + int i; + char *c = bb_get_chunk_from_file(file, &i); + + if (i && c[--i] == '\n') + c[i] = 0; + + return c; +} diff --git a/libbb/getopt32.c b/libbb/getopt32.c new file mode 100644 index 000000000..dddf8121a --- /dev/null +++ b/libbb/getopt32.c @@ -0,0 +1,529 @@ +/* vi: set sw=4 ts=4: */ +/* + * universal getopt32 implementation for busybox + * + * Copyright (C) 2003-2005 Vladimir Oleynik + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +/* Documentation + +uint32_t +getopt32(int argc, char **argv, const char *applet_opts, ...) + + The command line options must be declared in const char + *applet_opts as a string of chars, for example: + + flags = getopt32(argc, argv, "rnug"); + + If one of the given options is found, a flag value is added to + the return value (an unsigned long). + + The flag value is determined by the position of the char in + applet_opts string. For example, in the above case: + + flags = getopt32(argc, argv, "rnug"); + + "r" will add 1 (bit 0) + "n" will add 2 (bit 1) + "u will add 4 (bit 2) + "g" will add 8 (bit 3) + + and so on. You can also look at the return value as a bit + field and each option sets one bit. + + On exit, global variable optind is set so that if you + will do argc -= optind; argv += optind; then + argc will be equal to number of remaining non-option + arguments, first one would be in argv[0], next in argv[1] and so on + (options and their parameters will be moved into argv[] + positions prior to argv[optind]). + + ":" If one of the options requires an argument, then add a ":" + after the char in applet_opts and provide a pointer to store + the argument. For example: + + char *pointer_to_arg_for_a; + char *pointer_to_arg_for_b; + char *pointer_to_arg_for_c; + char *pointer_to_arg_for_d; + + flags = getopt32(argc, argv, "a:b:c:d:", + &pointer_to_arg_for_a, &pointer_to_arg_for_b, + &pointer_to_arg_for_c, &pointer_to_arg_for_d); + + The type of the pointer (char* or llist_t*) may be controlled + by the "::" special separator that is set in the external string + opt_complementary (see below for more info). + + "::" If option can have an *optional* argument, then add a "::" + after its char in applet_opts and provide a pointer to store + the argument. Note that optional arguments _must_ + immediately follow the option: -oparam, not -o param. + + "+" If the first character in the applet_opts string is a plus, + then option processing will stop as soon as a non-option is + encountered in the argv array. Useful for applets like env + which should not process arguments to subprograms: + env -i ls -d / + Here we want env to process just the '-i', not the '-d'. + +const struct option *applet_long_options + + This struct allows you to define long options. The syntax for + declaring the array is just like that of getopt's longopts. + (see getopt(3)) + + static const struct option applet_long_options[] = { + //name,has_arg,flag,val + { "verbose", 0, 0, 'v' }, + { 0, 0, 0, 0 } + }; + applet_long_options = applet_long_options; + + The last member of struct option (val) typically is set to + matching short option from applet_opts. If there is no matching + char in applet_opts, then: + - return bit have next position after short options + - if has_arg is not "no_argument", use ptr for arg also + - opt_complementary affects it too + + Note: a good applet will make long options configurable via the + config process and not a required feature. The current standard + is to name the config option CONFIG_FEATURE__LONG_OPTIONS. + +const char *opt_complementary + + ":" The colon (":") is used to separate groups of two or more chars + and/or groups of chars and special characters (stating some + conditions to be checked). + + "abc" If groups of two or more chars are specified, the first char + is the main option and the other chars are secondary options. + Their flags will be turned on if the main option is found even + if they are not specifed on the command line. For example: + + opt_complementary = "abc"; + flags = getopt32(argc, argv, "abcd") + + If getopt() finds "-a" on the command line, then + getopt32's return value will be as if "-a -b -c" were + found. + + "ww" Adjacent double options have a counter associated which indicates + the number of occurences of the option. + For example the ps applet needs: + if w is given once, GNU ps sets the width to 132, + if w is given more than once, it is "unlimited" + + int w_counter = 0; + opt_complementary = "ww"; + getopt32(argc, argv, "w", &w_counter); + if (w_counter) + width = (w_counter == 1) ? 132 : INT_MAX; + else + get_terminal_width(...&width...); + + w_counter is a pointer to an integer. It has to be passed to + getopt32() after all other option argument sinks. + + For example: accept multiple -v to indicate the level of verbosity + and for each -b optarg, add optarg to my_b. Finally, if b is given, + turn off c and vice versa: + + llist_t *my_b = NULL; + int verbose_level = 0; + opt_complementary = "vv:b::b-c:c-b"; + f = getopt32(argc, argv, "vb:c", &my_b, &verbose_level); + if (f & 2) // -c after -b unsets -b flag + while (my_b) { dosomething_with(my_b->data); my_b = my_b->link; } + if (my_b) // but llist is stored if -b is specified + free_llist(my_b); + if (verbose_level) printf("verbose level is %d\n", verbose_level); + +Special characters: + + "-" A dash as the first char in a opt_complementary group forces + all arguments to be treated as options, even if they have + no leading dashes. Next char in this case can't be a digit (0-9), + use ':' or end of line. For example: + + opt_complementary = "-:w-x:x-w"; + getopt32(argc, argv, "wx"); + + Allows any arguments to be given without a dash (./program w x) + as well as with a dash (./program -x). + + "--" A double dash at the beginning of opt_complementary means the + argv[1] string should always be treated as options, even if it isn't + prefixed with a "-". This is useful for special syntax in applets + such as "ar" and "tar": + tar xvf foo.tar + + "-N" A dash as the first char in a opt_complementary group followed + by a single digit (0-9) means that at least N non-option + arguments must be present on the command line + + "=N" An equal sign as the first char in a opt_complementary group followed + by a single digit (0-9) means that exactly N non-option + arguments must be present on the command line + + "?N" A "?" as the first char in a opt_complementary group followed + by a single digit (0-9) means that at most N arguments must be present + on the command line. + + "V-" An option with dash before colon or end-of-line results in + bb_show_usage being called if this option is encountered. + This is typically used to implement "print verbose usage message + and exit" option. + + "-" A dash between two options causes the second of the two + to be unset (and ignored) if it is given on the command line. + + [FIXME: what if they are the same? like "x-x"? Is it ever useful?] + + For example: + The du applet has the options "-s" and "-d depth". If + getopt32 finds -s, then -d is unset or if it finds -d + then -s is unset. (Note: busybox implements the GNU + "--max-depth" option as "-d".) To obtain this behavior, you + set opt_complementary = "s-d:d-s". Only one flag value is + added to getopt32's return value depending on the + position of the options on the command line. If one of the + two options requires an argument pointer (":" in applet_opts + as in "d:") optarg is set accordingly. + + char *smax_print_depth; + + opt_complementary = "s-d:d-s:x-x"; + opt = getopt32(argc, argv, "sd:x", &smax_print_depth); + + if (opt & 2) + max_print_depth = atoi(smax_print_depth); + if (opt & 4) + printf("Detected odd -x usage\n"); + + "--" A double dash between two options, or between an option and a group + of options, means that they are mutually exclusive. Unlike + the "-" case above, an error will be forced if the options + are used together. + + For example: + The cut applet must have only one type of list specified, so + -b, -c and -f are mutally exclusive and should raise an error + if specified together. In this case you must set + opt_complementary = "b--cf:c--bf:f--bc". If two of the + mutually exclusive options are found, getopt32's + return value will have the error flag set (BB_GETOPT_ERROR) so + that we can check for it: + + if (flags & BB_GETOPT_ERROR) + bb_show_usage(); + + "x--x" Variation of the above, it means that -x option should occur + at most once. + + "?" A "?" as the first char in a opt_complementary group means: + if BB_GETOPT_ERROR is detected, don't return, call bb_show_usage + and exit instead. Next char after '?' can't be a digit. + + "::" A double colon after a char in opt_complementary means that the + option can occur multiple times. Each occurrence will be saved as + a llist_t element instead of char*. + + For example: + The grep applet can have one or more "-e pattern" arguments. + In this case you should use getopt32() as follows: + + llist_t *patterns = NULL; + + (this pointer must be initializated to NULL if the list is empty + as required by *llist_add_to(llist_t *old_head, char *new_item).) + + opt_complementary = "e::"; + + getopt32(argc, argv, "e:", &patterns); + $ grep -e user -e root /etc/passwd + root:x:0:0:root:/root:/bin/bash + user:x:500:500::/home/user:/bin/bash + + "?" An "?" between an option and a group of options means that + at least one of them is required to occur if the first option + occurs in preceding command line arguments. + + For example from "id" applet: + + // Don't allow -n -r -rn -ug -rug -nug -rnug + opt_complementary = "r?ug:n?ug:?u--g:g--u"; + flags = getopt32(argc, argv, "rnug"); + + This example allowed only: + $ id; id -u; id -g; id -ru; id -nu; id -rg; id -ng; id -rnu; id -rng + + "X" A opt_complementary group with just a single letter means + that this option is required. If more than one such group exists, + at least one option is required to occur (not all of them). + For example from "start-stop-daemon" applet: + + // Don't allow -KS -SK, but -S or -K is required + opt_complementary = "K:S:?K--S:S--K"; + flags = getopt32(argc, argv, "KS...); + + + Don't forget to use ':'. For example, "?322-22-23X-x-a" + is interpreted as "?3:22:-2:2-2:2-3Xa:2--x" - + max 3 args; count uses of '-2'; min 2 args; if there is + a '-2' option then unset '-3', '-X' and '-a'; if there is + a '-2' and after it a '-x' then error out. +*/ + +/* Code here assumes that 'unsigned' is at least 32 bits wide */ + +const char *opt_complementary; + +typedef struct { + int opt; + int list_flg; + unsigned switch_on; + unsigned switch_off; + unsigned incongruously; + unsigned requires; + void **optarg; /* char **optarg or llist_t **optarg */ + int *counter; +} t_complementary; + +/* You can set applet_long_options for parse called long options */ +#if ENABLE_GETOPT_LONG +static const struct option bb_default_long_options[] = { +/* { "help", 0, NULL, '?' }, */ + { 0, 0, 0, 0 } +}; + +const struct option *applet_long_options = bb_default_long_options; +#endif + +uint32_t option_mask32; + +uint32_t +getopt32(int argc, char **argv, const char *applet_opts, ...) +{ + unsigned flags = 0; + unsigned requires = 0; + t_complementary complementary[33]; + int c; + const unsigned char *s; + t_complementary *on_off; + va_list p; +#if ENABLE_GETOPT_LONG + const struct option *l_o; +#endif + unsigned trigger; + char **pargv = NULL; + int min_arg = 0; + int max_arg = -1; + +#define SHOW_USAGE_IF_ERROR 1 +#define ALL_ARGV_IS_OPTS 2 +#define FIRST_ARGV_IS_OPT 4 +#define FREE_FIRST_ARGV_IS_OPT 8 + int spec_flgs = 0; + + va_start(p, applet_opts); + + c = 0; + on_off = complementary; + memset(on_off, 0, sizeof(complementary)); + + /* skip GNU extension */ + s = (const unsigned char *)applet_opts; + if (*s == '+' || *s == '-') + s++; + while (*s) { + if (c >= 32) break; + on_off->opt = *s; + on_off->switch_on = (1 << c); + if (*++s == ':') { + on_off->optarg = va_arg(p, void **); + while (*++s == ':') /* skip */; + } + on_off++; + c++; + } + +#if ENABLE_GETOPT_LONG + for (l_o = applet_long_options; l_o->name; l_o++) { + if (l_o->flag) + continue; + for (on_off = complementary; on_off->opt != 0; on_off++) + if (on_off->opt == l_o->val) + goto next_long; + if (c >= 32) break; + on_off->opt = l_o->val; + on_off->switch_on = (1 << c); + if (l_o->has_arg != no_argument) + on_off->optarg = va_arg(p, void **); + c++; + next_long: ; + } +#endif /* ENABLE_GETOPT_LONG */ + for (s = (const unsigned char *)opt_complementary; s && *s; s++) { + t_complementary *pair; + unsigned *pair_switch; + + if (*s == ':') + continue; + c = s[1]; + if (*s == '?') { + if (c < '0' || c > '9') { + spec_flgs |= SHOW_USAGE_IF_ERROR; + } else { + max_arg = c - '0'; + s++; + } + continue; + } + if (*s == '-') { + if (c < '0' || c > '9') { + if (c == '-') { + spec_flgs |= FIRST_ARGV_IS_OPT; + s++; + } else + spec_flgs |= ALL_ARGV_IS_OPTS; + } else { + min_arg = c - '0'; + s++; + } + continue; + } + if (*s == '=') { + min_arg = max_arg = c - '0'; + s++; + continue; + } + for (on_off = complementary; on_off->opt; on_off++) + if (on_off->opt == *s) + break; + if (c == ':' && s[2] == ':') { + on_off->list_flg++; + continue; + } + if (c == ':' || c == '\0') { + requires |= on_off->switch_on; + continue; + } + if (c == '-' && (s[2] == ':' || s[2] == '\0')) { + flags |= on_off->switch_on; + on_off->incongruously |= on_off->switch_on; + s++; + continue; + } + if (c == *s) { + on_off->counter = va_arg(p, int *); + s++; + } + pair = on_off; + pair_switch = &(pair->switch_on); + for (s++; *s && *s != ':'; s++) { + if (*s == '?') { + pair_switch = &(pair->requires); + } else if (*s == '-') { + if (pair_switch == &(pair->switch_off)) + pair_switch = &(pair->incongruously); + else + pair_switch = &(pair->switch_off); + } else { + for (on_off = complementary; on_off->opt; on_off++) + if (on_off->opt == *s) { + *pair_switch |= on_off->switch_on; + break; + } + } + } + s--; + } + va_end (p); + +#if ENABLE_AR || ENABLE_TAR + if (spec_flgs & FIRST_ARGV_IS_OPT) { + if (argv[1] && argv[1][0] != '-' && argv[1][0] != '\0') { + argv[1] = xasprintf("-%s", argv[1]); + if (ENABLE_FEATURE_CLEAN_UP) + spec_flgs |= FREE_FIRST_ARGV_IS_OPT; + } + } +#endif + /* Note: just "getopt() <= 0" will not work good for + * "fake" short options, like this one: + * wget $'-\203' "Test: test" http://kernel.org/ + * (supposed to act as --header, but doesn't) */ +#if ENABLE_GETOPT_LONG + while ((c = getopt_long(argc, argv, applet_opts, + applet_long_options, NULL)) != -1) { +#else + while ((c = getopt(argc, argv, applet_opts)) != -1) { +#endif /* ENABLE_GETOPT_LONG */ + c &= 0xff; /* fight libc's sign extends */ +loop_arg_is_opt: + for (on_off = complementary; on_off->opt != c; on_off++) { + /* c==0 if long opt have non NULL flag */ + if (on_off->opt == 0 && c != 0) + bb_show_usage(); + } + if (flags & on_off->incongruously) { + if ((spec_flgs & SHOW_USAGE_IF_ERROR)) + bb_show_usage(); + flags |= BB_GETOPT_ERROR; + } + trigger = on_off->switch_on & on_off->switch_off; + flags &= ~(on_off->switch_off ^ trigger); + flags |= on_off->switch_on ^ trigger; + flags ^= trigger; + if (on_off->counter) + (*(on_off->counter))++; + if (on_off->list_flg) { + llist_add_to((llist_t **)(on_off->optarg), optarg); + } else if (on_off->optarg) { + *(char **)(on_off->optarg) = optarg; + } + if (pargv != NULL) + break; + } + + if (spec_flgs & ALL_ARGV_IS_OPTS) { + /* process argv is option, for example "ps" applet */ + if (pargv == NULL) + pargv = argv + optind; + while (*pargv) { + c = **pargv; + if (c == '\0') { + pargv++; + } else { + (*pargv)++; + goto loop_arg_is_opt; + } + } + } + +#if (ENABLE_AR || ENABLE_TAR) && ENABLE_FEATURE_CLEAN_UP + if (spec_flgs & FREE_FIRST_ARGV_IS_OPT) + free(argv[1]); +#endif + /* check depending requires for given options */ + for (on_off = complementary; on_off->opt; on_off++) { + if (on_off->requires && (flags & on_off->switch_on) && + (flags & on_off->requires) == 0) + bb_show_usage(); + } + if (requires && (flags & requires) == 0) + bb_show_usage(); + argc -= optind; + if (argc < min_arg || (max_arg >= 0 && argc > max_arg)) + bb_show_usage(); + + option_mask32 = flags; + return flags; +} diff --git a/libbb/herror_msg.c b/libbb/herror_msg.c new file mode 100644 index 000000000..1e6908d82 --- /dev/null +++ b/libbb/herror_msg.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include + +#include "libbb.h" + +void bb_herror_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_vherror_msg(s, p); + va_end(p); +} diff --git a/libbb/herror_msg_and_die.c b/libbb/herror_msg_and_die.c new file mode 100644 index 000000000..f62ddd2ea --- /dev/null +++ b/libbb/herror_msg_and_die.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include + +#include "libbb.h" + +void bb_herror_msg_and_die(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_vherror_msg(s, p); + va_end(p); + if (die_sleep) + sleep(die_sleep); + exit(xfunc_error_retval); +} diff --git a/libbb/human_readable.c b/libbb/human_readable.c new file mode 100644 index 000000000..ff1b55141 --- /dev/null +++ b/libbb/human_readable.c @@ -0,0 +1,89 @@ +/* vi: set sw=4 ts=4: */ +/* + * June 30, 2001 Manuel Novoa III + * + * All-integer version (hey, not everyone has floating point) of + * make_human_readable_str, modified from similar code I had written + * for busybox several months ago. + * + * Notes: + * 1) I'm using an unsigned long long to hold the product size * block_size, + * as df (which calls this routine) could request a representation of a + * partition size in bytes > max of unsigned long. If long longs aren't + * available, it would be possible to do what's needed using polynomial + * representations (say, powers of 1024) and manipulating coefficients. + * The base ten "bytes" output could be handled similarly. + * + * 2) This routine always outputs a decimal point and a tenths digit when + * display_unit != 0. Hence, it isn't uncommon for the returned string + * to have a length of 5 or 6. + * + * It might be nice to add a flag to indicate no decimal digits in + * that case. This could be either an additional parameter, or a + * special value of display_unit. Such a flag would also be nice for du. + * + * Some code to omit the decimal point and tenths digit is sketched out + * and "#if 0"'d below. + */ + +#include +#include "libbb.h" + +const char *make_human_readable_str(unsigned long long size, + unsigned long block_size, unsigned long display_unit) +{ + /* The code will adjust for additional (appended) units. */ + static const char zero_and_units[] = { '0', 0, 'k', 'M', 'G', 'T' }; + static const char fmt[] = "%llu"; + static const char fmt_tenths[] = "%llu.%d%c"; + + static char str[21]; /* Sufficient for 64 bit unsigned integers. */ + + unsigned long long val; + int frac; + const char *u; + const char *f; + + u = zero_and_units; + f = fmt; + frac = 0; + + val = size * block_size; + if (val == 0) { + return u; + } + + if (display_unit) { + val += display_unit/2; /* Deal with rounding. */ + val /= display_unit; /* Don't combine with the line above!!! */ + } else { + ++u; + while ((val >= 1024) + && (u < zero_and_units + sizeof(zero_and_units) - 1) + ) { + f = fmt_tenths; + ++u; + frac = (((int)(val % 1024)) * 10 + 1024/2) / 1024; + val /= 1024; + } + if (frac >= 10) { /* We need to round up here. */ + ++val; + frac = 0; + } +#if 0 + /* Sample code to omit decimal point and tenths digit. */ + if ( /* no_tenths */ 1 ) { + if ( frac >= 5 ) { + ++val; + } + f = "%llu%*c" /* fmt_no_tenths */ ; + frac = 1; + } +#endif + } + + /* If f==fmt then 'frac' and 'u' are ignored. */ + snprintf(str, sizeof(str), f, val, frac, *u); + + return str; +} diff --git a/libbb/inet_common.c b/libbb/inet_common.c new file mode 100644 index 000000000..d8e00353e --- /dev/null +++ b/libbb/inet_common.c @@ -0,0 +1,235 @@ +/* vi: set sw=4 ts=4: */ +/* + * stolen from net-tools-1.59 and stripped down for busybox by + * Erik Andersen + * + * Heavily modified by Manuel Novoa III Mar 12, 2001 + * + * Version: $Id: inet_common.c,v 1.8 2004/03/10 07:42:38 mjn3 Exp $ + * + */ + +#include "libbb.h" +#include "inet_common.h" + +int INET_resolve(const char *name, struct sockaddr_in *s_in, int hostfirst) +{ + struct hostent *hp; + struct netent *np; + + /* Grmpf. -FvK */ + s_in->sin_family = AF_INET; + s_in->sin_port = 0; + + /* Default is special, meaning 0.0.0.0. */ + if (!strcmp(name, bb_str_default)) { + s_in->sin_addr.s_addr = INADDR_ANY; + return 1; + } + /* Look to see if it's a dotted quad. */ + if (inet_aton(name, &s_in->sin_addr)) { + return 0; + } + /* If we expect this to be a hostname, try hostname database first */ +#ifdef DEBUG + if (hostfirst) { + bb_error_msg("gethostbyname (%s)", name); + } +#endif + if (hostfirst && (hp = gethostbyname(name)) != (struct hostent *) NULL) { + memcpy((char *) &s_in->sin_addr, (char *) hp->h_addr_list[0], + sizeof(struct in_addr)); + return 0; + } + /* Try the NETWORKS database to see if this is a known network. */ +#ifdef DEBUG + bb_error_msg("getnetbyname (%s)", name); +#endif + if ((np = getnetbyname(name)) != (struct netent *) NULL) { + s_in->sin_addr.s_addr = htonl(np->n_net); + return 1; + } + if (hostfirst) { + /* Don't try again */ + return -1; + } +#ifdef DEBUG + res_init(); + _res.options |= RES_DEBUG; +#endif + +#ifdef DEBUG + bb_error_msg("gethostbyname (%s)", name); +#endif + if ((hp = gethostbyname(name)) == (struct hostent *) NULL) { + return -1; + } + memcpy((char *) &s_in->sin_addr, (char *) hp->h_addr_list[0], + sizeof(struct in_addr)); + + return 0; +} + +/* cache */ +struct addr { + struct sockaddr_in addr; + char *name; + int host; + struct addr *next; +}; + +static struct addr *INET_nn = NULL; /* addr-to-name cache */ + +/* numeric: & 0x8000: default instead of *, + * & 0x4000: host instead of net, + * & 0x0fff: don't resolve + */ +int INET_rresolve(char *name, size_t len, struct sockaddr_in *s_in, + int numeric, unsigned int netmask) +{ + struct hostent *ent; + struct netent *np; + struct addr *pn; + unsigned long ad, host_ad; + int host = 0; + + /* Grmpf. -FvK */ + if (s_in->sin_family != AF_INET) { +#ifdef DEBUG + bb_error_msg("rresolve: unsupport address family %d !", + s_in->sin_family); +#endif + errno = EAFNOSUPPORT; + return -1; + } + ad = (unsigned long) s_in->sin_addr.s_addr; +#ifdef DEBUG + bb_error_msg("rresolve: %08lx, mask %08x, num %08x", ad, netmask, numeric); +#endif + if (ad == INADDR_ANY) { + if ((numeric & 0x0FFF) == 0) { + if (numeric & 0x8000) + safe_strncpy(name, bb_str_default, len); + else + safe_strncpy(name, "*", len); + return 0; + } + } + if (numeric & 0x0FFF) { + safe_strncpy(name, inet_ntoa(s_in->sin_addr), len); + return 0; + } + + if ((ad & (~netmask)) != 0 || (numeric & 0x4000)) + host = 1; + pn = INET_nn; + while (pn != NULL) { + if (pn->addr.sin_addr.s_addr == ad && pn->host == host) { + safe_strncpy(name, pn->name, len); +#ifdef DEBUG + bb_error_msg("rresolve: found %s %08lx in cache", + (host ? "host" : "net"), ad); +#endif + return 0; + } + pn = pn->next; + } + + host_ad = ntohl(ad); + np = NULL; + ent = NULL; + if (host) { +#ifdef DEBUG + bb_error_msg("gethostbyaddr (%08lx)", ad); +#endif + ent = gethostbyaddr((char *) &ad, 4, AF_INET); + if (ent != NULL) { + safe_strncpy(name, ent->h_name, len); + } + } else { +#ifdef DEBUG + bb_error_msg("getnetbyaddr (%08lx)", host_ad); +#endif + np = getnetbyaddr(host_ad, AF_INET); + if (np != NULL) { + safe_strncpy(name, np->n_name, len); + } + } + if ((ent == NULL) && (np == NULL)) { + safe_strncpy(name, inet_ntoa(s_in->sin_addr), len); + } + pn = (struct addr *) xmalloc(sizeof(struct addr)); + pn->addr = *s_in; + pn->next = INET_nn; + pn->host = host; + pn->name = xstrdup(name); + INET_nn = pn; + + return 0; +} + +#ifdef CONFIG_FEATURE_IPV6 + +int INET6_resolve(const char *name, struct sockaddr_in6 *sin6) +{ + struct addrinfo req, *ai; + int s; + + memset(&req, '\0', sizeof req); + req.ai_family = AF_INET6; + s = getaddrinfo(name, NULL, &req, &ai); + if (s) { + bb_error_msg("getaddrinfo: %s: %d", name, s); + return -1; + } + memcpy(sin6, ai->ai_addr, sizeof(struct sockaddr_in6)); + + freeaddrinfo(ai); + + return 0; +} + +#ifndef IN6_IS_ADDR_UNSPECIFIED +# define IN6_IS_ADDR_UNSPECIFIED(a) \ + (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ + ((uint32_t *) (a))[2] == 0 && ((uint32_t *) (a))[3] == 0) +#endif + + +int INET6_rresolve(char *name, size_t len, struct sockaddr_in6 *sin6, + int numeric) +{ + int s; + + /* Grmpf. -FvK */ + if (sin6->sin6_family != AF_INET6) { +#ifdef DEBUG + bb_error_msg("rresolve: unsupport address family %d!", + sin6->sin6_family); +#endif + errno = EAFNOSUPPORT; + return -1; + } + if (numeric & 0x7FFF) { + inet_ntop(AF_INET6, &sin6->sin6_addr, name, len); + return 0; + } + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + if (numeric & 0x8000) { + strcpy(name, bb_str_default); + } else { + name[0] = '*'; + name[1] = '\0'; + } + return 0; + } + + s = getnameinfo((struct sockaddr *) sin6, sizeof(struct sockaddr_in6), name, len, NULL, 0, 0); + if (s) { + bb_error_msg("getnameinfo failed"); + return -1; + } + return 0; +} + +#endif /* CONFIG_FEATURE_IPV6 */ diff --git a/libbb/info_msg.c b/libbb/info_msg.c new file mode 100644 index 000000000..78d5c8f32 --- /dev/null +++ b/libbb/info_msg.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + +void bb_info_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_vinfo_msg(s, p); + va_end(p); +} diff --git a/libbb/inode_hash.c b/libbb/inode_hash.c new file mode 100644 index 000000000..2ac1623f4 --- /dev/null +++ b/libbb/inode_hash.c @@ -0,0 +1,88 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include "libbb.h" + +#define HASH_SIZE 311 /* Should be prime */ +#define hash_inode(i) ((i) % HASH_SIZE) + +typedef struct ino_dev_hash_bucket_struct { + struct ino_dev_hash_bucket_struct *next; + ino_t ino; + dev_t dev; + char name[1]; +} ino_dev_hashtable_bucket_t; + +static ino_dev_hashtable_bucket_t *ino_dev_hashtable[HASH_SIZE]; + +/* + * Return 1 if statbuf->st_ino && statbuf->st_dev are recorded in + * `ino_dev_hashtable', else return 0 + * + * If NAME is a non-NULL pointer to a character pointer, and there is + * a match, then set *NAME to the value of the name slot in that + * bucket. + */ +int is_in_ino_dev_hashtable(const struct stat *statbuf, char **name) +{ + ino_dev_hashtable_bucket_t *bucket; + + bucket = ino_dev_hashtable[hash_inode(statbuf->st_ino)]; + while (bucket != NULL) { + if ((bucket->ino == statbuf->st_ino) && + (bucket->dev == statbuf->st_dev)) + { + if (name) *name = bucket->name; + return 1; + } + bucket = bucket->next; + } + return 0; +} + +/* Add statbuf to statbuf hash table */ +void add_to_ino_dev_hashtable(const struct stat *statbuf, const char *name) +{ + int i; + size_t s; + ino_dev_hashtable_bucket_t *bucket; + + i = hash_inode(statbuf->st_ino); + s = name ? strlen(name) : 0; + bucket = xmalloc(sizeof(ino_dev_hashtable_bucket_t) + s); + bucket->ino = statbuf->st_ino; + bucket->dev = statbuf->st_dev; + if (name) + strcpy(bucket->name, name); + else + bucket->name[0] = '\0'; + bucket->next = ino_dev_hashtable[i]; + ino_dev_hashtable[i] = bucket; +} + +#ifdef CONFIG_FEATURE_CLEAN_UP +/* Clear statbuf hash table */ +void reset_ino_dev_hashtable(void) +{ + int i; + ino_dev_hashtable_bucket_t *bucket; + + for (i = 0; i < HASH_SIZE; i++) { + while (ino_dev_hashtable[i] != NULL) { + bucket = ino_dev_hashtable[i]->next; + free(ino_dev_hashtable[i]); + ino_dev_hashtable[i] = bucket; + } + } +} +#endif diff --git a/libbb/isdirectory.c b/libbb/isdirectory.c new file mode 100644 index 000000000..b35919869 --- /dev/null +++ b/libbb/isdirectory.c @@ -0,0 +1,39 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Based in part on code from sash, Copyright (c) 1999 by David I. Bell + * Permission has been granted to redistribute this code under the GPL. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +/* + * Return TRUE if a fileName is a directory. + * Nonexistent files return FALSE. + */ +int is_directory(const char *fileName, const int followLinks, struct stat *statBuf) +{ + int status; + struct stat astatBuf; + + if (statBuf == NULL) { + /* set from auto stack buffer */ + statBuf = &astatBuf; + } + + if (followLinks) + status = stat(fileName, statBuf); + else + status = lstat(fileName, statBuf); + + if (status < 0 || !(S_ISDIR(statBuf->st_mode))) { + status = FALSE; + } + else status = TRUE; + + return status; +} diff --git a/libbb/kernel_version.c b/libbb/kernel_version.c new file mode 100644 index 000000000..50b82ae18 --- /dev/null +++ b/libbb/kernel_version.c @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include /* for uname(2) */ + +#include "libbb.h" + +/* Returns current kernel version encoded as major*65536 + minor*256 + patch, + * so, for example, to check if the kernel is greater than 2.2.11: + * + * if (get_linux_version_code() > KERNEL_VERSION(2,2,11)) { } + */ +int get_linux_version_code(void) +{ + struct utsname name; + char *s; + int i, r; + + if (uname(&name) == -1) { + bb_perror_msg("cannot get system information"); + return 0; + } + + s = name.release; + r = 0; + for (i = 0; i < 3; i++) { + r = r * 256 + atoi(strtok(s, ".")); + s = NULL; + } + return r; +} diff --git a/libbb/last_char_is.c b/libbb/last_char_is.c new file mode 100644 index 000000000..3616d5916 --- /dev/null +++ b/libbb/last_char_is.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * busybox library eXtended function + * + * Copyright (C) 2001 Larry Doolittle, + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* Find out if the last character of a string matches the one given Don't + * underrun the buffer if the string length is 0. Also avoids a possible + * space-hogging inline of strlen() per usage. + */ +char* last_char_is(const char *s, int c) +{ + if (s) { + s = strrchr(s, c); + if (s && !s[1]) + return (char*)s; + } + return NULL; +} diff --git a/libbb/llist.c b/libbb/llist.c new file mode 100644 index 000000000..8a74832ee --- /dev/null +++ b/libbb/llist.c @@ -0,0 +1,78 @@ +/* vi: set sw=4 ts=4: */ +/* + * linked list helper functions. + * + * Copyright (C) 2003 Glenn McGrath + * Copyright (C) 2005 Vladimir Oleynik + * Copyright (C) 2005 Bernhard Fischer + * Copyright (C) 2006 Rob Landley + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include +#include "libbb.h" + +/* Add data to the start of the linked list. */ +void llist_add_to(llist_t **old_head, void *data) +{ + llist_t *new_head = xmalloc(sizeof(llist_t)); + new_head->data = data; + new_head->link = *old_head; + *old_head = new_head; +} + +/* Add data to the end of the linked list. */ +void llist_add_to_end(llist_t **list_head, void *data) +{ + llist_t *new_item = xmalloc(sizeof(llist_t)); + new_item->data = data; + new_item->link = NULL; + + if (!*list_head) *list_head = new_item; + else { + llist_t *tail = *list_head; + while (tail->link) tail = tail->link; + tail->link = new_item; + } +} + +/* Remove first element from the list and return it */ +void *llist_pop(llist_t **head) +{ + void *data; + + if(!*head) data = *head; + else { + void *next = (*head)->link; + data = (*head)->data; + free(*head); + *head = next; + } + + return data; +} + +/* Recursively free all elements in the linked list. If freeit != NULL + * call it on each datum in the list */ +void llist_free(llist_t *elm, void (*freeit)(void *data)) +{ + while (elm) { + void *data = llist_pop(&elm); + if (freeit) freeit(data); + } +} + +/* Reverse list order. Useful since getopt32 saves option params + * in reverse order */ +llist_t* rev_llist(llist_t *list) +{ + llist_t *new = NULL; + while (list) { + llist_t *next = list->link; + list->link = new; + new = list; + list = next; + } + return new; +} diff --git a/libbb/login.c b/libbb/login.c new file mode 100644 index 000000000..6ebb9a6a0 --- /dev/null +++ b/libbb/login.c @@ -0,0 +1,105 @@ +/* vi: set sw=4 ts=4: */ +/* + * issue.c: issue printing code + * + * Copyright (C) 2003 Bastian Blank + * + * Optimize and correcting OCRNL by Vladimir Oleynik + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include /* MAXHOSTNAMELEN */ +#include +#include +#include "libbb.h" + +#include +#include + +#define LOGIN " login: " + +static const char fmtstr_d[] = "%A, %d %B %Y"; +static const char fmtstr_t[] = "%H:%M:%S"; + +void print_login_issue(const char *issue_file, const char *tty) +{ + FILE *fd; + int c; + char buf[256+1]; + const char *outbuf; + time_t t; + struct utsname uts; + + time(&t); + uname(&uts); + + puts("\r"); /* start a new line */ + + fd = fopen(issue_file, "r"); + if (!fd) + return; + while ((c = fgetc(fd)) != EOF) { + outbuf = buf; + buf[0] = c; + buf[1] = '\0'; + if(c == '\n') { + buf[1] = '\r'; + buf[2] = '\0'; + } + if (c == '\\' || c == '%') { + c = fgetc(fd); + switch (c) { + case 's': + outbuf = uts.sysname; + break; + case 'n': + outbuf = uts.nodename; + break; + case 'r': + outbuf = uts.release; + break; + case 'v': + outbuf = uts.version; + break; + case 'm': + outbuf = uts.machine; + break; + case 'D': + case 'o': + c = getdomainname(buf, sizeof(buf) - 1); + buf[c >= 0 ? c : 0] = '\0'; + break; + case 'd': + strftime(buf, sizeof(buf), fmtstr_d, localtime(&t)); + break; + case 't': + strftime(buf, sizeof(buf), fmtstr_t, localtime(&t)); + break; + case 'h': + gethostname(buf, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + break; + case 'l': + outbuf = tty; + break; + default: + buf[0] = c; + } + } + fputs(outbuf, stdout); + } + fclose(fd); + fflush(stdout); +} + +void print_login_prompt(void) +{ + char buf[MAXHOSTNAMELEN+1]; + + if (gethostname(buf, MAXHOSTNAMELEN) == 0) + fputs(buf, stdout); + + fputs(LOGIN, stdout); + fflush(stdout); +} diff --git a/libbb/loop.c b/libbb/loop.c new file mode 100644 index 000000000..14835ec24 --- /dev/null +++ b/libbb/loop.c @@ -0,0 +1,149 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * Copyright (C) 2005 by Rob Landley + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +/* For 2.6, use the cleaned up header to get the 64 bit API. */ +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) +#include +typedef struct loop_info64 bb_loop_info; +#define BB_LOOP_SET_STATUS LOOP_SET_STATUS64 +#define BB_LOOP_GET_STATUS LOOP_GET_STATUS64 + +/* For 2.4 and earlier, use the 32 bit API (and don't trust the headers) */ +#else +/* Stuff stolen from linux/loop.h for 2.4 and earlier kernels*/ +#include +#define LO_NAME_SIZE 64 +#define LO_KEY_SIZE 32 +#define LOOP_SET_FD 0x4C00 +#define LOOP_CLR_FD 0x4C01 +#define BB_LOOP_SET_STATUS 0x4C02 +#define BB_LOOP_GET_STATUS 0x4C03 +typedef struct { + int lo_number; + __kernel_dev_t lo_device; + unsigned long lo_inode; + __kernel_dev_t lo_rdevice; + int lo_offset; + int lo_encrypt_type; + int lo_encrypt_key_size; + int lo_flags; + char lo_file_name[LO_NAME_SIZE]; + unsigned char lo_encrypt_key[LO_KEY_SIZE]; + unsigned long lo_init[2]; + char reserved[4]; +} bb_loop_info; +#endif + +char *query_loop(const char *device) +{ + int fd; + bb_loop_info loopinfo; + char *dev = 0; + + fd = open(device, O_RDONLY); + if (fd < 0) return 0; + if (!ioctl(fd, BB_LOOP_GET_STATUS, &loopinfo)) + dev = xasprintf("%ld %s", (long) loopinfo.lo_offset, + (char *)loopinfo.lo_file_name); + close(fd); + + return dev; +} + + +int del_loop(const char *device) +{ + int fd, rc; + + fd = open(device, O_RDONLY); + if (fd < 0) return 1; + rc = ioctl(fd, LOOP_CLR_FD, 0); + close(fd); + + return rc; +} + +/* Returns 0 if mounted RW, 1 if mounted read-only, <0 for error. + *device is loop device to use, or if *device==NULL finds a loop device to + mount it on and sets *device to a strdup of that loop device name. This + search will re-use an existing loop device already bound to that + file/offset if it finds one. + */ +int set_loop(char **device, const char *file, unsigned long long offset) +{ + char dev[20], *try; + bb_loop_info loopinfo; + struct stat statbuf; + int i, dfd, ffd, mode, rc=-1; + + /* Open the file. Barf if this doesn't work. */ + mode = O_RDWR; + ffd = open(file, mode); + if (ffd < 0) { + mode = O_RDONLY; + ffd = open(file, mode); + if (ffd < 0) + return -errno; + } + + /* Find a loop device. */ + try = *device ? : dev; + for (i=0;rc;i++) { + sprintf(dev, LOOP_FORMAT, i); + + /* Ran out of block devices, return failure. */ + if (stat(try, &statbuf) || !S_ISBLK(statbuf.st_mode)) { + rc=-ENOENT; + break; + } + /* Open the sucker and check its loopiness. */ + dfd = open(try, mode); + if (dfd < 0 && errno == EROFS) { + mode = O_RDONLY; + dfd = open(try, mode); + } + if (dfd < 0) goto try_again; + + rc = ioctl(dfd, BB_LOOP_GET_STATUS, &loopinfo); + + /* If device free, claim it. */ + if (rc && errno == ENXIO) { + memset(&loopinfo, 0, sizeof(loopinfo)); + safe_strncpy((char *)loopinfo.lo_file_name, file, LO_NAME_SIZE); + loopinfo.lo_offset = offset; + /* Associate free loop device with file. */ + if (!ioctl(dfd, LOOP_SET_FD, ffd)) { + if (!ioctl(dfd, BB_LOOP_SET_STATUS, &loopinfo)) rc = 0; + else ioctl(dfd, LOOP_CLR_FD, 0); + } + + /* If this block device already set up right, re-use it. + (Yes this is racy, but associating two loop devices with the same + file isn't pretty either. In general, mounting the same file twice + without using losetup manually is problematic.) + */ + } else if (strcmp(file,(char *)loopinfo.lo_file_name) + || offset != loopinfo.lo_offset) { + rc = -1; + } + close(dfd); +try_again: + if (*device) break; + } + close(ffd); + if (!rc) { + if (!*device) *device = strdup(dev); + return mode==O_RDONLY ? 1 : 0; + } + return rc; +} diff --git a/libbb/make_directory.c b/libbb/make_directory.c new file mode 100644 index 000000000..fbec4e20e --- /dev/null +++ b/libbb/make_directory.c @@ -0,0 +1,104 @@ +/* vi: set sw=4 ts=4: */ +/* + * parse_mode implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Mar 5, 2003 Manuel Novoa III + * + * This is the main work function for the 'mkdir' applet. As such, it + * strives to be SUSv3 compliant in it's behaviour when recursively + * making missing parent dirs, and in it's mode setting of the final + * directory 'path'. + * + * To recursively build all missing intermediate directories, make + * sure that (flags & FILEUTILS_RECUR) is non-zero. Newly created + * intermediate directories will have at least u+wx perms. + * + * To set specific permissions on 'path', pass the appropriate 'mode' + * val. Otherwise, pass -1 to get default permissions. + */ + +#include +#include +#include +#include "libbb.h" + +int bb_make_directory (char *path, long mode, int flags) +{ + mode_t mask; + const char *fail_msg; + char *s = path; + char c; + struct stat st; + + mask = umask(0); + if (mode == -1) { + umask(mask); + mode = (S_IXUSR | S_IXGRP | S_IXOTH | + S_IWUSR | S_IWGRP | S_IWOTH | + S_IRUSR | S_IRGRP | S_IROTH) & ~mask; + } else { + umask(mask & ~0300); + } + + do { + c = 0; + + if (flags & FILEUTILS_RECUR) { /* Get the parent. */ + /* Bypass leading non-'/'s and then subsequent '/'s. */ + while (*s) { + if (*s == '/') { + do { + ++s; + } while (*s == '/'); + c = *s; /* Save the current char */ + *s = 0; /* and replace it with nul. */ + break; + } + ++s; + } + } + + if (mkdir(path, 0777) < 0) { + /* If we failed for any other reason than the directory + * already exists, output a diagnostic and return -1.*/ + if (errno != EEXIST + || !(flags & FILEUTILS_RECUR) + || (stat(path, &st) < 0 || !S_ISDIR(st.st_mode))) { + fail_msg = "create"; + umask(mask); + break; + } + /* Since the directory exists, don't attempt to change + * permissions if it was the full target. Note that + * this is not an error conditon. */ + if (!c) { + umask(mask); + return 0; + } + } + + if (!c) { + /* Done. If necessary, updated perms on the newly + * created directory. Failure to update here _is_ + * an error.*/ + umask(mask); + if ((mode != -1) && (chmod(path, mode) < 0)){ + fail_msg = "set permissions of"; + break; + } + return 0; + } + + /* Remove any inserted nul from the path (recursive mode). */ + *s = c; + + } while (1); + + bb_perror_msg ("cannot %s directory '%s'", fail_msg, path); + return -1; +} diff --git a/libbb/makedev.c b/libbb/makedev.c new file mode 100644 index 000000000..4903e4783 --- /dev/null +++ b/libbb/makedev.c @@ -0,0 +1,20 @@ +/* + * Utility routines. + * + * Copyright (C) 2006 Denis Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +/* We do not include libbb.h - #define makedev() is there! */ +#include +#include + +#ifdef __GLIBC__ +/* At least glibc has horrendously large inline for this, so wrap it */ +/* uclibc people please check - do we need "&& !__UCLIBC__" above? */ +unsigned long long bb_makedev(unsigned int major, unsigned int minor) +{ + return makedev(major, minor); +} +#endif diff --git a/libbb/md5.c b/libbb/md5.c new file mode 100644 index 000000000..e672559cf --- /dev/null +++ b/libbb/md5.c @@ -0,0 +1,450 @@ +/* vi: set sw=4 ts=4: */ +/* + * md5.c - Compute MD5 checksum of strings according to the + * definition of MD5 in RFC 1321 from April 1992. + * + * Written by Ulrich Drepper , 1995. + * + * Copyright (C) 1995-1999 Free Software Foundation, Inc. + * Copyright (C) 2001 Manuel Novoa III + * Copyright (C) 2003 Glenn L. McGrath + * Copyright (C) 2003 Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +#if CONFIG_MD5_SIZE_VS_SPEED < 0 || CONFIG_MD5_SIZE_VS_SPEED > 3 +# define MD5_SIZE_VS_SPEED 2 +#else +# define MD5_SIZE_VS_SPEED CONFIG_MD5_SIZE_VS_SPEED +#endif + +/* Initialize structure containing state of computation. + * (RFC 1321, 3.3: Step 3) + */ +void md5_begin(md5_ctx_t *ctx) +{ + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + + ctx->total = 0; + ctx->buflen = 0; +} + +/* These are the four functions used in the four steps of the MD5 algorithm + * and defined in the RFC 1321. The first function is a little bit optimized + * (as found in Colin Plumbs public domain implementation). + * #define FF(b, c, d) ((b & c) | (~b & d)) + */ +# define FF(b, c, d) (d ^ (b & (c ^ d))) +# define FG(b, c, d) FF (d, b, c) +# define FH(b, c, d) (b ^ c ^ d) +# define FI(b, c, d) (c ^ (b | ~d)) + +/* Hash a single block, 64 bytes long and 4-byte aligned. */ +static void md5_hash_block(const void *buffer, md5_ctx_t *ctx) +{ + uint32_t correct_words[16]; + const uint32_t *words = buffer; + +# if MD5_SIZE_VS_SPEED > 0 + static const uint32_t C_array[] = { + /* round 1 */ + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + /* round 2 */ + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + /* round 3 */ + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + /* round 4 */ + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + static const char P_array[] = { +# if MD5_SIZE_VS_SPEED > 1 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 1 */ +# endif /* MD5_SIZE_VS_SPEED > 1 */ + 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, /* 2 */ + 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, /* 3 */ + 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 /* 4 */ + }; + +# if MD5_SIZE_VS_SPEED > 1 + static const char S_array[] = { + 7, 12, 17, 22, + 5, 9, 14, 20, + 4, 11, 16, 23, + 6, 10, 15, 21 + }; +# endif /* MD5_SIZE_VS_SPEED > 1 */ +# endif + + uint32_t A = ctx->A; + uint32_t B = ctx->B; + uint32_t C = ctx->C; + uint32_t D = ctx->D; + + /* Process all bytes in the buffer with 64 bytes in each round of + the loop. */ + uint32_t *cwp = correct_words; + uint32_t A_save = A; + uint32_t B_save = B; + uint32_t C_save = C; + uint32_t D_save = D; + +# if MD5_SIZE_VS_SPEED > 1 +# define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) + + const uint32_t *pc; + const char *pp; + const char *ps; + int i; + uint32_t temp; + + for (i = 0; i < 16; i++) { + cwp[i] = SWAP_LE32(words[i]); + } + words += 16; + +# if MD5_SIZE_VS_SPEED > 2 + pc = C_array; + pp = P_array; + ps = S_array - 4; + + for (i = 0; i < 64; i++) { + if ((i & 0x0f) == 0) + ps += 4; + temp = A; + switch (i >> 4) { + case 0: + temp += FF(B, C, D); + break; + case 1: + temp += FG(B, C, D); + break; + case 2: + temp += FH(B, C, D); + break; + case 3: + temp += FI(B, C, D); + } + temp += cwp[(int) (*pp++)] + *pc++; + CYCLIC(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } +# else + pc = C_array; + pp = P_array; + ps = S_array; + + for (i = 0; i < 16; i++) { + temp = A + FF(B, C, D) + cwp[(int) (*pp++)] + *pc++; + CYCLIC(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + + ps += 4; + for (i = 0; i < 16; i++) { + temp = A + FG(B, C, D) + cwp[(int) (*pp++)] + *pc++; + CYCLIC(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + ps += 4; + for (i = 0; i < 16; i++) { + temp = A + FH(B, C, D) + cwp[(int) (*pp++)] + *pc++; + CYCLIC(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + ps += 4; + for (i = 0; i < 16; i++) { + temp = A + FI(B, C, D) + cwp[(int) (*pp++)] + *pc++; + CYCLIC(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + +# endif /* MD5_SIZE_VS_SPEED > 2 */ +# else + /* First round: using the given function, the context and a constant + the next context is computed. Because the algorithms processing + unit is a 32-bit word and it is determined to work on words in + little endian byte order we perhaps have to change the byte order + before the computation. To reduce the work for the next steps + we store the swapped words in the array CORRECT_WORDS. */ + +# define OP(a, b, c, d, s, T) \ + do \ + { \ + a += FF (b, c, d) + (*cwp++ = SWAP_LE32(*words)) + T; \ + ++words; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* It is unfortunate that C does not provide an operator for + cyclic rotation. Hope the C compiler is smart enough. */ + /* gcc 2.95.4 seems to be --aaronl */ +# define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) + + /* Before we start, one word to the strange constants. + They are defined in RFC 1321 as + + T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 + */ + +# if MD5_SIZE_VS_SPEED == 1 + const uint32_t *pc; + const char *pp; + int i; +# endif /* MD5_SIZE_VS_SPEED */ + + /* Round 1. */ +# if MD5_SIZE_VS_SPEED == 1 + pc = C_array; + for (i = 0; i < 4; i++) { + OP(A, B, C, D, 7, *pc++); + OP(D, A, B, C, 12, *pc++); + OP(C, D, A, B, 17, *pc++); + OP(B, C, D, A, 22, *pc++); + } +# else + OP(A, B, C, D, 7, 0xd76aa478); + OP(D, A, B, C, 12, 0xe8c7b756); + OP(C, D, A, B, 17, 0x242070db); + OP(B, C, D, A, 22, 0xc1bdceee); + OP(A, B, C, D, 7, 0xf57c0faf); + OP(D, A, B, C, 12, 0x4787c62a); + OP(C, D, A, B, 17, 0xa8304613); + OP(B, C, D, A, 22, 0xfd469501); + OP(A, B, C, D, 7, 0x698098d8); + OP(D, A, B, C, 12, 0x8b44f7af); + OP(C, D, A, B, 17, 0xffff5bb1); + OP(B, C, D, A, 22, 0x895cd7be); + OP(A, B, C, D, 7, 0x6b901122); + OP(D, A, B, C, 12, 0xfd987193); + OP(C, D, A, B, 17, 0xa679438e); + OP(B, C, D, A, 22, 0x49b40821); +# endif /* MD5_SIZE_VS_SPEED == 1 */ + + /* For the second to fourth round we have the possibly swapped words + in CORRECT_WORDS. Redefine the macro to take an additional first + argument specifying the function to use. */ +# undef OP +# define OP(f, a, b, c, d, k, s, T) \ + do \ + { \ + a += f (b, c, d) + correct_words[k] + T; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* Round 2. */ +# if MD5_SIZE_VS_SPEED == 1 + pp = P_array; + for (i = 0; i < 4; i++) { + OP(FG, A, B, C, D, (int) (*pp++), 5, *pc++); + OP(FG, D, A, B, C, (int) (*pp++), 9, *pc++); + OP(FG, C, D, A, B, (int) (*pp++), 14, *pc++); + OP(FG, B, C, D, A, (int) (*pp++), 20, *pc++); + } +# else + OP(FG, A, B, C, D, 1, 5, 0xf61e2562); + OP(FG, D, A, B, C, 6, 9, 0xc040b340); + OP(FG, C, D, A, B, 11, 14, 0x265e5a51); + OP(FG, B, C, D, A, 0, 20, 0xe9b6c7aa); + OP(FG, A, B, C, D, 5, 5, 0xd62f105d); + OP(FG, D, A, B, C, 10, 9, 0x02441453); + OP(FG, C, D, A, B, 15, 14, 0xd8a1e681); + OP(FG, B, C, D, A, 4, 20, 0xe7d3fbc8); + OP(FG, A, B, C, D, 9, 5, 0x21e1cde6); + OP(FG, D, A, B, C, 14, 9, 0xc33707d6); + OP(FG, C, D, A, B, 3, 14, 0xf4d50d87); + OP(FG, B, C, D, A, 8, 20, 0x455a14ed); + OP(FG, A, B, C, D, 13, 5, 0xa9e3e905); + OP(FG, D, A, B, C, 2, 9, 0xfcefa3f8); + OP(FG, C, D, A, B, 7, 14, 0x676f02d9); + OP(FG, B, C, D, A, 12, 20, 0x8d2a4c8a); +# endif /* MD5_SIZE_VS_SPEED == 1 */ + + /* Round 3. */ +# if MD5_SIZE_VS_SPEED == 1 + for (i = 0; i < 4; i++) { + OP(FH, A, B, C, D, (int) (*pp++), 4, *pc++); + OP(FH, D, A, B, C, (int) (*pp++), 11, *pc++); + OP(FH, C, D, A, B, (int) (*pp++), 16, *pc++); + OP(FH, B, C, D, A, (int) (*pp++), 23, *pc++); + } +# else + OP(FH, A, B, C, D, 5, 4, 0xfffa3942); + OP(FH, D, A, B, C, 8, 11, 0x8771f681); + OP(FH, C, D, A, B, 11, 16, 0x6d9d6122); + OP(FH, B, C, D, A, 14, 23, 0xfde5380c); + OP(FH, A, B, C, D, 1, 4, 0xa4beea44); + OP(FH, D, A, B, C, 4, 11, 0x4bdecfa9); + OP(FH, C, D, A, B, 7, 16, 0xf6bb4b60); + OP(FH, B, C, D, A, 10, 23, 0xbebfbc70); + OP(FH, A, B, C, D, 13, 4, 0x289b7ec6); + OP(FH, D, A, B, C, 0, 11, 0xeaa127fa); + OP(FH, C, D, A, B, 3, 16, 0xd4ef3085); + OP(FH, B, C, D, A, 6, 23, 0x04881d05); + OP(FH, A, B, C, D, 9, 4, 0xd9d4d039); + OP(FH, D, A, B, C, 12, 11, 0xe6db99e5); + OP(FH, C, D, A, B, 15, 16, 0x1fa27cf8); + OP(FH, B, C, D, A, 2, 23, 0xc4ac5665); +# endif /* MD5_SIZE_VS_SPEED == 1 */ + + /* Round 4. */ +# if MD5_SIZE_VS_SPEED == 1 + for (i = 0; i < 4; i++) { + OP(FI, A, B, C, D, (int) (*pp++), 6, *pc++); + OP(FI, D, A, B, C, (int) (*pp++), 10, *pc++); + OP(FI, C, D, A, B, (int) (*pp++), 15, *pc++); + OP(FI, B, C, D, A, (int) (*pp++), 21, *pc++); + } +# else + OP(FI, A, B, C, D, 0, 6, 0xf4292244); + OP(FI, D, A, B, C, 7, 10, 0x432aff97); + OP(FI, C, D, A, B, 14, 15, 0xab9423a7); + OP(FI, B, C, D, A, 5, 21, 0xfc93a039); + OP(FI, A, B, C, D, 12, 6, 0x655b59c3); + OP(FI, D, A, B, C, 3, 10, 0x8f0ccc92); + OP(FI, C, D, A, B, 10, 15, 0xffeff47d); + OP(FI, B, C, D, A, 1, 21, 0x85845dd1); + OP(FI, A, B, C, D, 8, 6, 0x6fa87e4f); + OP(FI, D, A, B, C, 15, 10, 0xfe2ce6e0); + OP(FI, C, D, A, B, 6, 15, 0xa3014314); + OP(FI, B, C, D, A, 13, 21, 0x4e0811a1); + OP(FI, A, B, C, D, 4, 6, 0xf7537e82); + OP(FI, D, A, B, C, 11, 10, 0xbd3af235); + OP(FI, C, D, A, B, 2, 15, 0x2ad7d2bb); + OP(FI, B, C, D, A, 9, 21, 0xeb86d391); +# endif /* MD5_SIZE_VS_SPEED == 1 */ +# endif /* MD5_SIZE_VS_SPEED > 1 */ + + /* Add the starting values of the context. */ + A += A_save; + B += B_save; + C += C_save; + D += D_save; + + /* Put checksum in context given as argument. */ + ctx->A = A; + ctx->B = B; + ctx->C = C; + ctx->D = D; +} + +/* Feed data through a temporary buffer to call md5_hash_aligned_block() + * with chunks of data that are 4-byte aligned and a multiple of 64 bytes. + * This function's internal buffer remembers previous data until it has 64 + * bytes worth to pass on. Call md5_end() to flush this buffer. */ + +void md5_hash(const void *buffer, size_t len, md5_ctx_t *ctx) +{ + char *buf=(char *)buffer; + + /* RFC 1321 specifies the possible length of the file up to 2^64 bits, + * Here we only track the number of bytes. */ + + ctx->total += len; + + // Process all input. + + while (len) { + int i = 64 - ctx->buflen; + + // Copy data into aligned buffer. + + if (i > len) i = len; + memcpy(ctx->buffer + ctx->buflen, buf, i); + len -= i; + ctx->buflen += i; + buf += i; + + // When buffer fills up, process it. + + if (ctx->buflen == 64) { + md5_hash_block(ctx->buffer, ctx); + ctx->buflen = 0; + } + } +} + +/* Process the remaining bytes in the buffer and put result from CTX + * in first 16 bytes following RESBUF. The result is always in little + * endian byte order, so that a byte-wise output yields to the wanted + * ASCII representation of the message digest. + * + * IMPORTANT: On some systems it is required that RESBUF is correctly + * aligned for a 32 bits value. + */ +void *md5_end(void *resbuf, md5_ctx_t *ctx) +{ + char *buf = ctx->buffer; + int i; + + /* Pad data to block size. */ + + buf[ctx->buflen++] = 0x80; + memset(buf + ctx->buflen, 0, 128 - ctx->buflen); + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + ctx->total <<= 3; + if (ctx->buflen > 56) buf += 64; + for (i = 0; i < 8; i++) buf[56 + i] = ctx->total >> (i*8); + + /* Process last bytes. */ + if (buf != ctx->buffer) md5_hash_block(ctx->buffer, ctx); + md5_hash_block(buf, ctx); + + /* Put result from CTX in first 16 bytes following RESBUF. The result is + * always in little endian byte order, so that a byte-wise output yields + * to the wanted ASCII representation of the message digest. + * + * IMPORTANT: On some systems it is required that RESBUF is correctly + * aligned for a 32 bits value. + */ + ((uint32_t *) resbuf)[0] = SWAP_LE32(ctx->A); + ((uint32_t *) resbuf)[1] = SWAP_LE32(ctx->B); + ((uint32_t *) resbuf)[2] = SWAP_LE32(ctx->C); + ((uint32_t *) resbuf)[3] = SWAP_LE32(ctx->D); + + return resbuf; +} + diff --git a/libbb/messages.c b/libbb/messages.c new file mode 100644 index 000000000..c640faf5b --- /dev/null +++ b/libbb/messages.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +#ifndef BB_EXTRA_VERSION +#define BANNER "BusyBox v" BB_VER " (" BB_BT ")" +#else +#define BANNER "BusyBox v" BB_VER " (" BB_EXTRA_VERSION ")" +#endif +const char BB_BANNER[] = BANNER; +const char bb_msg_full_version[] = BANNER " multi-call binary"; + +const char bb_msg_memory_exhausted[] = "memory exhausted"; +const char bb_msg_invalid_date[] = "invalid date '%s'"; +const char bb_msg_write_error[] = "write error"; +const char bb_msg_read_error[] = "read error"; +const char bb_msg_unknown[] = "(unknown)"; +const char bb_msg_can_not_create_raw_socket[] = "can't create raw socket"; +const char bb_msg_perm_denied_are_you_root[] = "permission denied. (are you root?)"; +const char bb_msg_requires_arg[] = "%s requires an argument"; +const char bb_msg_invalid_arg[] = "invalid argument '%s' to '%s'"; +const char bb_msg_standard_input[] = "standard input"; +const char bb_msg_standard_output[] = "standard output"; + +const char bb_str_default[] = "default"; + +const char bb_path_passwd_file[] = "/etc/passwd"; +const char bb_path_shadow_file[] = "/etc/shadow"; +const char bb_path_group_file[] = "/etc/group"; +const char bb_path_gshadow_file[] = "/etc/gshadow"; +const char bb_path_nologin_file[] = "/etc/nologin"; +const char bb_path_securetty_file[] = "/etc/securetty"; +const char bb_path_motd_file[] = "/etc/motd"; +const char bb_default_login_shell[] = LIBBB_DEFAULT_LOGIN_SHELL; +const char bb_dev_null[] = "/dev/null"; + +#include +/* This is usually something like "/var/adm/wtmp" or "/var/log/wtmp" */ +const char bb_path_wtmp_file[] = +#if defined _PATH_WTMP +_PATH_WTMP; +#elif defined WTMP_FILE +WTMP_FILE; +#else +# error unknown path to wtmp file +#endif + +char bb_common_bufsiz1[BUFSIZ+1]; diff --git a/libbb/mode_string.c b/libbb/mode_string.c new file mode 100644 index 000000000..01029bfee --- /dev/null +++ b/libbb/mode_string.c @@ -0,0 +1,128 @@ +/* vi: set sw=4 ts=4: */ +/* + * mode_string implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Aug 13, 2003 + * Fix a bug reported by junkio@cox.net involving the mode_chars index. + */ + + +#include +#include + +#include "libbb.h" + +#if ( S_ISUID != 04000 ) || ( S_ISGID != 02000 ) || ( S_ISVTX != 01000 ) \ + || ( S_IRUSR != 00400 ) || ( S_IWUSR != 00200 ) || ( S_IXUSR != 00100 ) \ + || ( S_IRGRP != 00040 ) || ( S_IWGRP != 00020 ) || ( S_IXGRP != 00010 ) \ + || ( S_IROTH != 00004 ) || ( S_IWOTH != 00002 ) || ( S_IXOTH != 00001 ) +#error permission bitflag value assumption(s) violated! +#endif + +#if ( S_IFSOCK!= 0140000 ) || ( S_IFLNK != 0120000 ) \ + || ( S_IFREG != 0100000 ) || ( S_IFBLK != 0060000 ) \ + || ( S_IFDIR != 0040000 ) || ( S_IFCHR != 0020000 ) \ + || ( S_IFIFO != 0010000 ) +#warning mode type bitflag value assumption(s) violated! falling back to larger version + +#if (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX) == 07777 +#undef mode_t +#define mode_t unsigned short +#endif + +static const mode_t mode_flags[] = { + S_IRUSR, S_IWUSR, S_IXUSR, S_ISUID, + S_IRGRP, S_IWGRP, S_IXGRP, S_ISGID, + S_IROTH, S_IWOTH, S_IXOTH, S_ISVTX +}; + +/* The static const char arrays below are duplicated for the two cases + * because moving them ahead of the mode_flags declaration cause a text + * size increase with the gcc version I'm using. */ + +/* The previous version used "0pcCd?bB-?l?s???". However, the '0', 'C', + * and 'B' types don't appear to be available on linux. So I removed them. */ +static const char type_chars[16] = "?pc?d?b?-?l?s???"; +/* 0123456789abcdef */ +static const char mode_chars[7] = "rwxSTst"; + +const char *bb_mode_string(int mode) +{ + static char buf[12]; + char *p = buf; + + int i, j, k; + + *p = type_chars[ (mode >> 12) & 0xf ]; + i = 0; + do { + j = k = 0; + do { + *++p = '-'; + if (mode & mode_flags[i+j]) { + *p = mode_chars[j]; + k = j; + } + } while (++j < 3); + if (mode & mode_flags[i+j]) { + *p = mode_chars[3 + (k & 2) + ((i&8) >> 3)]; + } + i += 4; + } while (i < 12); + + /* Note: We don't bother with nul termination because bss initialization + * should have taken care of that for us. If the user scribbled in buf + * memory, they deserve whatever happens. But we'll at least assert. */ + assert(buf[10] == 0); + + return buf; +} + +#else + +/* The previous version used "0pcCd?bB-?l?s???". However, the '0', 'C', + * and 'B' types don't appear to be available on linux. So I removed them. */ +static const char type_chars[16] = "?pc?d?b?-?l?s???"; +/* 0123456789abcdef */ +static const char mode_chars[7] = "rwxSTst"; + +const char *bb_mode_string(int mode) +{ + static char buf[12]; + char *p = buf; + + int i, j, k, m; + + *p = type_chars[ (mode >> 12) & 0xf ]; + i = 0; + m = 0400; + do { + j = k = 0; + do { + *++p = '-'; + if (mode & m) { + *p = mode_chars[j]; + k = j; + } + m >>= 1; + } while (++j < 3); + ++i; + if (mode & (010000 >> i)) { + *p = mode_chars[3 + (k & 2) + (i == 3)]; + } + } while (i < 3); + + /* Note: We don't bother with nul termination because bss initialization + * should have taken care of that for us. If the user scribbled in buf + * memory, they deserve whatever happens. But we'll at least assert. */ + assert(buf[10] == 0); + + return buf; +} + +#endif diff --git a/libbb/mtab.c b/libbb/mtab.c new file mode 100644 index 000000000..18386efb5 --- /dev/null +++ b/libbb/mtab.c @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +#if ENABLE_FEATURE_MTAB_SUPPORT +void erase_mtab(const char *name) +{ + struct mntent *entries = NULL; + int i, count = 0; + FILE *mountTable; + struct mntent *m; + + mountTable = setmntent(bb_path_mtab_file, "r"); + /* Bummer. Fall back on trying the /proc filesystem */ + if (!mountTable) mountTable = setmntent("/proc/mounts", "r"); + if (!mountTable) { + bb_perror_msg(bb_path_mtab_file); + return; + } + + while ((m = getmntent(mountTable)) != 0) { + i = count++; + entries = xrealloc(entries, count * sizeof(entries[0])); + entries[i].mnt_fsname = xstrdup(m->mnt_fsname); + entries[i].mnt_dir = xstrdup(m->mnt_dir); + entries[i].mnt_type = xstrdup(m->mnt_type); + entries[i].mnt_opts = xstrdup(m->mnt_opts); + entries[i].mnt_freq = m->mnt_freq; + entries[i].mnt_passno = m->mnt_passno; + } + endmntent(mountTable); + + mountTable = setmntent(bb_path_mtab_file, "w"); + if (mountTable) { + for (i = 0; i < count; i++) { + if (strcmp(entries[i].mnt_fsname, name) != 0 + && strcmp(entries[i].mnt_dir, name) != 0) + addmntent(mountTable, &entries[i]); + } + endmntent(mountTable); + } else if (errno != EROFS) + bb_perror_msg(bb_path_mtab_file); +} +#endif diff --git a/libbb/mtab_file.c b/libbb/mtab_file.c new file mode 100644 index 000000000..67367e3d7 --- /dev/null +++ b/libbb/mtab_file.c @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + + +/* Busybox mount uses either /proc/mounts or /etc/mtab to + * get the list of currently mounted filesystems */ +const char bb_path_mtab_file[] = +USE_FEATURE_MTAB_SUPPORT("/etc/mtab")SKIP_FEATURE_MTAB_SUPPORT("/proc/mounts"); diff --git a/libbb/obscure.c b/libbb/obscure.c new file mode 100644 index 000000000..2599095df --- /dev/null +++ b/libbb/obscure.c @@ -0,0 +1,170 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini weak password checker implementation for busybox + * + * Copyright (C) 2006 Tito Ragusa + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* A good password: + 1) should contain at least six characters (man passwd); + 2) empty passwords are not permitted; + 3) should contain a mix of four different types of characters + upper case letters, + lower case letters, + numbers, + special characters such as !@#$%^&*,;". + This password types should not be permitted: + a) pure numbers: birthdates, social security number, license plate, phone numbers; + b) words and all letters only passwords (uppercase, lowercase or mixed) + as palindromes, consecutive or repetitive letters + or adjacent letters on your keyboard; + c) username, real name, company name or (e-mail?) address + in any form (as-is, reversed, capitalized, doubled, etc.). + (we can check only against username, gecos and hostname) + d) common and obvious letter-number replacements + (e.g. replace the letter O with number 0) + such as "M1cr0$0ft" or "P@ssw0rd" (CAVEAT: we cannot check for them + without the use of a dictionary). + + For each missing type of characters an increase of password length is + requested. + + If user is root we warn only. + + CAVEAT: some older versions of crypt() truncates passwords to 8 chars, + so that aaaaaaaa1Q$ is equal to aaaaaaaa making it possible to fool + some of our checks. We don't test for this special case as newer versions + of crypt do not truncate passwords. +*/ + +#include "libbb.h" + +static int string_checker_helper(const char *p1, const char *p2) __attribute__ ((__pure__)); + +static int string_checker_helper(const char *p1, const char *p2) +{ + /* as-is or capitalized */ + if (strcasecmp(p1, p2) == 0 + /* as sub-string */ + || strcasestr(p2, p1) != NULL + /* invert in case haystack is shorter than needle */ + || strcasestr(p1, p2) != NULL) + return 1; + return 0; +} + +static int string_checker(const char *p1, const char *p2) +{ + int size; + /* check string */ + int ret = string_checker_helper(p1, p2); + /* Make our own copy */ + char *p = xstrdup(p1); + /* reverse string */ + size = strlen(p); + + while (size--) { + *p = p1[size]; + p++; + } + /* restore pointer */ + p -= strlen(p1); + /* check reversed string */ + ret |= string_checker_helper(p, p2); + /* clean up */ + memset(p, 0, strlen(p1)); + free(p); + return ret; +} + +#define LOWERCASE 1 +#define UPPERCASE 2 +#define NUMBERS 4 +#define SPECIAL 8 + +static const char *obscure_msg(const char *old_p, const char *new_p, const struct passwd *pw) +{ + int i; + int c; + int length; + int mixed = 0; + /* Add 2 for each type of characters to the minlen of password */ + int size = CONFIG_PASSWORD_MINLEN + 8; + const char *p; + char hostname[255]; + + /* size */ + if (!new_p || (length = strlen(new_p)) < CONFIG_PASSWORD_MINLEN) + return "too short"; + + /* no username as-is, as sub-string, reversed, capitalized, doubled */ + if (string_checker(new_p, pw->pw_name)) { + return "similar to username"; + } + /* no gecos as-is, as sub-string, reversed, capitalized, doubled */ + if (*pw->pw_gecos && string_checker(new_p, pw->pw_gecos)) { + return "similar to gecos"; + } + /* hostname as-is, as sub-string, reversed, capitalized, doubled */ + if (gethostname(hostname, 255) == 0) { + hostname[254] = '\0'; + if (string_checker(new_p, hostname)) { + return "similar to hostname"; + } + } + + /* Should / Must contain a mix of: */ + for (i = 0; i < length; i++) { + if (islower(new_p[i])) { /* a-z */ + mixed |= LOWERCASE; + } else if (isupper(new_p[i])) { /* A-Z */ + mixed |= UPPERCASE; + } else if (isdigit(new_p[i])) { /* 0-9 */ + mixed |= NUMBERS; + } else { /* special characters */ + mixed |= SPECIAL; + } + /* More than 50% similar characters ? */ + c = 0; + p = new_p; + while (1) { + if ((p = strchr(p, new_p[i])) == NULL) { + break; + } + c++; + if (!++p) { + break; /* move past the matched char if possible */ + } + } + + if (c >= (length / 2)) { + return "too many similar characters"; + } + } + for (i=0; i<4; i++) + if (mixed & (1< + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* http://www.opengroup.org/onlinepubs/007904975/utilities/chmod.html */ + +#include +#include +#include +#include "libbb.h" + +#define FILEMODEBITS (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) + +int bb_parse_mode(const char *s, mode_t *current_mode) +{ + static const mode_t who_mask[] = { + S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO, /* a */ + S_ISUID | S_IRWXU, /* u */ + S_ISGID | S_IRWXG, /* g */ + S_IRWXO /* o */ + }; + + static const mode_t perm_mask[] = { + S_IRUSR | S_IRGRP | S_IROTH, /* r */ + S_IWUSR | S_IWGRP | S_IWOTH, /* w */ + S_IXUSR | S_IXGRP | S_IXOTH, /* x */ + S_IXUSR | S_IXGRP | S_IXOTH, /* X -- special -- see below */ + S_ISUID | S_ISGID, /* s */ + S_ISVTX /* t */ + }; + + static const char who_chars[] = "augo"; + static const char perm_chars[] = "rwxXst"; + + const char *p; + + mode_t wholist; + mode_t permlist; + mode_t mask; + mode_t new_mode; + char op; + + assert(s); + + if (((unsigned int)(*s - '0')) < 8) { + unsigned long tmp; + char *e; + + tmp = strtol(s, &e, 8); + if (*e || (tmp > 07777U)) { /* Check range and trailing chars. */ + return 0; + } + *current_mode = tmp; + return 1; + } + + mask = umask(0); + umask(mask); + + new_mode = *current_mode; + + /* Note: We allow empty clauses, and hence empty modes. + * We treat an empty mode as no change to perms. */ + + while (*s) { /* Process clauses. */ + + if (*s == ',') { /* We allow empty clauses. */ + ++s; + continue; + } + + /* Get a wholist. */ + wholist = 0; + + WHO_LIST: + p = who_chars; + do { + if (*p == *s) { + wholist |= who_mask[(int)(p-who_chars)]; + if (!*++s) { + return 0; + } + goto WHO_LIST; + } + } while (*++p); + + do { /* Process action list. */ + if ((*s != '+') && (*s != '-')) { + if (*s != '=') { + return 0; + } + /* Since op is '=', clear all bits corresponding to the + * wholist, of all file bits if wholist is empty. */ + permlist = ~FILEMODEBITS; + if (wholist) { + permlist = ~wholist; + } + new_mode &= permlist; + } + op = *s++; + + /* Check for permcopy. */ + p = who_chars + 1; /* Skip 'a' entry. */ + do { + if (*p == *s) { + int i = 0; + permlist = who_mask[(int)(p-who_chars)] + & (S_IRWXU | S_IRWXG | S_IRWXO) + & new_mode; + do { + if (permlist & perm_mask[i]) { + permlist |= perm_mask[i]; + } + } while (++i < 3); + ++s; + goto GOT_ACTION; + } + } while (*++p); + + /* It was not a permcopy, so get a permlist. */ + permlist = 0; + + PERM_LIST: + p = perm_chars; + do { + if (*p == *s) { + if ((*p != 'X') + || (new_mode & (S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH)) + ) { + permlist |= perm_mask[(int)(p-perm_chars)]; + } + if (!*++s) { + break; + } + goto PERM_LIST; + } + } while (*++p); + + GOT_ACTION: + if (permlist) { /* The permlist was nonempty. */ + mode_t tmp = ~mask; + if (wholist) { + tmp = wholist; + } + permlist &= tmp; + + if (op == '-') { + new_mode &= ~permlist; + } else { + new_mode |= permlist; + } + } + } while (*s && (*s != ',')); + } + + *current_mode = new_mode; + + return 1; +} diff --git a/libbb/perror_msg.c b/libbb/perror_msg.c new file mode 100644 index 000000000..7fb0830be --- /dev/null +++ b/libbb/perror_msg.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + +void bb_perror_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_vperror_msg(s, p); + va_end(p); +} diff --git a/libbb/perror_msg_and_die.c b/libbb/perror_msg_and_die.c new file mode 100644 index 000000000..2303ba211 --- /dev/null +++ b/libbb/perror_msg_and_die.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + +void bb_perror_msg_and_die(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_vperror_msg(s, p); + va_end(p); + if (die_sleep) + sleep(die_sleep); + exit(xfunc_error_retval); +} diff --git a/libbb/perror_nomsg.c b/libbb/perror_nomsg.c new file mode 100644 index 000000000..3aefd5301 --- /dev/null +++ b/libbb/perror_nomsg.c @@ -0,0 +1,16 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_perror_nomsg implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +void bb_perror_nomsg(void) +{ + /* Ignore the gcc warning about a null format string. */ + bb_perror_msg(NULL); +} diff --git a/libbb/perror_nomsg_and_die.c b/libbb/perror_nomsg_and_die.c new file mode 100644 index 000000000..e5623c2a9 --- /dev/null +++ b/libbb/perror_nomsg_and_die.c @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_perror_nomsg_and_die implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +void bb_perror_nomsg_and_die(void) +{ + /* Ignore the gcc warning about a null format string. */ + bb_perror_msg_and_die(NULL); +} diff --git a/libbb/process_escape_sequence.c b/libbb/process_escape_sequence.c new file mode 100644 index 000000000..138e751f5 --- /dev/null +++ b/libbb/process_escape_sequence.c @@ -0,0 +1,89 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) Manuel Novoa III + * and Vladimir Oleynik + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include "libbb.h" + +#define WANT_HEX_ESCAPES 1 + +/* Usual "this only works for ascii compatible encodings" disclaimer. */ +#undef _tolower +#define _tolower(X) ((X)|((char) 0x20)) + +char bb_process_escape_sequence(const char **ptr) +{ + static const char charmap[] = { + 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', 0, + '\a', '\b', '\f', '\n', '\r', '\t', '\v', '\\', '\\' }; + + const char *p; + const char *q; + unsigned int num_digits; + unsigned int r; + unsigned int n; + unsigned int d; + unsigned int base; + + num_digits = n = 0; + base = 8; + q = *ptr; + +#ifdef WANT_HEX_ESCAPES + if (*q == 'x') { + ++q; + base = 16; + ++num_digits; + } +#endif + + do { + d = (unsigned int)(*q - '0'); +#ifdef WANT_HEX_ESCAPES + if (d >= 10) { + d = ((unsigned int)(_tolower(*q) - 'a')) + 10; + } +#endif + + if (d >= base) { +#ifdef WANT_HEX_ESCAPES + if ((base == 16) && (!--num_digits)) { +/* return '\\'; */ + --q; + } +#endif + break; + } + + r = n * base + d; + if (r > UCHAR_MAX) { + break; + } + + n = r; + ++q; + } while (++num_digits < 3); + + if (num_digits == 0) { /* mnemonic escape sequence? */ + p = charmap; + do { + if (*p == *q) { + q++; + break; + } + } while (*++p); + n = *(p+(sizeof(charmap)/2)); + } + + *ptr = q; + + return (char) n; +} diff --git a/libbb/procps.c b/libbb/procps.c new file mode 100644 index 000000000..017710ff4 --- /dev/null +++ b/libbb/procps.c @@ -0,0 +1,255 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright 1998 by Albert Cahalan; all rights reserved. + * Copyright (C) 2002 by Vladimir Oleynik + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + + +typedef struct unsigned_to_name_map_t { + unsigned id; + char name[12]; +} unsigned_to_name_map_t; + +typedef struct cache_t { + unsigned_to_name_map_t *cache; + int size; +} cache_t; + +static cache_t username, groupname; + +static void clear_cache(cache_t *cp) +{ + free(cp->cache); + cp->cache = NULL; + cp->size = 0; +} +void clear_username_cache(void) +{ + clear_cache(&username); + clear_cache(&groupname); +} + +#if 0 /* more generic, but we don't need that yet */ +/* Returns -N-1 if not found. */ +/* cp->cache[N] is allocated and must be filled in this case */ +static int get_cached(cache_t *cp, unsigned id) +{ + int i; + for (i = 0; i < cp->size; i++) + if (cp->cache[i].id == id) + return i; + i = cp->size++; + cp->cache = xrealloc(cp->cache, cp->size * sizeof(*cp->cache)); + cp->cache[i++].id = id; + return -i; +} +#endif + +typedef char* ug_func(char *name, long uid, int bufsize); +static char* get_cached(cache_t *cp, unsigned id, ug_func* fp) +{ + int i; + for (i = 0; i < cp->size; i++) + if (cp->cache[i].id == id) + return cp->cache[i].name; + i = cp->size++; + cp->cache = xrealloc(cp->cache, cp->size * sizeof(*cp->cache)); + cp->cache[i].id = id; + fp(cp->cache[i].name, id, sizeof(cp->cache[i].name)); + return cp->cache[i].name; +} +const char* get_cached_username(uid_t uid) +{ + return get_cached(&username, uid, bb_getpwuid); +} +const char* get_cached_groupname(gid_t gid) +{ + return get_cached(&groupname, gid, bb_getgrgid); +} + + +#define PROCPS_BUFSIZE 1024 + +static int read_to_buf(const char *filename, void *buf) +{ + ssize_t ret; + ret = open_read_close(filename, buf, PROCPS_BUFSIZE-1); + ((char *)buf)[ret > 0 ? ret : 0] = '\0'; + return ret; +} + +procps_status_t* alloc_procps_scan(int flags) +{ + procps_status_t* sp = xzalloc(sizeof(procps_status_t)); + sp->dir = xopendir("/proc"); + return sp; +} + +void free_procps_scan(procps_status_t* sp) +{ + closedir(sp->dir); + free(sp->cmd); + free(sp); +} + +void BUG_comm_size(void); +procps_status_t* procps_scan(procps_status_t* sp, int flags) +{ + struct dirent *entry; + char buf[PROCPS_BUFSIZE]; + char filename[sizeof("/proc//cmdline") + sizeof(int)*3]; + char *filename_tail; + long tasknice; + unsigned pid; + int n; + struct stat sb; + + if (!sp) + sp = alloc_procps_scan(flags); + + for (;;) { + entry = readdir(sp->dir); + if (entry == NULL) { + free_procps_scan(sp); + return NULL; + } + pid = bb_strtou(entry->d_name, NULL, 10); + if (errno) + continue; + + /* After this point we have to break, not continue + * ("continue" would mean that current /proc/NNN + * is not a valid process info) */ + + memset(&sp->rss, 0, sizeof(*sp) - offsetof(procps_status_t, rss)); + + sp->pid = pid; + if (!(flags & ~PSSCAN_PID)) break; + + filename_tail = filename + sprintf(filename, "/proc/%d", pid); + + if (flags & PSSCAN_UIDGID) { + if (stat(filename, &sb)) + break; + /* Need comment - is this effective or read UID/GID? */ + sp->uid = sb.st_uid; + sp->gid = sb.st_gid; + } + + if (flags & PSSCAN_STAT) { + char *cp; + /* see proc(5) for some details on this */ + strcpy(filename_tail, "/stat"); + n = read_to_buf(filename, buf); + if (n < 0) + break; + cp = strrchr(buf, ')'); /* split into "PID (cmd" and "" */ + if (!cp || cp[1] != ' ') + break; + cp[0] = '\0'; + if (sizeof(sp->comm) < 16) + BUG_comm_size(); + sscanf(buf, "%*s (%15c", sp->comm); + n = sscanf(cp+2, + "%c %u " /* state, ppid */ + "%u %u %*s %*s " /* pgid, sid, tty, tpgid */ + "%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */ + "%lu %lu " /* utime, stime */ + "%*s %*s %*s " /* cutime, cstime, priority */ + "%ld " /* nice */ + "%*s %*s %*s " /* timeout, it_real_value, start_time */ + "%*s " /* vsize */ + "%lu", /* rss */ + sp->state, &sp->ppid, + &sp->pgid, &sp->sid, + &sp->utime, &sp->stime, + &tasknice, + &sp->rss); + if (n != 8) + break; + + if (sp->rss == 0 && sp->state[0] != 'Z') + sp->state[1] = 'W'; + else + sp->state[1] = ' '; + if (tasknice < 0) + sp->state[2] = '<'; + else if (tasknice > 0) + sp->state[2] = 'N'; + else + sp->state[2] = ' '; + +#ifdef PAGE_SHIFT + sp->rss <<= (PAGE_SHIFT - 10); /* 2**10 = 1kb */ +#else + sp->rss *= (getpagesize() >> 10); /* 2**10 = 1kb */ +#endif + } + + if (flags & PSSCAN_CMD) { + free(sp->cmd); + sp->cmd = NULL; + strcpy(filename_tail, "/cmdline"); + n = read_to_buf(filename, buf); + if (n <= 0) + break; + if (buf[n-1] == '\n') { + if (!--n) + break; + buf[n] = '\0'; + } + do { + n--; + if ((unsigned char)(buf[n]) < ' ') + buf[n] = ' '; + } while (n); + sp->cmd = strdup(buf); + } + break; + } + return sp; +} +/* from kernel: + // pid comm S ppid pgid sid tty_nr tty_pgrp flg + sprintf(buffer,"%d (%s) %c %d %d %d %d %d %lu %lu \ +%lu %lu %lu %lu %lu %ld %ld %ld %ld %d 0 %llu %lu %ld %lu %lu %lu %lu %lu \ +%lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %lu %llu\n", + task->pid, + tcomm, + state, + ppid, + pgid, + sid, + tty_nr, + tty_pgrp, + task->flags, + min_flt, + + cmin_flt, + maj_flt, + cmaj_flt, + cputime_to_clock_t(utime), + cputime_to_clock_t(stime), + cputime_to_clock_t(cutime), + cputime_to_clock_t(cstime), + priority, + nice, + num_threads, + // 0, + start_time, + vsize, + mm ? get_mm_rss(mm) : 0, + rsslim, + mm ? mm->start_code : 0, + mm ? mm->end_code : 0, + mm ? mm->start_stack : 0, + esp, + eip, +the rest is some obsolete cruft +*/ diff --git a/libbb/pw_encrypt.c b/libbb/pw_encrypt.c new file mode 100644 index 000000000..d546bc883 --- /dev/null +++ b/libbb/pw_encrypt.c @@ -0,0 +1,29 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routine. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +char *pw_encrypt(const char *clear, const char *salt) +{ + static char cipher[128]; + char *cp; + +#if 0 /* was CONFIG_FEATURE_SHA1_PASSWORDS, but there is no such thing??? */ + if (strncmp(salt, "$2$", 3) == 0) { + return sha1_crypt(clear); + } +#endif + cp = (char *) crypt(clear, salt); + /* if crypt (a nonstandard crypt) returns a string too large, + truncate it so we don't overrun buffers and hope there is + enough security in what's left */ + safe_strncpy(cipher, cp, sizeof(cipher)); + return cipher; +} diff --git a/libbb/read.c b/libbb/read.c new file mode 100644 index 000000000..b3648b4d7 --- /dev/null +++ b/libbb/read.c @@ -0,0 +1,134 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +ssize_t safe_read(int fd, void *buf, size_t count) +{ + ssize_t n; + + do { + n = read(fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} + +/* + * Read all of the supplied buffer from a file. + * This does multiple reads as necessary. + * Returns the amount read, or -1 on an error. + * A short read is returned on an end of file. + */ +ssize_t full_read(int fd, void *buf, size_t len) +{ + ssize_t cc; + ssize_t total; + + total = 0; + + while (len) { + cc = safe_read(fd, buf, len); + + if (cc < 0) + return cc; /* read() returns -1 on failure. */ + + if (cc == 0) + break; + + buf = ((char *)buf) + cc; + total += cc; + len -= cc; + } + + return total; +} + +// Die with an error message if we can't read the entire buffer. +void xread(int fd, void *buf, size_t count) +{ + if (count) { + ssize_t size = full_read(fd, buf, count); + if (size != count) + bb_error_msg_and_die("short read"); + } +} + +// Die with an error message if we can't read one character. +unsigned char xread_char(int fd) +{ + char tmp; + + xread(fd, &tmp, 1); + + return tmp; +} + +// Read one line a-la fgets. Works only on seekable streams +char *reads(int fd, char *buffer, size_t size) +{ + char *p; + + if (size < 2) + return NULL; + size = full_read(fd, buffer, size-1); + if ((ssize_t)size <= 0) + return NULL; + + buffer[size] = '\0'; + p = strchr(buffer, '\n'); + if (p) { + off_t offset; + *p++ = '\0'; + // avoid incorrect (unsigned) widening + offset = (off_t)(p-buffer) - (off_t)size; + // set fd position the right after the \n + if (offset && lseek(fd, offset, SEEK_CUR) == (off_t)-1) + return NULL; + } + return buffer; +} + +ssize_t read_close(int fd, void *buf, size_t size) +{ + int e; + size = full_read(fd, buf, size); + e = errno; + close(fd); + errno = e; + return size; +} + +ssize_t open_read_close(const char *filename, void *buf, size_t size) +{ + int fd = open(filename, O_RDONLY); + if (fd < 0) + return fd; + return read_close(fd, buf, size); +} + +void *xmalloc_open_read_close(const char *filename, size_t *sizep) +{ + char *buf; + size_t size = sizep ? *sizep : INT_MAX; + int fd = xopen(filename, O_RDONLY); + off_t len = xlseek(fd, 0, SEEK_END); + xlseek(fd, 0, SEEK_SET); + + if (len > size) + bb_error_msg_and_die("file '%s' is too big", filename); + size = len; + buf = xmalloc(size+1); + size = read_close(fd, buf, size); + if ((ssize_t)size < 0) + bb_perror_msg_and_die("'%s'", filename); + buf[size] = '\0'; + if (sizep) *sizep = size; + return buf; +} diff --git a/libbb/recursive_action.c b/libbb/recursive_action.c new file mode 100644 index 000000000..121a3dffd --- /dev/null +++ b/libbb/recursive_action.c @@ -0,0 +1,126 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +#undef DEBUG_RECURS_ACTION + +/* + * Walk down all the directories under the specified + * location, and do something (something specified + * by the fileAction and dirAction function pointers). + * + * Unfortunately, while nftw(3) could replace this and reduce + * code size a bit, nftw() wasn't supported before GNU libc 2.1, + * and so isn't sufficiently portable to take over since glibc2.1 + * is so stinking huge. + */ + +static int true_action(const char *fileName, struct stat *statbuf, void* userData, int depth) +{ + return TRUE; +} + +/* fileAction return value of 0 on any file in directory will make + * recursive_action() return 0, but it doesn't stop directory traversal + * (fileAction/dirAction will be called on each file). + * + * if !depthFirst, dirAction return value of 0 (FALSE) or 2 (SKIP) + * prevents recursion into that directory, instead + * recursive_action() returns 0 (if FALSE) or 1 (if SKIP). + * + * followLinks=0/1 differs mainly in handling of links to dirs. + * 0: lstat(statbuf). Calls fileAction on link name even if points to dir. + * 1: stat(statbuf). Calls dirAction and optionally recurse on link to dir. + */ + +int recursive_action(const char *fileName, + int recurse, int followLinks, int depthFirst, + int (*fileAction)(const char *fileName, struct stat *statbuf, void* userData, int depth), + int (*dirAction)(const char *fileName, struct stat *statbuf, void* userData, int depth), + void* userData, + int depth) +{ + struct stat statbuf; + int status; + DIR *dir; + struct dirent *next; + + if (!fileAction) fileAction = true_action; + if (!dirAction) dirAction = true_action; + + status = (followLinks ? stat : lstat)(fileName, &statbuf); + + if (status < 0) { +#ifdef DEBUG_RECURS_ACTION + bb_error_msg("status=%d followLinks=%d TRUE=%d", + status, followLinks, TRUE); +#endif + bb_perror_msg("%s", fileName); + return FALSE; + } + + /* If S_ISLNK(m), then we know that !S_ISDIR(m). + * Then we can skip checking first part: if it is true, then + * (!dir) is also true! */ + if ( /* (!followLinks && S_ISLNK(statbuf.st_mode)) || */ + !S_ISDIR(statbuf.st_mode) + ) { + return fileAction(fileName, &statbuf, userData, depth); + } + + /* It's a directory (or a link to one, and followLinks is set) */ + + if (!recurse) { + return dirAction(fileName, &statbuf, userData, depth); + } + + if (!depthFirst) { + status = dirAction(fileName, &statbuf, userData, depth); + if (!status) { + bb_perror_msg("%s", fileName); + return FALSE; + } + if (status == SKIP) + return TRUE; + } + + dir = opendir(fileName); + if (!dir) { + /* findutils-4.1.20 reports this */ + /* (i.e. it doesn't silently return with exit code 1) */ + /* To trigger: "find -exec rm -rf {} \;" */ + bb_perror_msg("%s", fileName); + return FALSE; + } + status = TRUE; + while ((next = readdir(dir)) != NULL) { + char *nextFile; + + nextFile = concat_subpath_file(fileName, next->d_name); + if (nextFile == NULL) + continue; + if (!recursive_action(nextFile, TRUE, followLinks, depthFirst, + fileAction, dirAction, userData, depth+1)) { + status = FALSE; + } + free(nextFile); + } + closedir(dir); + if (depthFirst) { + if (!dirAction(fileName, &statbuf, userData, depth)) { + bb_perror_msg("%s", fileName); + return FALSE; + } + } + + if (!status) + return FALSE; + return TRUE; +} diff --git a/libbb/remove_file.c b/libbb/remove_file.c new file mode 100644 index 000000000..ab159a481 --- /dev/null +++ b/libbb/remove_file.c @@ -0,0 +1,111 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini remove_file implementation for busybox + * + * Copyright (C) 2001 Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libbb.h" + +int remove_file(const char *path, int flags) +{ + struct stat path_stat; + int path_exists = 1; + + if (lstat(path, &path_stat) < 0) { + if (errno != ENOENT) { + bb_perror_msg("cannot stat '%s'", path); + return -1; + } + + path_exists = 0; + } + + if (!path_exists) { + if (!(flags & FILEUTILS_FORCE)) { + bb_perror_msg("cannot remove '%s'", path); + return -1; + } + return 0; + } + + if (S_ISDIR(path_stat.st_mode)) { + DIR *dp; + struct dirent *d; + int status = 0; + + if (!(flags & FILEUTILS_RECUR)) { + bb_error_msg("%s: is a directory", path); + return -1; + } + + if ((!(flags & FILEUTILS_FORCE) && access(path, W_OK) < 0 && + isatty(0)) || + (flags & FILEUTILS_INTERACTIVE)) { + fprintf(stderr, "%s: descend into directory '%s'? ", applet_name, + path); + if (!bb_ask_confirmation()) + return 0; + } + + if ((dp = opendir(path)) == NULL) { + return -1; + } + + while ((d = readdir(dp)) != NULL) { + char *new_path; + + new_path = concat_subpath_file(path, d->d_name); + if(new_path == NULL) + continue; + if (remove_file(new_path, flags) < 0) + status = -1; + free(new_path); + } + + if (closedir(dp) < 0) { + bb_perror_msg("cannot close '%s'", path); + return -1; + } + + if (flags & FILEUTILS_INTERACTIVE) { + fprintf(stderr, "%s: remove directory '%s'? ", applet_name, path); + if (!bb_ask_confirmation()) + return status; + } + + if (rmdir(path) < 0) { + bb_perror_msg("cannot remove '%s'", path); + return -1; + } + + return status; + } else { + if ((!(flags & FILEUTILS_FORCE) && access(path, W_OK) < 0 && + !S_ISLNK(path_stat.st_mode) && + isatty(0)) || + (flags & FILEUTILS_INTERACTIVE)) { + fprintf(stderr, "%s: remove '%s'? ", applet_name, path); + if (!bb_ask_confirmation()) + return 0; + } + + if (unlink(path) < 0) { + bb_perror_msg("cannot remove '%s'", path); + return -1; + } + + return 0; + } +} diff --git a/libbb/restricted_shell.c b/libbb/restricted_shell.c new file mode 100644 index 000000000..74a64140f --- /dev/null +++ b/libbb/restricted_shell.c @@ -0,0 +1,57 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "libbb.h" + + + +/* Return 1 if SHELL is a restricted shell (one not returned by + getusershell), else 0, meaning it is a standard shell. */ + +int restricted_shell ( const char *shell ) +{ + char *line; + + setusershell ( ); + while (( line = getusershell ( ))) { + if (( *line != '#' ) && ( strcmp ( line, shell ) == 0 )) + break; + } + endusershell ( ); + return line ? 0 : 1; +} + diff --git a/libbb/run_shell.c b/libbb/run_shell.c new file mode 100644 index 000000000..6be09088d --- /dev/null +++ b/libbb/run_shell.c @@ -0,0 +1,101 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "libbb.h" +#ifdef CONFIG_SELINUX +#include /* for setexeccon */ +#endif + +#ifdef CONFIG_SELINUX +static security_context_t current_sid; + +void +renew_current_security_context(void) +{ + if (current_sid) + freecon(current_sid); /* Release old context */ + getcon(¤t_sid); /* update */ +} +void +set_current_security_context(security_context_t sid) +{ + if (current_sid) + freecon(current_sid); /* Release old context */ + current_sid = sid; +} + +#endif + +/* Run SHELL, or DEFAULT_SHELL if SHELL is empty. + If COMMAND is nonzero, pass it to the shell with the -c option. + If ADDITIONAL_ARGS is nonzero, pass it to the shell as more + arguments. */ + +void run_shell(const char *shell, int loginshell, const char *command, const char **additional_args) +{ + const char **args; + int argno = 1; + int additional_args_cnt = 0; + + for (args = additional_args; args && *args; args++) + additional_args_cnt++; + + args = xmalloc(sizeof(char*) * (4 + additional_args_cnt)); + + args[0] = bb_get_last_path_component(xstrdup(shell)); + + if (loginshell) + args[0] = xasprintf("-%s", args[0]); + + if (command) { + args[argno++] = "-c"; + args[argno++] = command; + } + if (additional_args) { + for (; *additional_args; ++additional_args) + args[argno++] = *additional_args; + } + args[argno] = NULL; +#ifdef CONFIG_SELINUX + if (current_sid && !setexeccon(current_sid)) { + freecon(current_sid); + execve(shell, (char **) args, environ); + } else +#endif + execv(shell, (char **) args); + bb_perror_msg_and_die("cannot run %s", shell); +} diff --git a/libbb/safe_strncpy.c b/libbb/safe_strncpy.c new file mode 100644 index 000000000..42bc16ea0 --- /dev/null +++ b/libbb/safe_strncpy.c @@ -0,0 +1,21 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + + + +/* Like strncpy but make sure the resulting string is always 0 terminated. */ +char * safe_strncpy(char *dst, const char *src, size_t size) +{ + if (!size) return dst; + dst[--size] = '\0'; + return strncpy(dst, src, size); +} diff --git a/libbb/safe_write.c b/libbb/safe_write.c new file mode 100644 index 000000000..c81f1247f --- /dev/null +++ b/libbb/safe_write.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include "libbb.h" + + + +ssize_t safe_write(int fd, const void *buf, size_t count) +{ + ssize_t n; + + do { + n = write(fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} diff --git a/libbb/setup_environment.c b/libbb/setup_environment.c new file mode 100644 index 000000000..874a58efa --- /dev/null +++ b/libbb/setup_environment.c @@ -0,0 +1,83 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "libbb.h" + + + +#define DEFAULT_LOGIN_PATH "/bin:/usr/bin" +#define DEFAULT_ROOT_LOGIN_PATH "/usr/sbin:/bin:/usr/bin:/sbin" + +void setup_environment(const char *shell, int loginshell, int changeenv, const struct passwd *pw) +{ + if (loginshell) { + const char *term; + + /* Change the current working directory to be the home directory + * of the user. It is a fatal error for this process to be unable + * to change to that directory. There is no "default" home + * directory. + * Some systems default to HOME=/ + */ + if (chdir(pw->pw_dir)) { + xchdir("/"); + fputs("warning: cannot change to home directory\n", stderr); + } + + /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH. + Unset all other environment variables. */ + term = getenv("TERM"); + clearenv(); + if (term) + xsetenv("TERM", term); + xsetenv("HOME", pw->pw_dir); + xsetenv("SHELL", shell); + xsetenv("USER", pw->pw_name); + xsetenv("LOGNAME", pw->pw_name); + xsetenv("PATH", (pw->pw_uid ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH)); + } + else if (changeenv) { + /* Set HOME, SHELL, and if not becoming a super-user, + USER and LOGNAME. */ + xsetenv("HOME", pw->pw_dir); + xsetenv("SHELL", shell); + if (pw->pw_uid) { + xsetenv("USER", pw->pw_name); + xsetenv("LOGNAME", pw->pw_name); + } + } +} diff --git a/libbb/sha1.c b/libbb/sha1.c new file mode 100644 index 000000000..34813e24a --- /dev/null +++ b/libbb/sha1.c @@ -0,0 +1,178 @@ +/* vi: set sw=4 ts=4: */ +/* + * Based on shasum from http://www.netsw.org/crypto/hash/ + * Majorly hacked up to use Dr Brian Gladman's sha1 code + * + * Copyright (C) 2002 Dr Brian Gladman , Worcester, UK. + * Copyright (C) 2003 Glenn L. McGrath + * Copyright (C) 2003 Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * --------------------------------------------------------------------------- + * Issue Date: 10/11/2002 + * + * This is a byte oriented version of SHA1 that operates on arrays of bytes + * stored in memory. It runs at 22 cycles per byte on a Pentium P4 processor + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "libbb.h" + +#define SHA1_BLOCK_SIZE 64 +#define SHA1_DIGEST_SIZE 20 +#define SHA1_HASH_SIZE SHA1_DIGEST_SIZE +#define SHA2_GOOD 0 +#define SHA2_BAD 1 + +#define rotl32(x,n) (((x) << n) | ((x) >> (32 - n))) + +#define SHA1_MASK (SHA1_BLOCK_SIZE - 1) + +/* reverse byte order in 32-bit words */ +#define ch(x,y,z) ((z) ^ ((x) & ((y) ^ (z)))) +#define parity(x,y,z) ((x) ^ (y) ^ (z)) +#define maj(x,y,z) (((x) & (y)) | ((z) & ((x) | (y)))) + +/* A normal version as set out in the FIPS. This version uses */ +/* partial loop unrolling and is optimised for the Pentium 4 */ +#define rnd(f,k) \ + do { \ + t = a; a = rotl32(a,5) + f(b,c,d) + e + k + w[i]; \ + e = d; d = c; c = rotl32(b, 30); b = t; \ + } while(0) + +static void sha1_compile(sha1_ctx_t *ctx) +{ + uint32_t w[80], i, a, b, c, d, e, t; + + /* note that words are compiled from the buffer into 32-bit */ + /* words in big-endian order so an order reversal is needed */ + /* here on little endian machines */ + for (i = 0; i < SHA1_BLOCK_SIZE / 4; ++i) + w[i] = htonl(ctx->wbuf[i]); + + for (i = SHA1_BLOCK_SIZE / 4; i < 80; ++i) + w[i] = rotl32(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + + a = ctx->hash[0]; + b = ctx->hash[1]; + c = ctx->hash[2]; + d = ctx->hash[3]; + e = ctx->hash[4]; + + for (i = 0; i < 20; ++i) { + rnd(ch, 0x5a827999); + } + + for (i = 20; i < 40; ++i) { + rnd(parity, 0x6ed9eba1); + } + + for (i = 40; i < 60; ++i) { + rnd(maj, 0x8f1bbcdc); + } + + for (i = 60; i < 80; ++i) { + rnd(parity, 0xca62c1d6); + } + + ctx->hash[0] += a; + ctx->hash[1] += b; + ctx->hash[2] += c; + ctx->hash[3] += d; + ctx->hash[4] += e; +} + +void sha1_begin(sha1_ctx_t *ctx) +{ + ctx->count[0] = ctx->count[1] = 0; + ctx->hash[0] = 0x67452301; + ctx->hash[1] = 0xefcdab89; + ctx->hash[2] = 0x98badcfe; + ctx->hash[3] = 0x10325476; + ctx->hash[4] = 0xc3d2e1f0; +} + +/* SHA1 hash data in an array of bytes into hash buffer and call the */ +/* hash_compile function as required. */ +void sha1_hash(const void *data, size_t length, sha1_ctx_t *ctx) +{ + uint32_t pos = (uint32_t) (ctx->count[0] & SHA1_MASK); + uint32_t freeb = SHA1_BLOCK_SIZE - pos; + const unsigned char *sp = data; + + if ((ctx->count[0] += length) < length) + ++(ctx->count[1]); + + while (length >= freeb) { /* tranfer whole blocks while possible */ + memcpy(((unsigned char *) ctx->wbuf) + pos, sp, freeb); + sp += freeb; + length -= freeb; + freeb = SHA1_BLOCK_SIZE; + pos = 0; + sha1_compile(ctx); + } + + memcpy(((unsigned char *) ctx->wbuf) + pos, sp, length); +} + +void *sha1_end(void *resbuf, sha1_ctx_t *ctx) +{ + /* SHA1 Final padding and digest calculation */ +#if BB_BIG_ENDIAN + static uint32_t mask[4] = { 0x00000000, 0xff000000, 0xffff0000, 0xffffff00 }; + static uint32_t bits[4] = { 0x80000000, 0x00800000, 0x00008000, 0x00000080 }; +#else + static uint32_t mask[4] = { 0x00000000, 0x000000ff, 0x0000ffff, 0x00ffffff }; + static uint32_t bits[4] = { 0x00000080, 0x00008000, 0x00800000, 0x80000000 }; +#endif + + uint8_t *hval = resbuf; + uint32_t i, cnt = (uint32_t) (ctx->count[0] & SHA1_MASK); + + /* mask out the rest of any partial 32-bit word and then set */ + /* the next byte to 0x80. On big-endian machines any bytes in */ + /* the buffer will be at the top end of 32 bit words, on little */ + /* endian machines they will be at the bottom. Hence the AND */ + /* and OR masks above are reversed for little endian systems */ + ctx->wbuf[cnt >> 2] = + (ctx->wbuf[cnt >> 2] & mask[cnt & 3]) | bits[cnt & 3]; + + /* we need 9 or more empty positions, one for the padding byte */ + /* (above) and eight for the length count. If there is not */ + /* enough space pad and empty the buffer */ + if (cnt > SHA1_BLOCK_SIZE - 9) { + if (cnt < 60) + ctx->wbuf[15] = 0; + sha1_compile(ctx); + cnt = 0; + } else /* compute a word index for the empty buffer positions */ + cnt = (cnt >> 2) + 1; + + while (cnt < 14) /* and zero pad all but last two positions */ + ctx->wbuf[cnt++] = 0; + + /* assemble the eight byte counter in the buffer in big-endian */ + /* format */ + + ctx->wbuf[14] = htonl((ctx->count[1] << 3) | (ctx->count[0] >> 29)); + ctx->wbuf[15] = htonl(ctx->count[0] << 3); + + sha1_compile(ctx); + + /* extract the hash value as bytes in case the hash buffer is */ + /* misaligned for 32-bit words */ + + for (i = 0; i < SHA1_DIGEST_SIZE; ++i) + hval[i] = (unsigned char) (ctx->hash[i >> 2] >> 8 * (~i & 3)); + + return resbuf; +} diff --git a/libbb/simplify_path.c b/libbb/simplify_path.c new file mode 100644 index 000000000..b714c66b6 --- /dev/null +++ b/libbb/simplify_path.c @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_simplify_path implementation for busybox + * + * Copyright (C) 2001 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +char *bb_simplify_path(const char *path) +{ + char *s, *start, *p; + + if (path[0] == '/') + start = xstrdup(path); + else { + s = xgetcwd(NULL); + start = concat_path_file(s, path); + free(s); + } + p = s = start; + + do { + if (*p == '/') { + if (*s == '/') { /* skip duplicate (or initial) slash */ + continue; + } else if (*s == '.') { + if (s[1] == '/' || s[1] == 0) { /* remove extra '.' */ + continue; + } else if ((s[1] == '.') && (s[2] == '/' || s[2] == 0)) { + ++s; + if (p > start) { + while (*--p != '/'); /* omit previous dir */ + } + continue; + } + } + } + *++p = *s; + } while (*++s); + + if ((p == start) || (*p != '/')) { /* not a trailing slash */ + ++p; /* so keep last character */ + } + *p = 0; + + return start; +} diff --git a/libbb/skip_whitespace.c b/libbb/skip_whitespace.c new file mode 100644 index 000000000..02c1f5828 --- /dev/null +++ b/libbb/skip_whitespace.c @@ -0,0 +1,18 @@ +/* vi: set sw=4 ts=4: */ +/* + * skip_whitespace implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +char *skip_whitespace(const char *s) +{ + while (isspace(*s)) ++s; + + return (char *) s; +} diff --git a/libbb/speed_table.c b/libbb/speed_table.c new file mode 100644 index 000000000..6137b7731 --- /dev/null +++ b/libbb/speed_table.c @@ -0,0 +1,117 @@ +/* vi: set sw=4 ts=4: */ +/* + * compact speed_t <-> speed functions for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +struct speed_map { + unsigned short speed; + unsigned short value; +}; + +static const struct speed_map speeds[] = { + {B0, 0}, + {B50, 50}, + {B75, 75}, + {B110, 110}, + {B134, 134}, + {B150, 150}, + {B200, 200}, + {B300, 300}, + {B600, 600}, + {B1200, 1200}, + {B1800, 1800}, + {B2400, 2400}, + {B4800, 4800}, + {B9600, 9600}, +#ifdef B19200 + {B19200, 19200}, +#elif defined(EXTA) + {EXTA, 19200}, +#endif +#ifdef B38400 + {B38400, 38400/256 + 0x8000U}, +#elif defined(EXTB) + {EXTB, 38400/256 + 0x8000U}, +#endif +#ifdef B57600 + {B57600, 57600/256 + 0x8000U}, +#endif +#ifdef B115200 + {B115200, 115200/256 + 0x8000U}, +#endif +#ifdef B230400 + {B230400, 230400/256 + 0x8000U}, +#endif +#ifdef B460800 + {B460800, 460800/256 + 0x8000U}, +#endif +}; + +enum { NUM_SPEEDS = (sizeof(speeds) / sizeof(struct speed_map)) }; + +unsigned int tty_baud_to_value(speed_t speed) +{ + int i = 0; + + do { + if (speed == speeds[i].speed) { + if (speeds[i].value & 0x8000U) { + return ((unsigned long) (speeds[i].value) & 0x7fffU) * 256; + } + return speeds[i].value; + } + } while (++i < NUM_SPEEDS); + + return 0; +} + +speed_t tty_value_to_baud(unsigned int value) +{ + int i = 0; + + do { + if (value == tty_baud_to_value(speeds[i].speed)) { + return speeds[i].speed; + } + } while (++i < NUM_SPEEDS); + + return (speed_t) - 1; +} + +#if 0 +/* testing code */ +#include + +int main(void) +{ + unsigned long v; + speed_t s; + + for (v = 0 ; v < 500000 ; v++) { + s = tty_value_to_baud(v); + if (s == (speed_t) -1) { + continue; + } + printf("v = %lu -- s = %0lo\n", v, (unsigned long) s); + } + + printf("-------------------------------\n"); + + for (s = 0 ; s < 010017+1 ; s++) { + v = tty_baud_to_value(s); + if (!v) { + continue; + } + printf("v = %lu -- s = %0lo\n", v, (unsigned long) s); + } + + return 0; +} +#endif diff --git a/libbb/trim.c b/libbb/trim.c new file mode 100644 index 000000000..d36391540 --- /dev/null +++ b/libbb/trim.c @@ -0,0 +1,31 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include "libbb.h" + + +void trim(char *s) +{ + size_t len = strlen(s); + size_t lws; + + /* trim trailing whitespace */ + while (len && isspace(s[len-1])) --len; + + /* trim leading whitespace */ + if(len) { + lws = strspn(s, " \n\r\t\v"); + memmove(s, s + lws, len -= lws); + } + s[len] = 0; +} diff --git a/libbb/u_signal_names.c b/libbb/u_signal_names.c new file mode 100644 index 000000000..88311dd9c --- /dev/null +++ b/libbb/u_signal_names.c @@ -0,0 +1,58 @@ +/* vi: set sw=4 ts=4: */ +/* + * Signal name/number conversion routines. + * + * Copyright 2006 Rob Landley + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +static const struct signal_name { + int number; + char name[5]; +} signals[] = { + // SUSv3 says kill must support these, and specifies the numerical values, + // http://www.opengroup.org/onlinepubs/009695399/utilities/kill.html + {0, "0"}, {1, "HUP"}, {2, "INT"}, {3, "QUIT"}, {6, "ABRT"}, {9, "KILL"}, + {14, "ALRM"}, {15, "TERM"}, + // And Posix adds the following: + {SIGILL, "ILL"}, {SIGTRAP, "TRAP"}, {SIGFPE, "FPE"}, {SIGUSR1, "USR1"}, + {SIGSEGV, "SEGV"}, {SIGUSR2, "USR2"}, {SIGPIPE, "PIPE"}, {SIGCHLD, "CHLD"}, + {SIGCONT, "CONT"}, {SIGSTOP, "STOP"}, {SIGTSTP, "TSTP"}, {SIGTTIN, "TTIN"}, + {SIGTTOU, "TTOU"} +}; + +// Convert signal name to number. + +int get_signum(const char *name) +{ + int i; + + i = atoi(name); + if (i) return i; + for (i = 0; i < sizeof(signals) / sizeof(struct signal_name); i++) + if (!strcasecmp(signals[i].name, name) || + (!strncasecmp(signals[i].name, "SIG", 3) + && !strcasecmp(signals[i].name+3, signals[i].name))) + return signals[i].number; + return -1; +} + +// Convert signal number to name + +const char *get_signame(int number) +{ + int i; + static char buf[8]; + + for (i=0; i < sizeof(signals) / sizeof(struct signal_name); i++) { + if (number == signals[i].number) { + return signals[i].name; + } + } + + itoa_to_buf(number, buf, 8); + return buf; +} diff --git a/libbb/uuencode.c b/libbb/uuencode.c new file mode 100644 index 000000000..38401205b --- /dev/null +++ b/libbb/uuencode.c @@ -0,0 +1,63 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 2006 Rob Landley + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* Conversion table. for base 64 */ +const char bb_uuenc_tbl_base64[65] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', + '=' /* termination character */ +}; + +const char bb_uuenc_tbl_std[65] = { + '`', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`' /* termination character */ +}; + +/* + * Encode the string S of length LENGTH to base64 format and place it + * to STORE. STORE will be 0-terminated, and must point to a writable + * buffer of at least 1+BASE64_LENGTH(length) bytes. + * where BASE64_LENGTH(len) = (4 * ((LENGTH + 2) / 3)) + */ +void bb_uuencode(const unsigned char *s, char *store, const int length, const char *tbl) +{ + int i; + char *p = store; + + /* Transform the 3x8 bits to 4x6 bits, as required by base64. */ + for (i = 0; i < length; i += 3) { + *p++ = tbl[s[0] >> 2]; + *p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)]; + *p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)]; + *p++ = tbl[s[2] & 0x3f]; + s += 3; + } + /* Pad the result if necessary... */ + if (i == length + 1) { + *(p - 1) = tbl[64]; + } + else if (i == length + 2) { + *(p - 1) = *(p - 2) = tbl[64]; + } + /* ...and zero-terminate it. */ + *p = '\0'; +} diff --git a/libbb/vdprintf.c b/libbb/vdprintf.c new file mode 100644 index 000000000..ffcb7a444 --- /dev/null +++ b/libbb/vdprintf.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "libbb.h" + + + +#if (__GLIBC__ < 2) +int vdprintf(int d, const char *format, va_list ap) +{ + char buf[BUF_SIZE]; + int len; + + len = vsprintf(buf, format, ap); + return write(d, buf, len); +} +#endif diff --git a/libbb/verror_msg.c b/libbb/verror_msg.c new file mode 100644 index 000000000..0f018c517 --- /dev/null +++ b/libbb/verror_msg.c @@ -0,0 +1,46 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +int logmode = LOGMODE_STDIO; +const char *msg_eol = "\n"; + +void bb_verror_msg(const char *s, va_list p, const char* strerr) +{ + /* va_copy is used because it is not portable + * to use va_list p twice */ + va_list p2; + va_copy(p2, p); + + if (logmode & LOGMODE_STDIO) { + fflush(stdout); + fprintf(stderr, "%s: ", applet_name); + vfprintf(stderr, s, p); + if (!strerr) + fputs(msg_eol, stderr); + else + fprintf(stderr, "%s%s%s", + s ? ": " : "", + strerr, msg_eol); + } + if (ENABLE_FEATURE_SYSLOG && (logmode & LOGMODE_SYSLOG)) { + if (!strerr) + vsyslog(LOG_ERR, s, p2); + else { + char *msg; + if (vasprintf(&msg, s, p2) < 0) + bb_error_msg_and_die(bb_msg_memory_exhausted); + syslog(LOG_ERR, "%s: %s", msg, strerr); + free(msg); + } + } + va_end(p2); +} diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c new file mode 100644 index 000000000..ebd32f8cd --- /dev/null +++ b/libbb/vfork_daemon_rexec.c @@ -0,0 +1,67 @@ +/* vi: set sw=4 ts=4: */ +/* + * Rexec program for system have fork() as vfork() with foreground option + * + * Copyright (C) Vladimir N. Oleynik + * Copyright (C) 2003 Russ Dill + * + * daemon() portion taken from uClibc: + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Modified for uClibc by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + + +#ifdef BB_NOMMU +void vfork_daemon_rexec(int nochdir, int noclose, + int argc, char **argv, char *foreground_opt) +{ + int fd; + char **vfork_args; + int a = 0; + + setsid(); + + if (!nochdir) + xchdir("/"); + + if (!noclose && (fd = open(bb_dev_null, O_RDWR, 0)) != -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) + close(fd); + } + + vfork_args = xcalloc(sizeof(char *), argc + 3); + vfork_args[a++] = CONFIG_BUSYBOX_EXEC_PATH; + while(*argv) { + vfork_args[a++] = *argv; + argv++; + } + vfork_args[a] = foreground_opt; + switch (vfork()) { + case 0: /* child */ + /* Make certain we are not a session leader, or else we + * might reacquire a controlling terminal */ + if (vfork()) + _exit(0); + execv(vfork_args[0], vfork_args); + bb_perror_msg_and_die("execv %s", vfork_args[0]); + case -1: /* error */ + bb_perror_msg_and_die("vfork"); + default: /* parent */ + exit(0); + } +} +#endif /* BB_NOMMU */ diff --git a/libbb/vherror_msg.c b/libbb/vherror_msg.c new file mode 100644 index 000000000..04446a090 --- /dev/null +++ b/libbb/vherror_msg.c @@ -0,0 +1,15 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +void bb_vherror_msg(const char *s, va_list p) +{ + bb_verror_msg(s, p, hstrerror(h_errno)); +} diff --git a/libbb/vinfo_msg.c b/libbb/vinfo_msg.c new file mode 100644 index 000000000..fa2798625 --- /dev/null +++ b/libbb/vinfo_msg.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +void bb_vinfo_msg(const char *s, va_list p) +{ + /* va_copy is used because it is not portable + * to use va_list p twice */ + va_list p2; + va_copy(p2, p); + if (logmode & LOGMODE_STDIO) { + vprintf(s, p); + fputs(msg_eol, stdout); + } + if (ENABLE_FEATURE_SYSLOG && (logmode & LOGMODE_SYSLOG)) + vsyslog(LOG_INFO, s, p2); + va_end(p2); +} diff --git a/libbb/vperror_msg.c b/libbb/vperror_msg.c new file mode 100644 index 000000000..c3f79c23b --- /dev/null +++ b/libbb/vperror_msg.c @@ -0,0 +1,15 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +void bb_vperror_msg(const char *s, va_list p) +{ + bb_verror_msg(s, p, strerror(errno)); +} diff --git a/libbb/warn_ignoring_args.c b/libbb/warn_ignoring_args.c new file mode 100644 index 000000000..be78a4414 --- /dev/null +++ b/libbb/warn_ignoring_args.c @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +/* + * warn_ignoring_args implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +void bb_warn_ignoring_args(int n) +{ + if (n) { + bb_error_msg("ignoring all arguments"); + } +} diff --git a/libbb/wfopen.c b/libbb/wfopen.c new file mode 100644 index 000000000..26e6a13e2 --- /dev/null +++ b/libbb/wfopen.c @@ -0,0 +1,20 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +FILE *fopen_or_warn(const char *path, const char *mode) +{ + FILE *fp = fopen(path, mode); + if (!fp) { + bb_perror_msg("%s", path); + errno = 0; + } + return fp; +} diff --git a/libbb/wfopen_input.c b/libbb/wfopen_input.c new file mode 100644 index 000000000..3da855fe6 --- /dev/null +++ b/libbb/wfopen_input.c @@ -0,0 +1,31 @@ +/* vi: set sw=4 ts=4: */ +/* + * wfopen_input implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* A number of applets need to open a file for reading, where the filename + * is a command line arg. Since often that arg is '-' (meaning stdin), + * we avoid testing everywhere by consolidating things in this routine. + * + * Note: We also consider "" to main stdin (for 'cmp' at least). + */ + +#include "libbb.h" + +FILE *fopen_or_warn_stdin(const char *filename) +{ + FILE *fp = stdin; + + if (filename != bb_msg_standard_input + && filename[0] + && (filename[0] != '-' || filename[1]) + ) { + fp = fopen_or_warn(filename, "r"); + } + + return fp; +} diff --git a/libbb/xatonum.c b/libbb/xatonum.c new file mode 100644 index 000000000..35607c317 --- /dev/null +++ b/libbb/xatonum.c @@ -0,0 +1,69 @@ +/* vi: set sw=4 ts=4: */ +/* + * ascii-to-numbers implementations for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +#define type long long +#define xstrtou(rest) xstrtoull##rest +#define xstrto(rest) xstrtoll##rest +#define xatou(rest) xatoull##rest +#define xato(rest) xatoll##rest +#define XSTR_UTYPE_MAX ULLONG_MAX +#define XSTR_TYPE_MAX LLONG_MAX +#define XSTR_TYPE_MIN LLONG_MIN +#define XSTR_STRTOU strtoull +#include "xatonum_template.c" + +#if ULONG_MAX != ULLONG_MAX +#define type long +#define xstrtou(rest) xstrtoul##rest +#define xstrto(rest) xstrtol##rest +#define xatou(rest) xatoul##rest +#define xato(rest) xatol##rest +#define XSTR_UTYPE_MAX ULONG_MAX +#define XSTR_TYPE_MAX LONG_MAX +#define XSTR_TYPE_MIN LONG_MIN +#define XSTR_STRTOU strtoul +#include "xatonum_template.c" +#endif + +#if UINT_MAX != ULONG_MAX +extern inline unsigned bb_strtoui(const char *str, char **end, int b) +{ + unsigned long v = strtoul(str, end, b); + if (v > UINT_MAX) { + errno = ERANGE; + return UINT_MAX; + } + return v; +} +#define type int +#define xstrtou(rest) xstrtou##rest +#define xstrto(rest) xstrtoi##rest +#define xatou(rest) xatou##rest +#define xato(rest) xatoi##rest +#define XSTR_UTYPE_MAX UINT_MAX +#define XSTR_TYPE_MAX INT_MAX +#define XSTR_TYPE_MIN INT_MIN +/* libc has no strtoui, so we need to create/use our own */ +#define XSTR_STRTOU bb_strtoui +#include "xatonum_template.c" +#endif + +/* A few special cases */ + +int xatoi_u(const char *numstr) +{ + return xatou_range(numstr, 0, INT_MAX); +} + +uint16_t xatou16(const char *numstr) +{ + return xatou_range(numstr, 0, 0xffff); +} diff --git a/libbb/xatonum_template.c b/libbb/xatonum_template.c new file mode 100644 index 000000000..53ba544eb --- /dev/null +++ b/libbb/xatonum_template.c @@ -0,0 +1,185 @@ +/* +You need to define the following (example): + +#define type long +#define xstrtou(rest) xstrtoul##rest +#define xstrto(rest) xstrtol##rest +#define xatou(rest) xatoul##rest +#define xato(rest) xatol##rest +#define XSTR_UTYPE_MAX ULONG_MAX +#define XSTR_TYPE_MAX LONG_MAX +#define XSTR_TYPE_MIN LONG_MIN +#define XSTR_STRTOU strtoul +*/ + +unsigned type xstrtou(_range_sfx)(const char *numstr, int base, + unsigned type lower, + unsigned type upper, + const struct suffix_mult *suffixes) +{ + unsigned type r; + int old_errno; + char *e; + + /* Disallow '-' and any leading whitespace. Speed isn't critical here + * since we're parsing commandline args. So make sure we get the + * actual isspace function rather than a lnumstrer macro implementaion. */ + if ((*numstr == '-') || (isspace)(*numstr)) + goto inval; + + /* Since this is a lib function, we're not allowed to reset errno to 0. + * Doing so could break an app that is deferring checking of errno. + * So, save the old value so that we can restore it if successful. */ + old_errno = errno; + errno = 0; + r = XSTR_STRTOU(numstr, &e, base); + /* Do the initial validity check. Note: The standards do not + * guarantee that errno is set if no digits were found. So we + * must test for this explicitly. */ + if (errno || (numstr == e)) + goto inval; /* error / no digits / illegal trailing chars */ + + errno = old_errno; /* Ok. So restore errno. */ + + /* Do optional suffix parsing. Allow 'empty' suffix tables. + * Note that we also allow nul suffixes with associated multipliers, + * to allow for scaling of the numstr by some default multiplier. */ + if (suffixes) { + while (suffixes->suffix) { + if (strcmp(suffixes->suffix, e) == 0) { + if (XSTR_UTYPE_MAX / suffixes->mult < r) + goto range; /* overflow! */ + ++e; + r *= suffixes->mult; + break; + } + ++suffixes; + } + } + + /* Note: trailing space is an error. + It would be easy enough to allow though if desired. */ + if (*e) + goto inval; + /* Finally, check for range limits. */ + if (r >= lower && r <= upper) + return r; + range: + bb_error_msg_and_die("number %s is not in %llu..%llu range", + numstr, (unsigned long long)lower, + (unsigned long long)upper); + inval: + bb_error_msg_and_die("invalid number '%s'", numstr); +} + +unsigned type xstrtou(_range)(const char *numstr, int base, + unsigned type lower, + unsigned type upper) +{ + return xstrtou(_range_sfx)(numstr, base, lower, upper, NULL); +} + +unsigned type xstrtou(_sfx)(const char *numstr, int base, + const struct suffix_mult *suffixes) +{ + return xstrtou(_range_sfx)(numstr, base, 0, XSTR_UTYPE_MAX, suffixes); +} + +unsigned type xstrtou()(const char *numstr, int base) +{ + return xstrtou(_range_sfx)(numstr, base, 0, XSTR_UTYPE_MAX, NULL); +} + +unsigned type xatou(_range_sfx)(const char *numstr, + unsigned type lower, + unsigned type upper, + const struct suffix_mult *suffixes) +{ + return xstrtou(_range_sfx)(numstr, 10, lower, upper, suffixes); +} + +unsigned type xatou(_range)(const char *numstr, + unsigned type lower, + unsigned type upper) +{ + return xstrtou(_range_sfx)(numstr, 10, lower, upper, NULL); +} + +unsigned type xatou(_sfx)(const char *numstr, + const struct suffix_mult *suffixes) +{ + return xstrtou(_range_sfx)(numstr, 10, 0, XSTR_UTYPE_MAX, suffixes); +} + +unsigned type xatou()(const char *numstr) +{ + return xatou(_sfx)(numstr, NULL); +} + +/* Signed ones */ + +type xstrto(_range_sfx)(const char *numstr, int base, + type lower, + type upper, + const struct suffix_mult *suffixes) +{ + unsigned type u = XSTR_TYPE_MAX; + type r; + const char *p = numstr; + + if ((p[0] == '-') && (p[1] != '+')) { + ++p; + ++u; /* two's complement */ + } + + r = xstrtou(_range_sfx)(p, base, 0, u, suffixes); + + if (*numstr == '-') { + r = -r; + } + + if (r < lower || r > upper) { + bb_error_msg_and_die("number %s is not in %lld..%lld range", + numstr, (long long)lower, (long long)upper); + } + + return r; +} + +type xstrto(_range)(const char *numstr, int base, type lower, type upper) +{ + return xstrto(_range_sfx)(numstr, base, lower, upper, NULL); +} + +type xato(_range_sfx)(const char *numstr, + type lower, + type upper, + const struct suffix_mult *suffixes) +{ + return xstrto(_range_sfx)(numstr, 10, lower, upper, suffixes); +} + +type xato(_range)(const char *numstr, type lower, type upper) +{ + return xstrto(_range_sfx)(numstr, 10, lower, upper, NULL); +} + +type xato(_sfx)(const char *numstr, const struct suffix_mult *suffixes) +{ + return xstrto(_range_sfx)(numstr, 10, XSTR_TYPE_MIN, XSTR_TYPE_MAX, suffixes); +} + +type xato()(const char *numstr) +{ + return xstrto(_range_sfx)(numstr, 10, XSTR_TYPE_MIN, XSTR_TYPE_MAX, NULL); +} + +#undef type +#undef xstrtou +#undef xstrto +#undef xatou +#undef xato +#undef XSTR_UTYPE_MAX +#undef XSTR_TYPE_MAX +#undef XSTR_TYPE_MIN +#undef XSTR_STRTOU diff --git a/libbb/xconnect.c b/libbb/xconnect.c new file mode 100644 index 000000000..b85648007 --- /dev/null +++ b/libbb/xconnect.c @@ -0,0 +1,153 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Connect to host at port using address resolution from getaddrinfo + * + */ + +#include "libbb.h" + +/* Return network byte ordered port number for a service. + * If "port" is a number use it as the port. + * If "port" is a name it is looked up in /etc/services, if it isnt found return + * default_port + */ +unsigned short bb_lookup_port(const char *port, const char *protocol, unsigned short default_port) +{ + unsigned short port_nr = htons(default_port); + if (port) { + char *endptr; + int old_errno; + long port_long; + + /* Since this is a lib function, we're not allowed to reset errno to 0. + * Doing so could break an app that is deferring checking of errno. */ + old_errno = errno; + errno = 0; + port_long = strtol(port, &endptr, 10); + if (errno != 0 || *endptr!='\0' || endptr==port || port_long < 0 || port_long > 65535) { + struct servent *tserv = getservbyname(port, protocol); + if (tserv) { + port_nr = tserv->s_port; + } + } else { + port_nr = htons(port_long); + } + errno = old_errno; + } + return port_nr; +} + +void bb_lookup_host(struct sockaddr_in *s_in, const char *host) +{ + struct hostent *he; + + memset(s_in, 0, sizeof(struct sockaddr_in)); + s_in->sin_family = AF_INET; + he = xgethostbyname(host); + memcpy(&(s_in->sin_addr), he->h_addr_list[0], he->h_length); +} + +void xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen) +{ + if (connect(s, s_addr, addrlen) < 0) { + if (ENABLE_FEATURE_CLEAN_UP) close(s); + if (s_addr->sa_family == AF_INET) + bb_perror_msg_and_die("cannot connect to remote host (%s)", + inet_ntoa(((struct sockaddr_in *)s_addr)->sin_addr)); + bb_perror_msg_and_die("cannot connect to remote host"); + } +} + +int xconnect_tcp_v4(struct sockaddr_in *s_addr) +{ + int s = xsocket(AF_INET, SOCK_STREAM, 0); + xconnect(s, (struct sockaddr*) s_addr, sizeof(*s_addr)); + return s; +} + +static const int one = 1; +int setsockopt_reuseaddr(int fd) +{ + return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); +} +int setsockopt_broadcast(int fd) +{ + return setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)); +} + +int dotted2sockaddr(const char *dotted, struct sockaddr* sp, int socklen) +{ + union { + struct in_addr a4; +#if ENABLE_FEATURE_IPV6 + struct in6_addr a6; +#endif + } a; + + /* TODO maybe: port spec? like n.n.n.n:nn */ + +#if ENABLE_FEATURE_IPV6 + if (socklen >= sizeof(struct sockaddr_in6) + && inet_pton(AF_INET6, dotted, &a.a6) > 0 + ) { + ((struct sockaddr_in6*)sp)->sin6_family = AF_INET6; + ((struct sockaddr_in6*)sp)->sin6_addr = a.a6; + /* ((struct sockaddr_in6*)sp)->sin6_port = */ + return 0; /* success */ + } +#endif + if (socklen >= sizeof(struct sockaddr_in) + && inet_pton(AF_INET, dotted, &a.a4) > 0 + ) { + ((struct sockaddr_in*)sp)->sin_family = AF_INET; + ((struct sockaddr_in*)sp)->sin_addr = a.a4; + /* ((struct sockaddr_in*)sp)->sin_port = */ + return 0; /* success */ + } + return 1; +} + +int xsocket_stream_ip4or6(sa_family_t *fp) +{ + int fd; +#if ENABLE_FEATURE_IPV6 + fd = socket(AF_INET6, SOCK_STREAM, 0); + if (fp) *fp = AF_INET6; + if (fd < 0) +#endif + { + fd = xsocket(AF_INET, SOCK_STREAM, 0); + if (fp) *fp = AF_INET; + } + return fd; +} + +int create_and_bind_socket_ip4or6(const char *hostaddr, int port) +{ + int fd; + sockaddr_inet sa; + + memset(&sa, 0, sizeof(sa)); + if (hostaddr) { + if (dotted2sockaddr(hostaddr, &sa.sa, sizeof(sa))) + bb_error_msg_and_die("bad address '%s'", hostaddr); + /* user specified bind addr dictates family */ + fd = xsocket(sa.sa.sa_family, SOCK_STREAM, 0); + } else + fd = xsocket_stream_ip4or6(&sa.sa.sa_family); + setsockopt_reuseaddr(fd); + + /* if (port >= 0) { */ +#if ENABLE_FEATURE_IPV6 + if (sa.sa.sa_family == AF_INET6 /* && !sa.sin6.sin6_port */) + sa.sin6.sin6_port = htons(port); +#endif + if (sa.sa.sa_family == AF_INET /* && !sa.sin.sin_port */) + sa.sin.sin_port = htons(port); + /* } */ + + xbind(fd, &sa.sa, sizeof(sa)); + return fd; +} diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c new file mode 100644 index 000000000..313e32814 --- /dev/null +++ b/libbb/xfuncs.c @@ -0,0 +1,521 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * Copyright (C) 2006 Rob Landley + * Copyright (C) 2006 Denis Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* All the functions starting with "x" call bb_error_msg_and_die() if they + * fail, so callers never need to check for errors. If it returned, it + * succeeded. */ + +#ifndef DMALLOC +/* dmalloc provides variants of these that do abort() on failure. + * Since dmalloc's prototypes overwrite the impls here as they are + * included after these prototypes in libbb.h, all is well. + */ +// Die if we can't allocate size bytes of memory. +void *xmalloc(size_t size) +{ + void *ptr = malloc(size); + if (ptr == NULL && size != 0) + bb_error_msg_and_die(bb_msg_memory_exhausted); + return ptr; +} + +// Die if we can't resize previously allocated memory. (This returns a pointer +// to the new memory, which may or may not be the same as the old memory. +// It'll copy the contents to a new chunk and free the old one if necessary.) +void *xrealloc(void *ptr, size_t size) +{ + ptr = realloc(ptr, size); + if (ptr == NULL && size != 0) + bb_error_msg_and_die(bb_msg_memory_exhausted); + return ptr; +} +#endif /* DMALLOC */ + +// Die if we can't allocate and zero size bytes of memory. +void *xzalloc(size_t size) +{ + void *ptr = xmalloc(size); + memset(ptr, 0, size); + return ptr; +} + +// Die if we can't copy a string to freshly allocated memory. +char * xstrdup(const char *s) +{ + char *t; + + if (s == NULL) + return NULL; + + t = strdup(s); + + if (t == NULL) + bb_error_msg_and_die(bb_msg_memory_exhausted); + + return t; +} + +// Die if we can't allocate n+1 bytes (space for the null terminator) and copy +// the (possibly truncated to length n) string into it. +char * xstrndup(const char *s, int n) +{ + int m; + char *t; + + if (ENABLE_DEBUG && s == NULL) + bb_error_msg_and_die("xstrndup bug"); + + /* We can just xmalloc(n+1) and strncpy into it, */ + /* but think about xstrndup("abc", 10000) wastage! */ + m = n; + t = (char*) s; + while (m) { + if (!*t) break; + m--; t++; + } + n = n - m; + t = xmalloc(n + 1); + t[n] = '\0'; + + return memcpy(t,s,n); +} + +// Die if we can't open a file and return a FILE * to it. +// Notice we haven't got xfread(), This is for use with fscanf() and friends. +FILE *xfopen(const char *path, const char *mode) +{ + FILE *fp = fopen(path, mode); + if (fp == NULL) + bb_perror_msg_and_die("%s", path); + return fp; +} + +// Die if we can't open an existing file and return an fd. +int xopen(const char *pathname, int flags) +{ + //if (ENABLE_DEBUG && (flags & O_CREAT)) + // bb_error_msg_and_die("xopen() with O_CREAT"); + + return xopen3(pathname, flags, 0666); +} + +// Die if we can't open a new file and return an fd. +int xopen3(const char *pathname, int flags, int mode) +{ + int ret; + + ret = open(pathname, flags, mode); + if (ret < 0) { + bb_perror_msg_and_die("%s", pathname); + } + return ret; +} + +/* +int ndelay_off(int fd) +{ + return fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) & ~O_NONBLOCK); +} +*/ +// Turn on nonblocking I/O on a fd +int ndelay_on(int fd) +{ + return fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) | O_NONBLOCK); +} + +// Die with an error message if we can't write the entire buffer. +void xwrite(int fd, const void *buf, size_t count) +{ + if (count) { + ssize_t size = full_write(fd, buf, count); + if (size != count) + bb_error_msg_and_die("short write"); + } +} + +// Die with an error message if we can't lseek to the right spot. +off_t xlseek(int fd, off_t offset, int whence) +{ + off_t off = lseek(fd, offset, whence); + if (off == (off_t)-1) { + if (whence == SEEK_SET) + bb_perror_msg_and_die("lseek(%"OFF_FMT"u)", offset); + bb_perror_msg_and_die("lseek"); + } + return off; +} + +// Die with supplied filename if this FILE * has ferror set. +void die_if_ferror(FILE *fp, const char *fn) +{ + if (ferror(fp)) { + bb_error_msg_and_die("%s: I/O error", fn); + } +} + +// Die with an error message if stdout has ferror set. +void die_if_ferror_stdout(void) +{ + die_if_ferror(stdout, bb_msg_standard_output); +} + +// Die with an error message if we have trouble flushing stdout. +void xfflush_stdout(void) +{ + if (fflush(stdout)) { + bb_perror_msg_and_die(bb_msg_standard_output); + } +} + +// This does a fork/exec in one call, using vfork(). Return PID of new child, +// -1 for failure. Runs argv[0], searching path if that has no / in it. +pid_t spawn(char **argv) +{ + static int failed; + pid_t pid; + void *app = ENABLE_FEATURE_SH_STANDALONE_SHELL ? find_applet_by_name(argv[0]) : 0; + + // Be nice to nommu machines. + failed = 0; + pid = vfork(); + if (pid < 0) return pid; + if (!pid) { + execvp(app ? CONFIG_BUSYBOX_EXEC_PATH : *argv, argv); + + // We're sharing a stack with blocked parent, let parent know we failed + // and then exit to unblock parent (but don't run atexit() stuff, which + // would screw up parent.) + + failed = -1; + _exit(0); + } + return failed ? failed : pid; +} + +// Die with an error message if we can't spawn a child process. +pid_t xspawn(char **argv) +{ + pid_t pid = spawn(argv); + if (pid < 0) bb_perror_msg_and_die("%s", *argv); + return pid; +} + +// Wait for the specified child PID to exit, returning child's error return. +int wait4pid(int pid) +{ + int status; + + if (pid == -1 || waitpid(pid, &status, 0) == -1) return -1; + if (WIFEXITED(status)) return WEXITSTATUS(status); + if (WIFSIGNALED(status)) return WTERMSIG(status); + return 0; +} + +void xsetenv(const char *key, const char *value) +{ + if (setenv(key, value, 1)) + bb_error_msg_and_die(bb_msg_memory_exhausted); +} + +// Converts unsigned long long value into compact 4-char +// representation. Examples: "1234", "1.2k", " 27M", "123T" +// Fifth char is always '\0' +void smart_ulltoa5(unsigned long long ul, char buf[5]) +{ + char *fmt; + char c; + unsigned v,idx = 0; + ul *= 10; + if (ul > 9999*10) { // do not scale if 9999 or less + while (ul >= 10000) { + ul /= 1024; + idx++; + } + } + v = ul; // ullong divisions are expensive, avoid them + + fmt = " 123456789"; + if (!idx) { // 9999 or less: use 1234 format + c = buf[0] = " 123456789"[v/10000]; + if (c!=' ') fmt = "0123456789"; + c = buf[1] = fmt[v/1000%10]; + if (c!=' ') fmt = "0123456789"; + buf[2] = fmt[v/100%10]; + buf[3] = "0123456789"[v/10%10]; + } else { + if (v>=10*10) { // scaled value is >=10: use 123M format + c = buf[0] = " 123456789"[v/1000]; + if (c!=' ') fmt = "0123456789"; + buf[1] = fmt[v/100%10]; + buf[2] = "0123456789"[v/10%10]; + } else { // scaled value is <10: use 1.2M format + buf[0] = "0123456789"[v/10]; + buf[1] = '.'; + buf[2] = "0123456789"[v%10]; + } + // see http://en.wikipedia.org/wiki/Tera + buf[3] = " kMGTPEZY"[idx]; + } + buf[4] = '\0'; +} + +// Convert unsigned integer to ascii, writing into supplied buffer. A +// truncated result is always null terminated (unless buflen is 0), and +// contains the first few digits of the result ala strncpy. +void BUG_sizeof_unsigned_not_4(void); +void utoa_to_buf(unsigned n, char *buf, unsigned buflen) +{ + unsigned i, out, res; + if (sizeof(unsigned) != 4) + BUG_sizeof_unsigned_not_4(); + if (buflen) { + out = 0; + for (i = 1000000000; i; i /= 10) { + res = n / i; + if (res || out || i == 1) { + if (!--buflen) break; + out++; + n -= res*i; + *buf++ = '0' + res; + } + } + *buf = '\0'; + } +} + +// Convert signed integer to ascii, like utoa_to_buf() +void itoa_to_buf(int n, char *buf, unsigned buflen) +{ + if (buflen && n<0) { + n = -n; + *buf++ = '-'; + buflen--; + } + utoa_to_buf((unsigned)n, buf, buflen); +} + +// The following two functions use a static buffer, so calling either one a +// second time will overwrite previous results. +// +// The largest 32 bit integer is -2 billion plus null terminator, or 12 bytes. +// Int should always be 32 bits on any remotely Unix-like system, see +// http://www.unix.org/whitepapers/64bit.html for the reasons why. + +static char local_buf[12]; + +// Convert unsigned integer to ascii using a static buffer (returned). +char *utoa(unsigned n) +{ + utoa_to_buf(n, local_buf, sizeof(local_buf)); + + return local_buf; +} + +// Convert signed integer to ascii using a static buffer (returned). +char *itoa(int n) +{ + itoa_to_buf(n, local_buf, sizeof(local_buf)); + + return local_buf; +} + +// Die with an error message if we can't set gid. (Because resource limits may +// limit this user to a given number of processes, and if that fills up the +// setgid() will fail and we'll _still_be_root_, which is bad.) +void xsetgid(gid_t gid) +{ + if (setgid(gid)) bb_error_msg_and_die("setgid"); +} + +// Die with an error message if we can't set uid. (See xsetgid() for why.) +void xsetuid(uid_t uid) +{ + if (setuid(uid)) bb_error_msg_and_die("setuid"); +} + +// Return how long the file at fd is, if there's any way to determine it. +off_t fdlength(int fd) +{ + off_t bottom = 0, top = 0, pos; + long size; + + // If the ioctl works for this, return it. + + if (ioctl(fd, BLKGETSIZE, &size) >= 0) return size*512; + + // FIXME: explain why lseek(SEEK_END) is not used here! + + // If not, do a binary search for the last location we can read. (Some + // block devices don't do BLKGETSIZE right.) + + do { + char temp; + + pos = bottom + (top - bottom) / 2; + + // If we can read from the current location, it's bigger. + + if (lseek(fd, pos, SEEK_SET)>=0 && safe_read(fd, &temp, 1)==1) { + if (bottom == top) bottom = top = (top+1) * 2; + else bottom = pos; + + // If we can't, it's smaller. + + } else { + if (bottom == top) { + if (!top) return 0; + bottom = top/2; + } + else top = pos; + } + } while (bottom + 1 != top); + + return pos + 1; +} + +// Die with an error message if we can't malloc() enough space and do an +// sprintf() into that space. +char *xasprintf(const char *format, ...) +{ + va_list p; + int r; + char *string_ptr; + +#if 1 + // GNU extension + va_start(p, format); + r = vasprintf(&string_ptr, format, p); + va_end(p); +#else + // Bloat for systems that haven't got the GNU extension. + va_start(p, format); + r = vsnprintf(NULL, 0, format, p); + va_end(p); + string_ptr = xmalloc(r+1); + va_start(p, format); + r = vsnprintf(string_ptr, r+1, format, p); + va_end(p); +#endif + + if (r < 0) bb_error_msg_and_die(bb_msg_memory_exhausted); + return string_ptr; +} + +// Die with an error message if we can't copy an entire FILE * to stdout, then +// close that file. +void xprint_and_close_file(FILE *file) +{ + fflush(stdout); + // copyfd outputs error messages for us. + if (bb_copyfd_eof(fileno(file), 1) == -1) + exit(xfunc_error_retval); + + fclose(file); +} + +// Die if we can't chdir to a new path. +void xchdir(const char *path) +{ + if (chdir(path)) + bb_perror_msg_and_die("chdir(%s)", path); +} + +// Print a warning message if opendir() fails, but don't die. +DIR *warn_opendir(const char *path) +{ + DIR *dp; + + if ((dp = opendir(path)) == NULL) { + bb_perror_msg("cannot open '%s'", path); + return NULL; + } + return dp; +} + +// Die with an error message if opendir() fails. +DIR *xopendir(const char *path) +{ + DIR *dp; + + if ((dp = opendir(path)) == NULL) + bb_perror_msg_and_die("cannot open '%s'", path); + return dp; +} + +#ifndef BB_NOMMU +// Die with an error message if we can't daemonize. +void xdaemon(int nochdir, int noclose) +{ + if (daemon(nochdir, noclose)) + bb_perror_msg_and_die("daemon"); +} +#endif + +// Die with an error message if we can't open a new socket. +int xsocket(int domain, int type, int protocol) +{ + int r = socket(domain, type, protocol); + + if (r < 0) bb_perror_msg_and_die("socket"); + + return r; +} + +// Die with an error message if we can't bind a socket to an address. +void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen) +{ + if (bind(sockfd, my_addr, addrlen)) bb_perror_msg_and_die("bind"); +} + +// Die with an error message if we can't listen for connections on a socket. +void xlisten(int s, int backlog) +{ + if (listen(s, backlog)) bb_perror_msg_and_die("listen"); +} + +// xstat() - a stat() which dies on failure with meaningful error message +void xstat(char *name, struct stat *stat_buf) +{ + if (stat(name, stat_buf)) + bb_perror_msg_and_die("can't stat '%s'", name); +} + +/* It is perfectly ok to pass in a NULL for either width or for + * height, in which case that value will not be set. */ +int get_terminal_width_height(int fd, int *width, int *height) +{ + struct winsize win = { 0, 0, 0, 0 }; + int ret = ioctl(fd, TIOCGWINSZ, &win); + + if (height) { + if (!win.ws_row) { + char *s = getenv("LINES"); + if (s) win.ws_row = atoi(s); + } + if (win.ws_row <= 1 || win.ws_row >= 30000) + win.ws_row = 24; + *height = (int) win.ws_row; + } + + if (width) { + if (!win.ws_col) { + char *s = getenv("COLUMNS"); + if (s) win.ws_col = atoi(s); + } + if (win.ws_col <= 1 || win.ws_col >= 30000) + win.ws_col = 80; + *width = (int) win.ws_col; + } + + return ret; +} diff --git a/libbb/xgetcwd.c b/libbb/xgetcwd.c new file mode 100644 index 000000000..0ac450d3b --- /dev/null +++ b/libbb/xgetcwd.c @@ -0,0 +1,44 @@ +/* vi: set sw=4 ts=4: */ +/* + * xgetcwd.c -- return current directory with unlimited length + * Copyright (C) 1992, 1996 Free Software Foundation, Inc. + * Written by David MacKenzie . + * + * Special function for busybox written by Vladimir Oleynik +*/ + +#include "libbb.h" + +/* Amount to increase buffer size by in each try. */ +#define PATH_INCR 32 + +/* Return the current directory, newly allocated, arbitrarily long. + Return NULL and set errno on error. + If argument is not NULL (previous usage allocate memory), call free() +*/ + +char * +xgetcwd(char *cwd) +{ + char *ret; + unsigned path_max; + + path_max = (unsigned) PATH_MAX; + path_max += 2; /* The getcwd docs say to do this. */ + + if (cwd==0) + cwd = xmalloc(path_max); + + while ((ret = getcwd(cwd, path_max)) == NULL && errno == ERANGE) { + path_max += PATH_INCR; + cwd = xrealloc(cwd, path_max); + } + + if (ret == NULL) { + free(cwd); + bb_perror_msg("getcwd"); + return NULL; + } + + return cwd; +} diff --git a/libbb/xgethostbyname.c b/libbb/xgethostbyname.c new file mode 100644 index 000000000..c3158c339 --- /dev/null +++ b/libbb/xgethostbyname.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini xgethostbyname implementation. + * + * Copyright (C) 2001 Matt Kraai . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +struct hostent *xgethostbyname(const char *name) +{ + struct hostent *retval = gethostbyname(name); + if (!retval) + bb_herror_msg_and_die("%s", name); + return retval; +} diff --git a/libbb/xgethostbyname2.c b/libbb/xgethostbyname2.c new file mode 100644 index 000000000..83d538669 --- /dev/null +++ b/libbb/xgethostbyname2.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini xgethostbyname2 implementation. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + + +#ifdef CONFIG_FEATURE_IPV6 +struct hostent *xgethostbyname2(const char *name, int af) +{ + struct hostent *retval; + + if ((retval = gethostbyname2(name, af)) == NULL) + bb_herror_msg_and_die("%s", name); + + return retval; +} +#endif diff --git a/libbb/xreadlink.c b/libbb/xreadlink.c new file mode 100644 index 000000000..14863d9d5 --- /dev/null +++ b/libbb/xreadlink.c @@ -0,0 +1,38 @@ +/* vi: set sw=4 ts=4: */ +/* + * xreadlink.c - safe implementation of readlink. + * Returns a NULL on failure... + */ + +#include + +/* + * NOTE: This function returns a malloced char* that you will have to free + * yourself. You have been warned. + */ + +#include +#include "libbb.h" + +char *xreadlink(const char *path) +{ + enum { GROWBY = 80 }; /* how large we will grow strings by */ + + char *buf = NULL; + int bufsize = 0, readsize = 0; + + do { + buf = xrealloc(buf, bufsize += GROWBY); + readsize = readlink(path, buf, bufsize); /* 1st try */ + if (readsize == -1) { + bb_perror_msg("%s", path); + free(buf); + return NULL; + } + } + while (bufsize < readsize + 1); + + buf[readsize] = '\0'; + + return buf; +} diff --git a/libbb/xregcomp.c b/libbb/xregcomp.c new file mode 100644 index 000000000..4bcb9aedf --- /dev/null +++ b/libbb/xregcomp.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" +#include "xregex.h" + + + +void xregcomp(regex_t *preg, const char *regex, int cflags) +{ + int ret; + if ((ret = regcomp(preg, regex, cflags)) != 0) { + int errmsgsz = regerror(ret, preg, NULL, 0); + char *errmsg = xmalloc(errmsgsz); + regerror(ret, preg, errmsg, errmsgsz); + bb_error_msg_and_die("xregcomp: %s", errmsg); + } +} diff --git a/libpwdgrp/Kbuild b/libpwdgrp/Kbuild new file mode 100644 index 000000000..9e60ef1e5 --- /dev/null +++ b/libpwdgrp/Kbuild @@ -0,0 +1,7 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:=pwd_grp.o uidgid_get.o diff --git a/libpwdgrp/pwd_grp.c b/libpwdgrp/pwd_grp.c new file mode 100644 index 000000000..ac65d4c5b --- /dev/null +++ b/libpwdgrp/pwd_grp.c @@ -0,0 +1,969 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPL v2, or later. See file LICENSE in this tarball. + */ + +/* Nov 6, 2003 Initial version. + * + * NOTE: This implementation is quite strict about requiring all + * field seperators. It also does not allow leading whitespace + * except when processing the numeric fields. glibc is more + * lenient. See the various glibc difference comments below. + * + * TODO: + * Move to dynamic allocation of (currently statically allocated) + * buffers; especially for the group-related functions since + * large group member lists will cause error returns. + * + */ + +#include "libbb.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _PATH_SHADOW +#define _PATH_SHADOW "/etc/shadow" +#endif +#ifndef _PATH_PASSWD +#define _PATH_PASSWD "/etc/passwd" +#endif +#ifndef _PATH_GROUP +#define _PATH_GROUP "/etc/group" +#endif + +/**********************************************************************/ +/* Sizes for statically allocated buffers. */ + +/* If you change these values, also change _SC_GETPW_R_SIZE_MAX and + * _SC_GETGR_R_SIZE_MAX in libc/unistd/sysconf.c to match */ +#define PWD_BUFFER_SIZE 256 +#define GRP_BUFFER_SIZE 256 + +/**********************************************************************/ +/* Prototypes for internal functions. */ + +extern int __parsepwent(void *pw, char *line); +extern int __parsegrent(void *gr, char *line); +extern int __parsespent(void *sp, char *line); + +extern int __pgsreader(int (*__parserfunc)(void *d, char *line), void *data, + char *__restrict line_buff, size_t buflen, FILE *f); + +/**********************************************************************/ +/* For the various fget??ent_r funcs, return + * + * 0: success + * ENOENT: end-of-file encountered + * ERANGE: buflen too small + * other error values possible. See __pgsreader. + * + * Also, *result == resultbuf on success and NULL on failure. + * + * NOTE: glibc difference - For the ENOENT case, glibc also sets errno. + * We do not, as it really isn't an error if we reach the end-of-file. + * Doing so is analogous to having fgetc() set errno on EOF. + */ +/**********************************************************************/ + +int fgetpwent_r(FILE *__restrict stream, struct passwd *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct passwd **__restrict result) +{ + int rv; + + *result = NULL; + + if (!(rv = __pgsreader(__parsepwent, resultbuf, buffer, buflen, stream))) { + *result = resultbuf; + } + + return rv; +} + +int fgetgrent_r(FILE *__restrict stream, struct group *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct group **__restrict result) +{ + int rv; + + *result = NULL; + + if (!(rv = __pgsreader(__parsegrent, resultbuf, buffer, buflen, stream))) { + *result = resultbuf; + } + + return rv; +} + +int fgetspent_r(FILE *__restrict stream, struct spwd *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct spwd **__restrict result) +{ + int rv; + + *result = NULL; + + if (!(rv = __pgsreader(__parsespent, resultbuf, buffer, buflen, stream))) { + *result = resultbuf; + } + + return rv; +} + +/**********************************************************************/ +/* For the various fget??ent funcs, return NULL on failure and a + * pointer to the appropriate struct (statically allocated) on success. + */ +/**********************************************************************/ + +struct passwd *fgetpwent(FILE *stream) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct passwd resultbuf; + struct passwd *result; + + fgetpwent_r(stream, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +struct group *fgetgrent(FILE *stream) +{ + static char buffer[GRP_BUFFER_SIZE]; + static struct group resultbuf; + struct group *result; + + fgetgrent_r(stream, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +extern int fgetspent_r(FILE *__restrict stream, struct spwd *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct spwd **__restrict result); +struct spwd *fgetspent(FILE *stream) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct spwd resultbuf; + struct spwd *result; + + fgetspent_r(stream, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +int sgetspent_r(const char *string, struct spwd *result_buf, + char *buffer, size_t buflen, struct spwd **result) +{ + int rv = ERANGE; + + *result = NULL; + + if (buflen < PWD_BUFFER_SIZE) { + DO_ERANGE: + errno=rv; + goto DONE; + } + + if (string != buffer) { + if (strlen(string) >= buflen) { + goto DO_ERANGE; + } + strcpy(buffer, string); + } + + if (!(rv = __parsespent(result_buf, buffer))) { + *result = result_buf; + } + + DONE: + return rv; +} + +/**********************************************************************/ + +#ifdef GETXXKEY_R_FUNC +#error GETXXKEY_R_FUNC is already defined! +#endif + +#define GETXXKEY_R_FUNC getpwnam_R +#define GETXXKEY_R_PARSER __parsepwent +#define GETXXKEY_R_ENTTYPE struct passwd +#define GETXXKEY_R_TEST(ENT) (!strcmp((ENT)->pw_name, key)) +#define DO_GETXXKEY_R_KEYTYPE const char *__restrict +#define DO_GETXXKEY_R_PATHNAME _PATH_PASSWD +#include "pwd_grp_internal.c" + +#define GETXXKEY_R_FUNC getgrnam_R +#define GETXXKEY_R_PARSER __parsegrent +#define GETXXKEY_R_ENTTYPE struct group +#define GETXXKEY_R_TEST(ENT) (!strcmp((ENT)->gr_name, key)) +#define DO_GETXXKEY_R_KEYTYPE const char *__restrict +#define DO_GETXXKEY_R_PATHNAME _PATH_GROUP +#include "pwd_grp_internal.c" + +#define GETXXKEY_R_FUNC getspnam_R +#define GETXXKEY_R_PARSER __parsespent +#define GETXXKEY_R_ENTTYPE struct spwd +#define GETXXKEY_R_TEST(ENT) (!strcmp((ENT)->sp_namp, key)) +#define DO_GETXXKEY_R_KEYTYPE const char *__restrict +#define DO_GETXXKEY_R_PATHNAME _PATH_SHADOW +#include "pwd_grp_internal.c" + +#define GETXXKEY_R_FUNC getpwuid_R +#define GETXXKEY_R_PARSER __parsepwent +#define GETXXKEY_R_ENTTYPE struct passwd +#define GETXXKEY_R_TEST(ENT) ((ENT)->pw_uid == key) +#define DO_GETXXKEY_R_KEYTYPE uid_t +#define DO_GETXXKEY_R_PATHNAME _PATH_PASSWD +#include "pwd_grp_internal.c" + +#define GETXXKEY_R_FUNC getgrgid_R +#define GETXXKEY_R_PARSER __parsegrent +#define GETXXKEY_R_ENTTYPE struct group +#define GETXXKEY_R_TEST(ENT) ((ENT)->gr_gid == key) +#define DO_GETXXKEY_R_KEYTYPE gid_t +#define DO_GETXXKEY_R_PATHNAME _PATH_GROUP +#include "pwd_grp_internal.c" + +/**********************************************************************/ + +struct passwd *getpwuid(uid_t uid) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct passwd resultbuf; + struct passwd *result; + + getpwuid_r(uid, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +struct group *getgrgid(gid_t gid) +{ + static char buffer[GRP_BUFFER_SIZE]; + static struct group resultbuf; + struct group *result; + + getgrgid_r(gid, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +/* This function is non-standard and is currently not built. It seems + * to have been created as a reentrant version of the non-standard + * functions getspuid. Why getspuid was added, I do not know. */ + +int getspuid_r(uid_t uid, struct spwd *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct spwd **__restrict result) +{ + int rv; + struct passwd *pp; + struct passwd password; + char pwd_buff[PWD_BUFFER_SIZE]; + + *result = NULL; + if (!(rv = getpwuid_r(uid, &password, pwd_buff, sizeof(pwd_buff), &pp))) { + rv = getspnam_r(password.pw_name, resultbuf, buffer, buflen, result); + } + + return rv; +} + +/* This function is non-standard and is currently not built. + * Why it was added, I do not know. */ + +struct spwd *getspuid(uid_t uid) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct spwd resultbuf; + struct spwd *result; + + getspuid_r(uid, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +struct passwd *getpwnam(const char *name) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct passwd resultbuf; + struct passwd *result; + + getpwnam_r(name, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +struct group *getgrnam(const char *name) +{ + static char buffer[GRP_BUFFER_SIZE]; + static struct group resultbuf; + struct group *result; + + getgrnam_r(name, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +struct spwd *getspnam(const char *name) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct spwd resultbuf; + struct spwd *result; + + getspnam_r(name, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +int getpw(uid_t uid, char *buf) +{ + struct passwd resultbuf; + struct passwd *result; + char buffer[PWD_BUFFER_SIZE]; + + if (!buf) { + errno=EINVAL; + } else if (!getpwuid_r(uid, &resultbuf, buffer, sizeof(buffer), &result)) { + if (sprintf(buf, "%s:%s:%lu:%lu:%s:%s:%s\n", + resultbuf.pw_name, resultbuf.pw_passwd, + (unsigned long)(resultbuf.pw_uid), + (unsigned long)(resultbuf.pw_gid), + resultbuf.pw_gecos, resultbuf.pw_dir, + resultbuf.pw_shell) >= 0 + ) { + return 0; + } + } + + return -1; +} + +/**********************************************************************/ + +#if defined CONFIG_USE_BB_THREADSAFE_SHADOW && defined PTHREAD_MUTEX_INITIALIZER +static pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER; +# define LOCK pthread_mutex_lock(&mylock) +# define UNLOCK pthread_mutex_unlock(&mylock); +#else +# define LOCK ((void) 0) +# define UNLOCK ((void) 0) +#endif + +static FILE *pwf /*= NULL*/; +void setpwent(void) +{ + LOCK; + if (pwf) { + rewind(pwf); + } + UNLOCK; +} + +void endpwent(void) +{ + LOCK; + if (pwf) { + fclose(pwf); + pwf = NULL; + } + UNLOCK; +} + + +int getpwent_r(struct passwd *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct passwd **__restrict result) +{ + int rv; + + LOCK; + *result = NULL; /* In case of error... */ + + if (!pwf) { + if (!(pwf = fopen(_PATH_PASSWD, "r"))) { + rv = errno; + goto ERR; + } + } + + if (!(rv = __pgsreader(__parsepwent, resultbuf, + buffer, buflen, pwf))) { + *result = resultbuf; + } + + ERR: + UNLOCK; + return rv; +} + +static FILE *grf /*= NULL*/; +void setgrent(void) +{ + LOCK; + if (grf) { + rewind(grf); + } + UNLOCK; +} + +void endgrent(void) +{ + LOCK; + if (grf) { + fclose(grf); + grf = NULL; + } + UNLOCK; +} + +int getgrent_r(struct group *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct group **__restrict result) +{ + int rv; + + LOCK; + *result = NULL; /* In case of error... */ + + if (!grf) { + if (!(grf = fopen(_PATH_GROUP, "r"))) { + rv = errno; + goto ERR; + } + } + + if (!(rv = __pgsreader(__parsegrent, resultbuf, + buffer, buflen, grf))) { + *result = resultbuf; + } + + ERR: + UNLOCK; + return rv; +} + +static FILE *spf /*= NULL*/; +void setspent(void) +{ + LOCK; + if (spf) { + rewind(spf); + } + UNLOCK; +} + +void endspent(void) +{ + LOCK; + if (spf) { + fclose(spf); + spf = NULL; + } + UNLOCK; +} + +int getspent_r(struct spwd *resultbuf, char *buffer, + size_t buflen, struct spwd **result) +{ + int rv; + + LOCK; + *result = NULL; /* In case of error... */ + + if (!spf) { + if (!(spf = fopen(_PATH_SHADOW, "r"))) { + rv = errno; + goto ERR; + } + } + + if (!(rv = __pgsreader(__parsespent, resultbuf, + buffer, buflen, spf))) { + *result = resultbuf; + } + + ERR: + UNLOCK; + return rv; +} + +struct passwd *getpwent(void) +{ + static char line_buff[PWD_BUFFER_SIZE]; + static struct passwd pwd; + struct passwd *result; + + getpwent_r(&pwd, line_buff, sizeof(line_buff), &result); + return result; +} + +struct group *getgrent(void) +{ + static char line_buff[GRP_BUFFER_SIZE]; + static struct group gr; + struct group *result; + + getgrent_r(&gr, line_buff, sizeof(line_buff), &result); + return result; +} + +struct spwd *getspent(void) +{ + static char line_buff[PWD_BUFFER_SIZE]; + static struct spwd spwd; + struct spwd *result; + + getspent_r(&spwd, line_buff, sizeof(line_buff), &result); + return result; +} + +struct spwd *sgetspent(const char *string) +{ + static char line_buff[PWD_BUFFER_SIZE]; + static struct spwd spwd; + struct spwd *result; + + sgetspent_r(string, &spwd, line_buff, sizeof(line_buff), &result); + return result; +} + +int initgroups(const char *user, gid_t gid) +{ + FILE *grfile; + gid_t *group_list; + int num_groups, rv; + char **m; + struct group group; + char buff[PWD_BUFFER_SIZE]; + + rv = -1; + + /* We alloc space for 8 gids at a time. */ + if (((group_list = (gid_t *) malloc(8*sizeof(gid_t *))) != NULL) + && ((grfile = fopen(_PATH_GROUP, "r")) != NULL) + ) { + + *group_list = gid; + num_groups = 1; + + while (!__pgsreader(__parsegrent, &group, buff, sizeof(buff), grfile)) { + assert(group.gr_mem); /* Must have at least a NULL terminator. */ + if (group.gr_gid != gid) { + for (m=group.gr_mem ; *m ; m++) { + if (!strcmp(*m, user)) { + if (!(num_groups & 7)) { + gid_t *tmp = (gid_t *) + realloc(group_list, + (num_groups+8) * sizeof(gid_t *)); + if (!tmp) { + rv = -1; + goto DO_CLOSE; + } + group_list = tmp; + } + group_list[num_groups++] = group.gr_gid; + break; + } + } + } + } + + rv = setgroups(num_groups, group_list); + DO_CLOSE: + fclose(grfile); + } + + /* group_list will be NULL if initial malloc failed, which may trigger + * warnings from various malloc debuggers. */ + free(group_list); + return rv; +} + +int putpwent(const struct passwd *__restrict p, FILE *__restrict f) +{ + int rv = -1; + + if (!p || !f) { + errno=EINVAL; + } else { + /* No extra thread locking is needed above what fprintf does. */ + if (fprintf(f, "%s:%s:%lu:%lu:%s:%s:%s\n", + p->pw_name, p->pw_passwd, + (unsigned long)(p->pw_uid), + (unsigned long)(p->pw_gid), + p->pw_gecos, p->pw_dir, p->pw_shell) >= 0 + ) { + rv = 0; + } + } + + return rv; +} + +int putgrent(const struct group *__restrict p, FILE *__restrict f) +{ + static const char format[] = ",%s"; + char **m; + const char *fmt; + int rv = -1; + + if (!p || !f) { /* Sigh... glibc checks. */ + errno=EINVAL; + } else { + if (fprintf(f, "%s:%s:%lu:", + p->gr_name, p->gr_passwd, + (unsigned long)(p->gr_gid)) >= 0 + ) { + + fmt = format + 1; + + assert(p->gr_mem); + m = p->gr_mem; + + do { + if (!*m) { + if (fputc('\n', f) >= 0) { + rv = 0; + } + break; + } + if (fprintf(f, fmt, *m) < 0) { + break; + } + ++m; + fmt = format; + } while (1); + + } + + } + + return rv; +} + +static const unsigned char _sp_off[] = { + offsetof(struct spwd, sp_lstchg), /* 2 - not a char ptr */ + offsetof(struct spwd, sp_min), /* 3 - not a char ptr */ + offsetof(struct spwd, sp_max), /* 4 - not a char ptr */ + offsetof(struct spwd, sp_warn), /* 5 - not a char ptr */ + offsetof(struct spwd, sp_inact), /* 6 - not a char ptr */ + offsetof(struct spwd, sp_expire), /* 7 - not a char ptr */ +}; + +int putspent(const struct spwd *p, FILE *stream) +{ + static const char ld_format[] = "%ld:"; + const char *f; + long int x; + int i; + int rv = -1; + + /* Unlike putpwent and putgrent, glibc does not check the args. */ + if (fprintf(stream, "%s:%s:", p->sp_namp, + (p->sp_pwdp ? p->sp_pwdp : "")) < 0 + ) { + goto DO_UNLOCK; + } + + for (i=0 ; i < sizeof(_sp_off) ; i++) { + f = ld_format; + if ((x = *(const long int *)(((const char *) p) + _sp_off[i])) == -1) { + f += 3; + } + if (fprintf(stream, f, x) < 0) { + goto DO_UNLOCK; + } + } + + if ((p->sp_flag != ~0UL) && (fprintf(stream, "%lu", p->sp_flag) < 0)) { + goto DO_UNLOCK; + } + + if (fputc('\n', stream) > 0) { + rv = 0; + } + +DO_UNLOCK: + return rv; +} + +/**********************************************************************/ +/* Internal uClibc functions. */ +/**********************************************************************/ + +static const unsigned char pw_off[] = { + offsetof(struct passwd, pw_name), /* 0 */ + offsetof(struct passwd, pw_passwd), /* 1 */ + offsetof(struct passwd, pw_uid), /* 2 - not a char ptr */ + offsetof(struct passwd, pw_gid), /* 3 - not a char ptr */ + offsetof(struct passwd, pw_gecos), /* 4 */ + offsetof(struct passwd, pw_dir), /* 5 */ + offsetof(struct passwd, pw_shell) /* 6 */ +}; + +int __parsepwent(void *data, char *line) +{ + char *endptr; + char *p; + int i; + + i = 0; + do { + p = ((char *) ((struct passwd *) data)) + pw_off[i]; + + if ((i & 6) ^ 2) { /* i!=2 and i!=3 */ + *((char **) p) = line; + if (i==6) { + return 0; + } + /* NOTE: glibc difference - glibc allows omission of + * ':' seperators after the gid field if all remaining + * entries are empty. We require all separators. */ + if (!(line = strchr(line, ':'))) { + break; + } + } else { + unsigned long t = strtoul(line, &endptr, 10); + /* Make sure we had at least one digit, and that the + * failing char is the next field seperator ':'. See + * glibc difference note above. */ + /* TODO: Also check for leading whitespace? */ + if ((endptr == line) || (*endptr != ':')) { + break; + } + line = endptr; + if (i & 1) { /* i == 3 -- gid */ + *((gid_t *) p) = t; + } else { /* i == 2 -- uid */ + *((uid_t *) p) = t; + } + } + + *line++ = 0; + ++i; + } while (1); + + return -1; +} + +/**********************************************************************/ + +static const unsigned char gr_off[] = { + offsetof(struct group, gr_name), /* 0 */ + offsetof(struct group, gr_passwd), /* 1 */ + offsetof(struct group, gr_gid) /* 2 - not a char ptr */ +}; + +int __parsegrent(void *data, char *line) +{ + char *endptr; + char *p; + int i; + char **members; + char *end_of_buf; + + end_of_buf = ((struct group *) data)->gr_name; /* Evil hack! */ + i = 0; + do { + p = ((char *) ((struct group *) data)) + gr_off[i]; + + if (i < 2) { + *((char **) p) = line; + if (!(line = strchr(line, ':'))) { + break; + } + *line++ = 0; + ++i; + } else { + *((gid_t *) p) = strtoul(line, &endptr, 10); + + /* NOTE: glibc difference - glibc allows omission of the + * trailing colon when there is no member list. We treat + * this as an error. */ + + /* Make sure we had at least one digit, and that the + * failing char is the next field seperator ':'. See + * glibc difference note above. */ + if ((endptr == line) || (*endptr != ':')) { + break; + } + + i = 1; /* Count terminating NULL ptr. */ + p = endptr; + + if (p[1]) { /* We have a member list to process. */ + /* Overwrite the last ':' with a ',' before counting. + * This allows us to test for initial ',' and adds + * one ',' so that the ',' count equals the member + * count. */ + *p = ','; + do { + /* NOTE: glibc difference - glibc allows and trims leading + * (but not trailing) space. We treat this as an error. */ + /* NOTE: glibc difference - glibc allows consecutive and + * trailing commas, and ignores "empty string" users. We + * treat this as an error. */ + if (*p == ',') { + ++i; + *p = 0; /* nul-terminate each member string. */ + if (!*++p || (*p == ',') || isspace(*p)) { + goto ERR; + } + } + } while (*++p); + } + + /* Now align (p+1), rounding up. */ + /* Assumes sizeof(char **) is a power of 2. */ + members = (char **)( (((intptr_t) p) + sizeof(char **)) + & ~((intptr_t)(sizeof(char **) - 1)) ); + + if (((char *)(members + i)) > end_of_buf) { /* No space. */ + break; + } + + ((struct group *) data)->gr_mem = members; + + if (--i) { + p = endptr; /* Pointing to char prior to first member. */ + do { + *members++ = ++p; + if (!--i) break; + while (*++p) {} + } while (1); + } + *members = NULL; + + return 0; + } + } while (1); + + ERR: + return -1; +} + +/**********************************************************************/ + +static const unsigned char sp_off[] = { + offsetof(struct spwd, sp_namp), /* 0 */ + offsetof(struct spwd, sp_pwdp), /* 1 */ + offsetof(struct spwd, sp_lstchg), /* 2 - not a char ptr */ + offsetof(struct spwd, sp_min), /* 3 - not a char ptr */ + offsetof(struct spwd, sp_max), /* 4 - not a char ptr */ + offsetof(struct spwd, sp_warn), /* 5 - not a char ptr */ + offsetof(struct spwd, sp_inact), /* 6 - not a char ptr */ + offsetof(struct spwd, sp_expire), /* 7 - not a char ptr */ + offsetof(struct spwd, sp_flag) /* 8 - not a char ptr */ +}; + +int __parsespent(void *data, char * line) +{ + char *endptr; + char *p; + int i; + + i = 0; + do { + p = ((char *) ((struct spwd *) data)) + sp_off[i]; + if (i < 2) { + *((char **) p) = line; + if (!(line = strchr(line, ':'))) { + break; + } + } else { + *((long *) p) = (long) strtoul(line, &endptr, 10); + + if (endptr == line) { + *((long *) p) = ((i != 8) ? -1L : ((long)(~0UL))); + } + + line = endptr; + + if (i == 8) { + if (!*endptr) { + return 0; + } + break; + } + + if (*endptr != ':') { + break; + } + + } + + *line++ = 0; + ++i; + } while (1); + + return EINVAL; +} + +/**********************************************************************/ + +/* Reads until if EOF, or until if finds a line which fits in the buffer + * and for which the parser function succeeds. + * + * Returns 0 on success and ENOENT for end-of-file (glibc concession). + */ + +int __pgsreader(int (*__parserfunc)(void *d, char *line), void *data, + char *__restrict line_buff, size_t buflen, FILE *f) +{ + int line_len; + int skip; + int rv = ERANGE; + + if (buflen < PWD_BUFFER_SIZE) { + errno=rv; + } else { + skip = 0; + do { + if (!fgets(line_buff, buflen, f)) { + if (feof(f)) { + rv = ENOENT; + } + break; + } + + line_len = strlen(line_buff) - 1; /* strlen() must be > 0. */ + if (line_buff[line_len] == '\n') { + line_buff[line_len] = 0; + } else if (line_len + 2 == buflen) { /* line too long */ + ++skip; + continue; + } + + if (skip) { + --skip; + continue; + } + + /* NOTE: glibc difference - glibc strips leading whitespace from + * records. We do not allow leading whitespace. */ + + /* Skip empty lines, comment lines, and lines with leading + * whitespace. */ + if (*line_buff && (*line_buff != '#') && !isspace(*line_buff)) { + if (__parserfunc == __parsegrent) { /* Do evil group hack. */ + /* The group entry parsing function needs to know where + * the end of the buffer is so that it can construct the + * group member ptr table. */ + ((struct group *) data)->gr_name = line_buff + buflen; + } + + if (!__parserfunc(data, line_buff)) { + rv = 0; + break; + } + } + } while (1); + + } + + return rv; +} + +/**********************************************************************/ diff --git a/libpwdgrp/pwd_grp_internal.c b/libpwdgrp/pwd_grp_internal.c new file mode 100644 index 000000000..866ed3699 --- /dev/null +++ b/libpwdgrp/pwd_grp_internal.c @@ -0,0 +1,62 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPL v2, or later. See file LICENSE in this tarball. + */ + +/* Nov 6, 2003 Initial version. + * + * NOTE: This implementation is quite strict about requiring all + * field seperators. It also does not allow leading whitespace + * except when processing the numeric fields. glibc is more + * lenient. See the various glibc difference comments below. + * + * TODO: + * Move to dynamic allocation of (currently statically allocated) + * buffers; especially for the group-related functions since + * large group member lists will cause error returns. + * + */ + +#ifndef GETXXKEY_R_FUNC +#error GETXXKEY_R_FUNC is not defined! +#endif + +int GETXXKEY_R_FUNC(DO_GETXXKEY_R_KEYTYPE key, + GETXXKEY_R_ENTTYPE *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + GETXXKEY_R_ENTTYPE **__restrict result) +{ + FILE *stream; + int rv; + + *result = NULL; + + stream = fopen(DO_GETXXKEY_R_PATHNAME, "r"); + if (!stream) + return errno; + while (1) { + rv = __pgsreader(GETXXKEY_R_PARSER, resultbuf, buffer, buflen, stream); + if (!rv) { + if (GETXXKEY_R_TEST(resultbuf)) { /* Found key? */ + *result = resultbuf; + break; + } + } else { + if (rv == ENOENT) { /* end-of-file encountered. */ + rv = 0; + } + break; + } + } + fclose(stream); + + return rv; +} + +#undef GETXXKEY_R_FUNC +#undef GETXXKEY_R_PARSER +#undef GETXXKEY_R_ENTTYPE +#undef GETXXKEY_R_TEST +#undef DO_GETXXKEY_R_KEYTYPE +#undef DO_GETXXKEY_R_PATHNAME diff --git a/libpwdgrp/uidgid_get.c b/libpwdgrp/uidgid_get.c new file mode 100644 index 000000000..a2d02a84f --- /dev/null +++ b/libpwdgrp/uidgid_get.c @@ -0,0 +1,49 @@ +#include "busybox.h" + +unsigned uidgid_get(struct bb_uidgid_t *u, const char *ug /*, unsigned dogrp */) +{ + struct passwd *pwd; + struct group *gr; + const char *g; + + /* g = 0; if (dogrp) g = strchr(ug, ':'); */ + g = strchr(ug, ':'); + if (g) { + int sz = (++g) - ug; + char buf[sz]; + safe_strncpy(buf, ug, sz); + pwd = getpwnam(buf); + } else + pwd = getpwnam(ug); + if (!pwd) + return 0; + u->uid = pwd->pw_uid; + u->gid = pwd->pw_gid; + if (g) { + gr = getgrnam(g); + if (!gr) return 0; + u->gid = gr->gr_gid; + } + return 1; +} + +#if 0 +#include +int main() +{ + unsigned u; + struct bb_uidgid_t ug; + u = uidgid_get(&ug, "apache"); + printf("%u = %u:%u\n", u, ug.uid, ug.gid); + ug.uid = ug.gid = 1111; + u = uidgid_get(&ug, "apache"); + printf("%u = %u:%u\n", u, ug.uid, ug.gid); + ug.uid = ug.gid = 1111; + u = uidgid_get(&ug, "apache:users"); + printf("%u = %u:%u\n", u, ug.uid, ug.gid); + ug.uid = ug.gid = 1111; + u = uidgid_get(&ug, "apache:users"); + printf("%u = %u:%u\n", u, ug.uid, ug.gid); + return 0; +} +#endif diff --git a/loginutils/Config.in b/loginutils/Config.in new file mode 100644 index 000000000..2ad141511 --- /dev/null +++ b/loginutils/Config.in @@ -0,0 +1,183 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Login/Password Management Utilities" + +config FEATURE_SHADOWPASSWDS + bool "Support for shadow passwords" + default n + help + Build support for shadow password in /etc/shadow. This file is only + readable by root and thus the encrypted passwords are no longer + publicly readable. + +config USE_BB_SHADOW + bool " Use busybox shadow password functions" + default y + depends on USE_BB_PWD_GRP && FEATURE_SHADOWPASSWDS + help + If you leave this disabled, busybox will use the system's shadow + password handling functions. And if you are using the GNU C library + (glibc), you will then need to install the /etc/nsswitch.conf + configuration file and the required /lib/libnss_* libraries in + order for the shadow password functions to work. This generally + makes your embedded system quite a bit larger. + + Enabling this option will cause busybox to directly access the + system's /etc/shadow file when handling shadow passwords. This + makes your system smaller and I will get fewer emails asking about + how glibc NSS works). When this option is enabled, you will not be + able to use PAM to access shadow passwords from remote LDAP + password servers and whatnot. + +config USE_BB_PWD_GRP + bool "Use internal password and group functions rather than system functions" + default n + help + If you leave this disabled, busybox will use the system's password + and group functions. And if you are using the GNU C library + (glibc), you will then need to install the /etc/nsswitch.conf + configuration file and the required /lib/libnss_* libraries in + order for the password and group functions to work. This generally + makes your embedded system quite a bit larger. + + Enabling this option will cause busybox to directly access the + system's /etc/password, /etc/group files (and your system will be + smaller, and I will get fewer emails asking about how glibc NSS + works). When this option is enabled, you will not be able to use + PAM to access remote LDAP password servers and whatnot. And if you + want hostname resolution to work with glibc, you still need the + /lib/libnss_* libraries. + + If you enable this option, it will add about 1.5k to busybox. + +config ADDGROUP + bool "addgroup" + default n + help + Utility for creating a new group account. + +config DELGROUP + bool "delgroup" + default n + help + Utility for deleting a group account. + +config ADDUSER + bool "adduser" + default n + help + Utility for creating a new user account. + +config DELUSER + bool "deluser" + default n + help + Utility for deleting a user account. + +config GETTY + bool "getty" + default n + select FEATURE_SYSLOG + help + getty lets you log in on a tty, it is normally invoked by init. + +config FEATURE_UTMP + bool "Support utmp file" + depends on GETTY || LOGIN || SU || WHO + default n + help + The file /var/run/utmp is used to track who is currently logged in. + +config FEATURE_WTMP + bool "Support wtmp file" + depends on GETTY || LOGIN || SU || LAST + default n + select FEATURE_UTMP + help + The file /var/run/wtmp is used to track when user's have logged into + and logged out of the system. + +config LOGIN + bool "login" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + login is used when signing onto a system. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +config LOGIN_SCRIPTS + bool "Support for login scripts" + depends on LOGIN + default n + help + Enable this if you want login to execute $LOGIN_PRE_SUID_SCRIPT + just prior to switching from root to logged-in user. + +config FEATURE_SECURETTY + bool "Support for /etc/securetty" + default y + depends on LOGIN + help + The file /etc/securetty is used by (some versions of) login(1). + The file contains the device names of tty lines (one per line, + without leading /dev/) on which root is allowed to login. + +config PASSWD + bool "passwd" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + passwd changes passwords for user and group accounts. A normal user + may only change the password for his/her own account, the super user + may change the password for any account. The administrator of a group + may change the password for the group. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +config SU + bool "su" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + su is used to become another user during a login session. + Invoked without a username, su defaults to becoming the super user. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +config SU_SYSLOG + bool "Support for syslog in su" + default y + depends on SU + help + Enables support for syslog in su. + +config SULOGIN + bool "sulogin" + default n + select FEATURE_SYSLOG + help + sulogin is invoked when the system goes into single user + mode (this is done through an entry in inittab). + +config VLOCK + bool "vlock" + default n + select FEATURE_SUID + help + Build the "vlock" applet which allows you to lock (virtual) terminals. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +endmenu + diff --git a/loginutils/Kbuild b/loginutils/Kbuild new file mode 100644 index 000000000..6c9d193e1 --- /dev/null +++ b/loginutils/Kbuild @@ -0,0 +1,17 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_ADDGROUP) += addgroup.o +lib-$(CONFIG_ADDUSER) += adduser.o +lib-$(CONFIG_GETTY) += getty.o +lib-$(CONFIG_LOGIN) += login.o +lib-$(CONFIG_PASSWD) += passwd.o +lib-$(CONFIG_SU) += su.o +lib-$(CONFIG_SULOGIN) += sulogin.o +lib-$(CONFIG_VLOCK) += vlock.o +lib-$(CONFIG_DELUSER) += deluser.o +lib-$(CONFIG_DELGROUP) += deluser.o diff --git a/loginutils/addgroup.c b/loginutils/addgroup.c new file mode 100644 index 000000000..0172e6041 --- /dev/null +++ b/loginutils/addgroup.c @@ -0,0 +1,107 @@ +/* vi: set sw=4 ts=4: */ +/* + * addgroup - add users to /etc/passwd and /etc/shadow + * + * Copyright (C) 1999 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "busybox.h" + +/* make sure gr_name isn't taken, make sure gid is kosher + * return 1 on failure */ +static int group_study(struct group *g) +{ + FILE *etc_group; + gid_t desired; + + struct group *grp; + const int max = 65000; + + etc_group = xfopen(bb_path_group_file, "r"); + + /* make sure gr_name isn't taken, make sure gid is kosher */ + desired = g->gr_gid; + while ((grp = fgetgrent(etc_group))) { + if ((strcmp(grp->gr_name, g->gr_name)) == 0) { + bb_error_msg_and_die("%s: group already in use", g->gr_name); + } + if ((desired) && grp->gr_gid == desired) { + bb_error_msg_and_die("%d: gid already in use", + desired); + } + if ((grp->gr_gid > g->gr_gid) && (grp->gr_gid < max)) { + g->gr_gid = grp->gr_gid; + } + } + fclose(etc_group); + + /* gid */ + if (desired) { + g->gr_gid = desired; + } else { + g->gr_gid++; + } + /* return 1; */ + return 0; +} + +/* append a new user to the passwd file */ +static int addgroup(char *group, gid_t gid, const char *user) +{ + FILE *file; + struct group gr; + + /* make sure gid and group haven't already been allocated */ + gr.gr_gid = gid; + gr.gr_name = group; + if (group_study(&gr)) + return 1; + + /* add entry to group */ + file = xfopen(bb_path_group_file, "a"); + /* group:passwd:gid:userlist */ + fprintf(file, "%s:%s:%d:%s\n", group, "x", gr.gr_gid, user); + fclose(file); + +#if ENABLE_FEATURE_SHADOWPASSWDS + file = xfopen(bb_path_gshadow_file, "a"); + fprintf(file, "%s:!::\n", group); + fclose(file); +#endif + + /* return 1; */ + return 0; +} + +/* + * addgroup will take a login_name as its first parameter. + * + * gid + * + * can be customized via command-line parameters. + * ________________________________________________________________________ */ +int addgroup_main(int argc, char **argv) +{ + char *group; + gid_t gid = 0; + + /* check for min, max and missing args and exit on error */ + opt_complementary = "-1:?2:?"; + if (getopt32(argc, argv, "g:", &group)) { + gid = xatoul_range(group, 0, (gid_t)ULONG_MAX); + } + /* move past the commandline options */ + argv += optind; + + /* need to be root */ + if (geteuid()) { + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + } + + /* werk */ + return addgroup(argv[0], gid, (argv[1]) ? argv[1] : ""); +} diff --git a/loginutils/adduser.c b/loginutils/adduser.c new file mode 100644 index 000000000..44516ef5a --- /dev/null +++ b/loginutils/adduser.c @@ -0,0 +1,194 @@ +/* vi: set sw=4 ts=4: */ +/* + * adduser - add users to /etc/passwd and /etc/shadow + * + * Copyright (C) 1999 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +#define DONT_SET_PASS (1 << 4) +#define DONT_MAKE_HOME (1 << 6) + + +/* remix */ +/* EDR recoded such that the uid may be passed in *p */ +static int passwd_study(const char *filename, struct passwd *p) +{ + struct passwd *pw; + FILE *passwd; + + const int min = 500; + const int max = 65000; + + passwd = xfopen(filename, "r"); + + /* EDR if uid is out of bounds, set to min */ + if ((p->pw_uid > max) || (p->pw_uid < min)) + p->pw_uid = min; + + /* stuff to do: + * make sure login isn't taken; + * find free uid and gid; + */ + while ((pw = fgetpwent(passwd))) { + if (strcmp(pw->pw_name, p->pw_name) == 0) { + /* return 0; */ + return 1; + } + if ((pw->pw_uid >= p->pw_uid) && (pw->pw_uid < max) + && (pw->pw_uid >= min)) { + p->pw_uid = pw->pw_uid + 1; + } + } + + if (p->pw_gid == 0) { + /* EDR check for an already existing gid */ + while (getgrgid(p->pw_uid) != NULL) + p->pw_uid++; + + /* EDR also check for an existing group definition */ + if (getgrnam(p->pw_name) != NULL) + return 3; + + /* EDR create new gid always = uid */ + p->pw_gid = p->pw_uid; + } + + /* EDR bounds check */ + if ((p->pw_uid > max) || (p->pw_uid < min)) + return 2; + + /* return 1; */ + return 0; +} + +static void addgroup_wrapper(struct passwd *p) +{ + char *cmd; + + cmd = xasprintf("addgroup -g %d \"%s\"", p->pw_gid, p->pw_name); + system(cmd); + free(cmd); +} + +static void passwd_wrapper(const char *login) ATTRIBUTE_NORETURN; + +static void passwd_wrapper(const char *login) +{ + static const char prog[] = "passwd"; + execlp(prog, prog, login, NULL); + bb_error_msg_and_die("failed to execute '%s', you must set the password for '%s' manually", prog, login); +} + +/* putpwent(3) remix */ +static int adduser(struct passwd *p, unsigned long flags) +{ + FILE *file; + int addgroup = !p->pw_gid; + + /* make sure everything is kosher and setup uid && gid */ + file = xfopen(bb_path_passwd_file, "a"); + fseek(file, 0, SEEK_END); + + switch (passwd_study(bb_path_passwd_file, p)) { + case 1: + bb_error_msg_and_die("%s: login already in use", p->pw_name); + case 2: + bb_error_msg_and_die("illegal uid or no uids left"); + case 3: + bb_error_msg_and_die("%s: group name already in use", p->pw_name); + } + + /* add to passwd */ + if (putpwent(p, file) == -1) { + bb_perror_nomsg_and_die(); + } + fclose(file); + +#if ENABLE_FEATURE_SHADOWPASSWDS + /* add to shadow if necessary */ + file = xfopen(bb_path_shadow_file, "a"); + fseek(file, 0, SEEK_END); + fprintf(file, "%s:!:%ld:%d:%d:%d:::\n", + p->pw_name, /* username */ + time(NULL) / 86400, /* sp->sp_lstchg */ + 0, /* sp->sp_min */ + 99999, /* sp->sp_max */ + 7); /* sp->sp_warn */ + fclose(file); +#endif + + /* add to group */ + /* addgroup should be responsible for dealing w/ gshadow */ + /* if using a pre-existing group, don't create one */ + if (addgroup) addgroup_wrapper(p); + + /* Clear the umask for this process so it doesn't + * * screw up the permissions on the mkdir and chown. */ + umask(0); + if (!(flags & DONT_MAKE_HOME)) { + /* Set the owner and group so it is owned by the new user, + then fix up the permissions to 2755. Can't do it before + since chown will clear the setgid bit */ + if (mkdir(p->pw_dir, 0755) + || chown(p->pw_dir, p->pw_uid, p->pw_gid) + || chmod(p->pw_dir, 02755)) { + bb_perror_msg("%s", p->pw_dir); + } + } + + if (!(flags & DONT_SET_PASS)) { + /* interactively set passwd */ + passwd_wrapper(p->pw_name); + } + + return 0; +} + +/* + * adduser will take a login_name as its first parameter. + * + * home + * shell + * gecos + * + * can be customized via command-line parameters. + * ________________________________________________________________________ */ +int adduser_main(int argc, char **argv) +{ + struct passwd pw; + const char *usegroup = NULL; + unsigned long flags; + + pw.pw_gecos = "Linux User,,,"; + pw.pw_shell = (char *)DEFAULT_SHELL; + pw.pw_dir = NULL; + + /* check for min, max and missing args and exit on error */ + opt_complementary = "-1:?1:?"; + flags = getopt32(argc, argv, "h:g:s:G:DSH", &pw.pw_dir, &pw.pw_gecos, &pw.pw_shell, &usegroup); + + /* got root? */ + if(geteuid()) { + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + } + + /* create string for $HOME if not specified already */ + if (!pw.pw_dir) { + snprintf(bb_common_bufsiz1, BUFSIZ, "/home/%s", argv[optind]); + pw.pw_dir = &bb_common_bufsiz1[0]; + } + + /* create a passwd struct */ + pw.pw_name = argv[optind]; + pw.pw_passwd = "x"; + pw.pw_uid = 0; + pw.pw_gid = (usegroup) ? bb_xgetgrnam(usegroup) : 0; /* exits on failure */ + + /* grand finale */ + return adduser(&pw, flags); +} diff --git a/loginutils/deluser.c b/loginutils/deluser.c new file mode 100644 index 000000000..869b22013 --- /dev/null +++ b/loginutils/deluser.c @@ -0,0 +1,83 @@ +/* vi: set sw=4 ts=4: */ +/* + * deluser (remove lusers from the system ;) for TinyLogin + * + * Copyright (C) 1999 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * Unified with delgroup by Tito Ragusa + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + * + */ + +#include "busybox.h" + +static void del_line_matching(const char *login, const char *filename) +{ + char *line; + FILE *passwd; + int len = strlen(login); + int found = 0; + llist_t *plist = NULL; + + passwd = fopen_or_warn(filename, "r"); + if (!passwd) return; + + while ((line = xmalloc_fgets(passwd))) { + if (!strncmp(line, login, len) + && line[len] == ':' + ) { + found++; + free(line); + } else { + llist_add_to_end(&plist, line); + } + } + + if (!ENABLE_FEATURE_CLEAN_UP) { + if (!found) { + bb_error_msg("can't find '%s' in '%s'", login, filename); + return; + } + passwd = fopen_or_warn(filename, "w"); + if (passwd) + while ((line = llist_pop(&plist))) + fputs(line, passwd); + } else { + if (!found) { + bb_error_msg("can't find '%s' in '%s'", login, filename); + goto clean_up; + } + fclose(passwd); + passwd = fopen_or_warn(filename, "w"); + if (passwd) { + clean_up: + while ((line = llist_pop(&plist))) { + if (found) fputs(line, passwd); + free(line); + } + fclose(passwd); + } + } +} + +int deluser_main(int argc, char **argv) +{ + if (argc != 2) + bb_show_usage(); + + if (ENABLE_DELUSER + && (!ENABLE_DELGROUP || applet_name[3] == 'u') + ) { + del_line_matching(argv[1], bb_path_passwd_file); + if (ENABLE_FEATURE_SHADOWPASSWDS) + del_line_matching(argv[1], bb_path_shadow_file); + } + del_line_matching(argv[1], bb_path_group_file); + if (ENABLE_FEATURE_SHADOWPASSWDS) + del_line_matching(argv[1], bb_path_gshadow_file); + + return EXIT_SUCCESS; +} + +/* $Id: deluser.c,v 1.4 2003/07/14 20:20:45 andersen Exp $ */ diff --git a/loginutils/getty.c b/loginutils/getty.c new file mode 100644 index 000000000..a85e52306 --- /dev/null +++ b/loginutils/getty.c @@ -0,0 +1,862 @@ +/* vi: set sw=4 ts=4: */ +/* agetty.c - another getty program for Linux. By W. Z. Venema 1989 + * Ported to Linux by Peter Orbaek + * This program is freely distributable. The entire man-page used to + * be here. Now read the real man-page agetty.8 instead. + * + * option added by Eric Rasmussen - 12/28/95 + * + * 1999-02-22 Arkadiusz Mi¶kiewicz + * - added Native Language Support + + * 1999-05-05 Thorsten Kranzkowski + * - enable hardware flow control before displaying /etc/issue + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "busybox.h" +#include + +#if ENABLE_FEATURE_UTMP +#include +#endif + +/* + * Some heuristics to find out what environment we are in: if it is not + * System V, assume it is SunOS 4. + */ +#ifdef LOGIN_PROCESS /* defined in System V utmp.h */ +#define SYSV_STYLE /* select System V style getty */ +#include +#include +#if ENABLE_FEATURE_WTMP +extern void updwtmp(const char *filename, const struct utmp *ut); +static void update_utmp(char *line); +#endif +#endif /* LOGIN_PROCESS */ + +/* + * Things you may want to modify. + * + * You may disagree with the default line-editing etc. characters defined + * below. Note, however, that DEL cannot be used for interrupt generation + * and for line editing at the same time. + */ + +/* I doubt there are systems which still need this */ +#undef HANDLE_ALLCAPS + +#define _PATH_LOGIN "/bin/login" + +/* If ISSUE is not defined, getty will never display the contents of the + * /etc/issue file. You will not want to spit out large "issue" files at the + * wrong baud rate. + */ +#define ISSUE "/etc/issue" /* displayed before the login prompt */ + +/* Some shorthands for control characters. */ +#define CTL(x) (x ^ 0100) /* Assumes ASCII dialect */ +#define CR CTL('M') /* carriage return */ +#define NL CTL('J') /* line feed */ +#define BS CTL('H') /* back space */ +#define DEL CTL('?') /* delete */ + +/* Defaults for line-editing etc. characters; you may want to change this. */ +#define DEF_ERASE DEL /* default erase character */ +#define DEF_INTR CTL('C') /* default interrupt character */ +#define DEF_QUIT CTL('\\') /* default quit char */ +#define DEF_KILL CTL('U') /* default kill char */ +#define DEF_EOF CTL('D') /* default EOF char */ +#define DEF_EOL '\n' +#define DEF_SWITCH 0 /* default switch char */ + +/* + * When multiple baud rates are specified on the command line, the first one + * we will try is the first one specified. + */ +#define FIRST_SPEED 0 + +/* Storage for command-line options. */ + +#define MAX_SPEED 10 /* max. nr. of baud rates */ + +struct options { + int flags; /* toggle switches, see below */ + unsigned timeout; /* time-out period */ + char *login; /* login program */ + char *tty; /* name of tty */ + char *initstring; /* modem init string */ + char *issue; /* alternative issue file */ + int numspeed; /* number of baud rates to try */ + int speeds[MAX_SPEED]; /* baud rates to be tried */ +}; + +static const char opt_string[] = "I:LH:f:hil:mt:wn"; +#define F_INITSTRING (1<<0) /* initstring is set */ +#define F_LOCAL (1<<1) /* force local */ +#define F_FAKEHOST (1<<2) /* force fakehost */ +#define F_CUSTISSUE (1<<3) /* give alternative issue file */ +#define F_RTSCTS (1<<4) /* enable RTS/CTS flow control */ +#define F_ISSUE (1<<5) /* display /etc/issue */ +#define F_LOGIN (1<<6) /* non-default login program */ +#define F_PARSE (1<<7) /* process modem status messages */ +#define F_TIMEOUT (1<<8) /* time out */ +#define F_WAITCRLF (1<<9) /* wait for CR or LF */ +#define F_NOPROMPT (1<<10) /* don't ask for login name! */ + +/* Storage for things detected while the login name was read. */ +struct chardata { + unsigned char erase; /* erase character */ + unsigned char kill; /* kill character */ + unsigned char eol; /* end-of-line character */ + unsigned char parity; /* what parity did we see */ +#ifdef HANDLE_ALLCAPS + unsigned char capslock; /* upper case without lower case */ +#endif +}; + +/* Initial values for the above. */ +static const struct chardata init_chardata = { + DEF_ERASE, /* default erase character */ + DEF_KILL, /* default kill character */ + 13, /* default eol char */ + 0, /* space parity */ +#ifdef HANDLE_ALLCAPS + 0, /* no capslock */ +#endif +}; + +/* The following is used for understandable diagnostics. */ + +/* Fake hostname for ut_host specified on command line. */ +static char *fakehost = NULL; + +/* ... */ +#ifdef DEBUGGING +#define debug(s) fprintf(dbf,s); fflush(dbf) +#define DEBUGTERM "/dev/ttyp0" +static FILE *dbf; +#else +#define debug(s) /* nothing */ +#endif + + +/* bcode - convert speed string to speed code; return 0 on failure */ +static int bcode(const char *s) +{ + int r; + unsigned value = bb_strtou(s, NULL, 10); + if (errno) { + return -1; + } + r = tty_value_to_baud(value); + if (r > 0) { + return r; + } + return 0; +} + + +/* parse_speeds - parse alternate baud rates */ +static void parse_speeds(struct options *op, char *arg) +{ + char *cp; + + debug("entered parse_speeds\n"); + for (cp = strtok(arg, ","); cp != 0; cp = strtok((char *) 0, ",")) { + if ((op->speeds[op->numspeed++] = bcode(cp)) <= 0) + bb_error_msg_and_die("bad speed: %s", cp); + if (op->numspeed > MAX_SPEED) + bb_error_msg_and_die("too many alternate speeds"); + } + debug("exiting parsespeeds\n"); +} + + +/* parse_args - parse command-line arguments */ +static void parse_args(int argc, char **argv, struct options *op) +{ + char *ts; + + op->flags = getopt32(argc, argv, opt_string, + &(op->initstring), &fakehost, &(op->issue), + &(op->login), &ts); + if (op->flags & F_INITSTRING) { + const char *p = op->initstring; + char *q; + + q = op->initstring = xstrdup(op->initstring); + /* copy optarg into op->initstring decoding \ddd + octal codes into chars */ + while (*p) { + if (*p == '\\') { + p++; + *q++ = bb_process_escape_sequence(&p); + } else { + *q++ = *p++; + } + } + *q = '\0'; + } + op->flags ^= F_ISSUE; /* revert flag show /etc/issue */ + if (op->flags & F_TIMEOUT) { + op->timeout = xatoul_range(ts, 1, INT_MAX); + } + argv += optind; + argc -= optind; + debug("after getopt loop\n"); + if (argc < 2) /* check parameter count */ + bb_show_usage(); + + /* we loosen up a bit and accept both "baudrate tty" and "tty baudrate" */ + if ('0' <= argv[0][0] && argv[0][0] <= '9') { + /* a number first, assume it's a speed (BSD style) */ + parse_speeds(op, argv[0]); /* baud rate(s) */ + op->tty = argv[1]; /* tty name */ + } else { + op->tty = argv[0]; /* tty name */ + parse_speeds(op, argv[1]); /* baud rate(s) */ + } + + if (argv[2]) + setenv("TERM", argv[2], 1); + + debug("exiting parseargs\n"); +} + +static void xdup2(int srcfd, int dstfd, const char *tty) +{ + if (dup2(srcfd, dstfd) == -1) + bb_perror_msg_and_die("%s: dup", tty); +} + +/* open_tty - set up tty as standard { input, output, error } */ +static void open_tty(char *tty, struct termios *tp, int local) +{ + int chdir_to_root = 0; + + /* Set up new standard input, unless we are given an already opened port. */ + + if (strcmp(tty, "-")) { + struct stat st; + int fd; + + /* Sanity checks... */ + + xchdir("/dev"); + chdir_to_root = 1; + xstat(tty, &st); + if ((st.st_mode & S_IFMT) != S_IFCHR) + bb_error_msg_and_die("%s: not a character device", tty); + + /* Open the tty as standard input. */ + + debug("open(2)\n"); + fd = xopen(tty, O_RDWR | O_NONBLOCK); + if (fd) { + xdup2(fd, 0, tty); + close(fd); + } + } else { + /* + * Standard input should already be connected to an open port. Make + * sure it is open for read/write. + */ + + if ((fcntl(0, F_GETFL, 0) & O_RDWR) != O_RDWR) + bb_error_msg_and_die("%s: not open for read/write", tty); + } + + /* Replace current standard output/error fd's with new ones */ + debug("duping\n"); + xdup2(0, 1, tty); + xdup2(0, 2, tty); + + /* + * The following ioctl will fail if stdin is not a tty, but also when + * there is noise on the modem control lines. In the latter case, the + * common course of action is (1) fix your cables (2) give the modem more + * time to properly reset after hanging up. SunOS users can achieve (2) + * by patching the SunOS kernel variable "zsadtrlow" to a larger value; + * 5 seconds seems to be a good value. + */ + + if (ioctl(0, TCGETS, tp) < 0) + bb_perror_msg_and_die("%s: ioctl(TCGETS)", tty); + + /* + * It seems to be a terminal. Set proper protections and ownership. Mode + * 0622 is suitable for SYSV <4 because /bin/login does not change + * protections. SunOS 4 login will change the protections to 0620 (write + * access for group tty) after the login has succeeded. + */ + +#ifdef DEBIAN +#warning Debian /dev/vcs[a]NN hack is deprecated and will be removed + { + /* tty to root.dialout 660 */ + struct group *gr; + int id; + + gr = getgrnam("dialout"); + id = gr ? gr->gr_gid : 0; + chown(tty, 0, id); + chmod(tty, 0660); + + /* vcs,vcsa to root.sys 600 */ + if (!strncmp(tty, "tty", 3) && isdigit(tty[3])) { + char *vcs, *vcsa; + + vcs = xstrdup(tty); + vcsa = xmalloc(strlen(tty) + 2); + strcpy(vcs, "vcs"); + strcpy(vcs + 3, tty + 3); + strcpy(vcsa, "vcsa"); + strcpy(vcsa + 4, tty + 3); + + id = (gr = getgrnam("sys")) ? gr->gr_gid : 0; + chown(vcs, 0, id); + chmod(vcs, 0600); + chown(vcsa, 0, id); + chmod(vcs, 0600); + + free(vcs); + free(vcsa); + } + } +#else + chown(tty, 0, 0); /* root, sys */ + chmod(tty, 0622); /* crw--w--w- */ +#endif + if (chdir_to_root) + xchdir("/"); +} + +/* termios_init - initialize termios settings */ +static void termios_init(struct termios *tp, int speed, struct options *op) +{ + /* + * Initial termios settings: 8-bit characters, raw-mode, blocking i/o. + * Special characters are set after we have read the login name; all + * reads will be done in raw mode anyway. Errors will be dealt with + * later on. + */ +#ifdef __linux__ + /* flush input and output queues, important for modems! */ + ioctl(0, TCFLSH, TCIOFLUSH); +#endif + + tp->c_cflag = CS8 | HUPCL | CREAD | speed; + if (op->flags & F_LOCAL) { + tp->c_cflag |= CLOCAL; + } + + tp->c_iflag = tp->c_lflag = tp->c_line = 0; + tp->c_oflag = OPOST | ONLCR; + tp->c_cc[VMIN] = 1; + tp->c_cc[VTIME] = 0; + + /* Optionally enable hardware flow control */ + +#ifdef CRTSCTS + if (op->flags & F_RTSCTS) + tp->c_cflag |= CRTSCTS; +#endif + + ioctl(0, TCSETS, tp); + + /* go to blocking input even in local mode */ + fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) & ~O_NONBLOCK); + + debug("term_io 2\n"); +} + +/* auto_baud - extract baud rate from modem status message */ +static void auto_baud(char *buf, unsigned size_buf, struct termios *tp) +{ + int speed; + int vmin; + unsigned iflag; + char *bp; + int nread; + + /* + * This works only if the modem produces its status code AFTER raising + * the DCD line, and if the computer is fast enough to set the proper + * baud rate before the message has gone by. We expect a message of the + * following format: + * + * + * + * The number is interpreted as the baud rate of the incoming call. If the + * modem does not tell us the baud rate within one second, we will keep + * using the current baud rate. It is advisable to enable BREAK + * processing (comma-separated list of baud rates) if the processing of + * modem status messages is enabled. + */ + + /* + * Use 7-bit characters, don't block if input queue is empty. Errors will + * be dealt with later on. + */ + + iflag = tp->c_iflag; + tp->c_iflag |= ISTRIP; /* enable 8th-bit stripping */ + vmin = tp->c_cc[VMIN]; + tp->c_cc[VMIN] = 0; /* don't block if queue empty */ + ioctl(0, TCSETS, tp); + + /* + * Wait for a while, then read everything the modem has said so far and + * try to extract the speed of the dial-in call. + */ + + sleep(1); + nread = read(0, buf, size_buf - 1); + if (nread > 0) { + buf[nread] = '\0'; + for (bp = buf; bp < buf + nread; bp++) { + if (isascii(*bp) && isdigit(*bp)) { + speed = bcode(bp); + if (speed) { + tp->c_cflag &= ~CBAUD; + tp->c_cflag |= speed; + } + break; + } + } + } + /* Restore terminal settings. Errors will be dealt with later on. */ + + tp->c_iflag = iflag; + tp->c_cc[VMIN] = vmin; + ioctl(0, TCSETS, tp); +} + +/* next_speed - select next baud rate */ +static void next_speed(struct termios *tp, struct options *op) +{ + static int baud_index = FIRST_SPEED; /* current speed index */ + + baud_index = (baud_index + 1) % op->numspeed; + tp->c_cflag &= ~CBAUD; + tp->c_cflag |= op->speeds[baud_index]; + ioctl(0, TCSETS, tp); +} + + +/* do_prompt - show login prompt, optionally preceded by /etc/issue contents */ +static void do_prompt(struct options *op, struct termios *tp) +{ +#ifdef ISSUE + print_login_issue(op->issue, op->tty); +#endif + print_login_prompt(); +} + +#ifdef HANDLE_ALLCAPS +/* caps_lock - string contains upper case without lower case */ +/* returns 1 if true, 0 if false */ +static int caps_lock(const char *s) +{ + while (*s) + if (islower(*s++)) + return 0; + return 1; +} +#endif + +/* get_logname - get user name, establish parity, speed, erase, kill, eol */ +/* return NULL on failure, logname on success */ +static char *get_logname(char *logname, unsigned size_logname, + struct options *op, struct chardata *cp, struct termios *tp) +{ + char *bp; + char c; /* input character, full eight bits */ + char ascval; /* low 7 bits of input character */ + int bits; /* # of "1" bits per character */ + int mask; /* mask with 1 bit up */ + static const char erase[][3] = { /* backspace-space-backspace */ + "\010\040\010", /* space parity */ + "\010\040\010", /* odd parity */ + "\210\240\210", /* even parity */ + "\210\240\210", /* no parity */ + }; + + /* Initialize kill, erase, parity etc. (also after switching speeds). */ + + *cp = init_chardata; + + /* Flush pending input (esp. after parsing or switching the baud rate). */ + + sleep(1); + ioctl(0, TCFLSH, TCIFLUSH); + + /* Prompt for and read a login name. */ + + logname[0] = '\0'; + while (!logname[0]) { + + /* Write issue file and prompt, with "parity" bit == 0. */ + + do_prompt(op, tp); + + /* Read name, watch for break, parity, erase, kill, end-of-line. */ + + bp = logname; + cp->eol = '\0'; + while (cp->eol == '\0') { + + /* Do not report trivial EINTR/EIO errors. */ + if (read(0, &c, 1) < 1) { + if (errno == EINTR || errno == EIO) + exit(0); + bb_perror_msg_and_die("%s: read", op->tty); + } + + /* Do BREAK handling elsewhere. */ + if (c == '\0' && op->numspeed > 1) + return NULL; + + /* Do parity bit handling. */ + ascval = c & 0177; + if (c != ascval) { /* "parity" bit on ? */ + bits = 1; + mask = 1; + while (mask & 0177) { + if (mask & ascval) + bits++; /* count "1" bits */ + mask <<= 1; + } + /* ... |= 2 - even, 1 - odd */ + cp->parity |= 2 - (bits & 1); + } + + /* Do erase, kill and end-of-line processing. */ + switch (ascval) { + case CR: + case NL: + *bp = '\0'; /* terminate logname */ + cp->eol = ascval; /* set end-of-line char */ + break; + case BS: + case DEL: + case '#': + cp->erase = ascval; /* set erase character */ + if (bp > logname) { + write(1, erase[cp->parity], 3); + bp--; + } + break; + case CTL('U'): + case '@': + cp->kill = ascval; /* set kill character */ + while (bp > logname) { + write(1, erase[cp->parity], 3); + bp--; + } + break; + case CTL('D'): + exit(0); + default: + if (!isascii(ascval) || !isprint(ascval)) { + /* ignore garbage characters */ + } else if (bp - logname >= size_logname - 1) { + bb_error_msg_and_die("%s: input overrun", op->tty); + } else { + write(1, &c, 1); /* echo the character */ + *bp++ = ascval; /* and store it */ + } + break; + } + } + } + /* Handle names with upper case and no lower case. */ + +#ifdef HANDLE_ALLCAPS + cp->capslock = caps_lock(logname); + if (cp->capslock) { + for (bp = logname; *bp; bp++) + if (isupper(*bp)) + *bp = tolower(*bp); /* map name to lower case */ + } +#endif + return logname; +} + +/* termios_final - set the final tty mode bits */ +static void termios_final(struct options *op, struct termios *tp, struct chardata *cp) +{ + /* General terminal-independent stuff. */ + + tp->c_iflag |= IXON | IXOFF; /* 2-way flow control */ + tp->c_lflag |= ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHOKE; + /* no longer| ECHOCTL | ECHOPRT */ + tp->c_oflag |= OPOST; + /* tp->c_cflag = 0; */ + tp->c_cc[VINTR] = DEF_INTR; /* default interrupt */ + tp->c_cc[VQUIT] = DEF_QUIT; /* default quit */ + tp->c_cc[VEOF] = DEF_EOF; /* default EOF character */ + tp->c_cc[VEOL] = DEF_EOL; + tp->c_cc[VSWTC] = DEF_SWITCH; /* default switch character */ + + /* Account for special characters seen in input. */ + + if (cp->eol == CR) { + tp->c_iflag |= ICRNL; /* map CR in input to NL */ + tp->c_oflag |= ONLCR; /* map NL in output to CR-NL */ + } + tp->c_cc[VERASE] = cp->erase; /* set erase character */ + tp->c_cc[VKILL] = cp->kill; /* set kill character */ + + /* Account for the presence or absence of parity bits in input. */ + + switch (cp->parity) { + case 0: /* space (always 0) parity */ + break; + case 1: /* odd parity */ + tp->c_cflag |= PARODD; + /* FALLTHROUGH */ + case 2: /* even parity */ + tp->c_cflag |= PARENB; + tp->c_iflag |= INPCK | ISTRIP; + /* FALLTHROUGH */ + case (1 | 2): /* no parity bit */ + tp->c_cflag &= ~CSIZE; + tp->c_cflag |= CS7; + break; + } + /* Account for upper case without lower case. */ + +#ifdef HANDLE_ALLCAPS + if (cp->capslock) { + tp->c_iflag |= IUCLC; + tp->c_lflag |= XCASE; + tp->c_oflag |= OLCUC; + } +#endif + /* Optionally enable hardware flow control */ + +#ifdef CRTSCTS + if (op->flags & F_RTSCTS) + tp->c_cflag |= CRTSCTS; +#endif + + /* Finally, make the new settings effective */ + + if (ioctl(0, TCSETS, tp) < 0) + bb_perror_msg_and_die("%s: ioctl(TCSETS)", op->tty); +} + + +#ifdef SYSV_STYLE +#if ENABLE_FEATURE_UTMP +/* update_utmp - update our utmp entry */ +static void update_utmp(char *line) +{ + struct utmp ut; + struct utmp *utp; + time_t t; + int mypid = getpid(); + + /* + * The utmp file holds miscellaneous information about things started by + * /sbin/init and other system-related events. Our purpose is to update + * the utmp entry for the current process, in particular the process type + * and the tty line we are listening to. Return successfully only if the + * utmp file can be opened for update, and if we are able to find our + * entry in the utmp file. + */ + if (access(_PATH_UTMP, R_OK|W_OK) == -1) { + close(creat(_PATH_UTMP, 0664)); + } + utmpname(_PATH_UTMP); + setutent(); + while ((utp = getutent()) + && !(utp->ut_type == INIT_PROCESS && utp->ut_pid == mypid)) + /* nothing */; + + if (utp) { + memcpy(&ut, utp, sizeof(ut)); + } else { + /* some inits don't initialize utmp... */ + memset(&ut, 0, sizeof(ut)); + safe_strncpy(ut.ut_id, line + 3, sizeof(ut.ut_id)); + } + /* endutent(); */ + + strcpy(ut.ut_user, "LOGIN"); + safe_strncpy(ut.ut_line, line, sizeof(ut.ut_line)); + if (fakehost) + safe_strncpy(ut.ut_host, fakehost, sizeof(ut.ut_host)); + time(&t); + ut.ut_time = t; + ut.ut_type = LOGIN_PROCESS; + ut.ut_pid = mypid; + + pututline(&ut); + endutent(); + +#if ENABLE_FEATURE_WTMP + if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) + close(creat(bb_path_wtmp_file, 0664)); + updwtmp(bb_path_wtmp_file, &ut); +#endif +} + +#endif /* CONFIG_FEATURE_UTMP */ +#endif /* SYSV_STYLE */ + + +int getty_main(int argc, char **argv) +{ + int nullfd; + char *logname = NULL; /* login name, given to /bin/login */ + /* Merging these into "struct local" may _seem_ to reduce + * parameter passing, but today's gcc will inline + * statics which are called once anyway, so don't do that */ + struct chardata chardata; /* set by get_logname() */ + struct termios termios; /* terminal mode bits */ + struct options options = { + 0, /* show /etc/issue (SYSV_STYLE) */ + 0, /* no timeout */ + _PATH_LOGIN, /* default login program */ + "tty1", /* default tty line */ + "", /* modem init string */ +#ifdef ISSUE + ISSUE, /* default issue file */ +#else + NULL, +#endif + 0, /* no baud rates known yet */ + }; + + /* Already too late because of theoretical + * possibility of getty --help somehow triggered + * inadvertently before we reach this. Oh well. */ + close(0); + close(1); + close(2); + logmode = LOGMODE_NONE; +#ifdef __linux__ + setsid(); +#endif + /* Was "/dev/console". Why should we spam *system console* + * if there is a problem with getty on /dev/ttyS15?... */ + nullfd = xopen(bb_dev_null, O_RDWR); + if (nullfd) { + dup2(nullfd, 0); + close(nullfd); + } + dup2(0, 1); + dup2(0, 2); + /* We want special flavor of error_msg_and_die */ + die_sleep = 10; + msg_eol = "\r\n"; + openlog(applet_name, LOG_PID, LOG_AUTH); + logmode = LOGMODE_BOTH; + +#ifdef DEBUGGING + dbf = xfopen(DEBUGTERM, "w"); + + { + int i; + + for (i = 1; i < argc; i++) { + debug(argv[i]); + debug("\n"); + } + } +#endif + + /* Parse command-line arguments. */ + parse_args(argc, argv, &options); + +#ifdef SYSV_STYLE +#if ENABLE_FEATURE_UTMP + /* Update the utmp file. */ + update_utmp(options.tty); +#endif +#endif + + debug("calling open_tty\n"); + /* Open the tty as standard { input, output, error }. */ + open_tty(options.tty, &termios, options.flags & F_LOCAL); + +#ifdef __linux__ + { + int iv; + + iv = getpid(); + ioctl(0, TIOCSPGRP, &iv); + } +#endif + /* Initialize the termios settings (raw mode, eight-bit, blocking i/o). */ + debug("calling termios_init\n"); + termios_init(&termios, options.speeds[FIRST_SPEED], &options); + + /* write the modem init string and DON'T flush the buffers */ + if (options.flags & F_INITSTRING) { + debug("writing init string\n"); + write(1, options.initstring, strlen(options.initstring)); + } + + if (!(options.flags & F_LOCAL)) { + /* go to blocking write mode unless -L is specified */ + fcntl(1, F_SETFL, fcntl(1, F_GETFL, 0) & ~O_NONBLOCK); + } + + /* Optionally detect the baud rate from the modem status message. */ + debug("before autobaud\n"); + if (options.flags & F_PARSE) + auto_baud(bb_common_bufsiz1, sizeof(bb_common_bufsiz1), &termios); + + /* Set the optional timer. */ + if (options.timeout) + alarm(options.timeout); + + /* optionally wait for CR or LF before writing /etc/issue */ + if (options.flags & F_WAITCRLF) { + char ch; + + debug("waiting for cr-lf\n"); + while (read(0, &ch, 1) == 1) { + ch &= 0x7f; /* strip "parity bit" */ +#ifdef DEBUGGING + fprintf(dbf, "read %c\n", ch); +#endif + if (ch == '\n' || ch == '\r') + break; + } + } + + chardata = init_chardata; + if (!(options.flags & F_NOPROMPT)) { + /* Read the login name. */ + debug("reading login name\n"); + logname = get_logname(bb_common_bufsiz1, sizeof(bb_common_bufsiz1), + &options, &chardata, &termios); + while (logname == NULL) + next_speed(&termios, &options); + } + + /* Disable timer. */ + + if (options.timeout) + alarm(0); + + /* Finalize the termios settings. */ + + termios_final(&options, &termios, &chardata); + + /* Now the newline character should be properly written. */ + + write(1, "\n", 1); + + /* Let the login program take care of password validation. */ + + execl(options.login, options.login, "--", logname, (char *) 0); + bb_error_msg_and_die("%s: can't exec %s", options.tty, options.login); +} diff --git a/loginutils/login.c b/loginutils/login.c new file mode 100644 index 000000000..bd3c112b9 --- /dev/null +++ b/loginutils/login.c @@ -0,0 +1,402 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include + +#ifdef CONFIG_SELINUX +#include /* for is_selinux_enabled() */ +#include /* for get_default_context() */ +#include /* for security class definitions */ +#include +#endif + +enum { + TIMEOUT = 60, + EMPTY_USERNAME_COUNT = 10, + USERNAME_SIZE = 32, + TTYNAME_SIZE = 32, +}; + +static char full_tty[TTYNAME_SIZE]; +static char* short_tty = full_tty; + +#if ENABLE_FEATURE_UTMP +/* vv Taken from tinylogin utmp.c vv */ +/* + * read_or_build_utent - see if utmp file is correct for this process + * + * System V is very picky about the contents of the utmp file + * and requires that a slot for the current process exist. + * The utmp file is scanned for an entry with the same process + * ID. If no entry exists the process exits with a message. + * + * The "picky" flag is for network and other logins that may + * use special flags. It allows the pid checks to be overridden. + * This means that getty should never invoke login with any + * command line flags. + */ + +static struct utmp utent; + +static void read_or_build_utent(int picky) +{ + struct utmp *ut; + pid_t pid = getpid(); + + setutent(); + + /* First, try to find a valid utmp entry for this process. */ + while ((ut = getutent())) + if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] && + (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS)) + break; + + /* If there is one, just use it, otherwise create a new one. */ + if (ut) { + utent = *ut; + } else { + if (picky) + bb_error_msg_and_die("no utmp entry found"); + + memset(&utent, 0, sizeof(utent)); + utent.ut_type = LOGIN_PROCESS; + utent.ut_pid = pid; + strncpy(utent.ut_line, short_tty, sizeof(utent.ut_line)); + /* This one is only 4 chars wide. Try to fit something + * remotely meaningful by skipping "tty"... */ + strncpy(utent.ut_id, short_tty + 3, sizeof(utent.ut_id)); + strncpy(utent.ut_user, "LOGIN", sizeof(utent.ut_user)); + utent.ut_time = time(NULL); + } + if (!picky) /* root login */ + memset(utent.ut_host, 0, sizeof(utent.ut_host)); +} + +/* + * write_utent - put a USER_PROCESS entry in the utmp file + * + * write_utent changes the type of the current utmp entry to + * USER_PROCESS. the wtmp file will be updated as well. + */ +static void write_utent(const char *username) +{ + utent.ut_type = USER_PROCESS; + strncpy(utent.ut_user, username, sizeof(utent.ut_user)); + utent.ut_time = time(NULL); + /* other fields already filled in by read_or_build_utent above */ + setutent(); + pututline(&utent); + endutent(); +#if ENABLE_FEATURE_WTMP + if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) { + close(creat(bb_path_wtmp_file, 0664)); + } + updwtmp(bb_path_wtmp_file, &utent); +#endif +} +#else /* !ENABLE_FEATURE_UTMP */ +static inline void read_or_build_utent(int ATTRIBUTE_UNUSED picky) {} +static inline void write_utent(const char ATTRIBUTE_UNUSED *username) {} +#endif /* !ENABLE_FEATURE_UTMP */ + +static void die_if_nologin_and_non_root(int amroot) +{ + FILE *fp; + int c; + + if (access(bb_path_nologin_file, F_OK)) + return; + + fp = fopen(bb_path_nologin_file, "r"); + if (fp) { + while ((c = getc(fp)) != EOF) + putchar((c=='\n') ? '\r' : c); + fflush(stdout); + fclose(fp); + } else + puts("\r\nSystem closed for routine maintenance\r"); + if (!amroot) + exit(1); + puts("\r\n[Disconnect bypassed -- root login allowed.]\r"); +} + +#if ENABLE_FEATURE_SECURETTY +static int check_securetty(void) +{ + FILE *fp; + int i; + char buf[BUFSIZ]; + + fp = fopen(bb_path_securetty_file, "r"); + if (!fp) { + /* A missing securetty file is not an error. */ + return 1; + } + while (fgets(buf, sizeof(buf)-1, fp)) { + for(i = strlen(buf)-1; i>=0; --i) { + if (!isspace(buf[i])) + break; + } + buf[++i] = '\0'; + if ((buf[0]=='\0') || (buf[0]=='#')) + continue; + if (strcmp(buf, short_tty) == 0) { + fclose(fp); + return 1; + } + } + fclose(fp); + return 0; +} +#else +static inline int check_securetty(void) { return 1; } +#endif + +static void get_username_or_die(char *buf, int size_buf) +{ + int c, cntdown; + cntdown = EMPTY_USERNAME_COUNT; +prompt: + /* skip whitespace */ + print_login_prompt(); + do { + c = getchar(); + if (c == EOF) exit(1); + if (c == '\n') { + if (!--cntdown) exit(1); + goto prompt; + } + } while (isspace(c)); + + *buf++ = c; + if (!fgets(buf, size_buf-2, stdin)) + exit(1); + if (!strchr(buf, '\n')) + exit(1); + while (isgraph(*buf)) buf++; + *buf = '\0'; +} + +static void motd(void) +{ + FILE *fp; + int c; + + fp = fopen(bb_path_motd_file, "r"); + if (fp) { + while ((c = getc(fp)) != EOF) + putchar(c); + fclose(fp); + } +} + +static void nonblock(int fd) +{ + fcntl(fd, F_SETFL, O_NONBLOCK | fcntl(fd, F_GETFL)); +} + +static void alarm_handler(int sig ATTRIBUTE_UNUSED) +{ + /* This is the escape hatch! Poor serial line users and the like + * arrive here when their connection is broken. + * We don't want to block here */ + nonblock(1); + nonblock(2); + bb_info_msg("\r\nLogin timed out after %d seconds\r", TIMEOUT); + exit(EXIT_SUCCESS); +} + +int login_main(int argc, char **argv) +{ + enum { + LOGIN_OPT_f = (1<<0), + LOGIN_OPT_h = (1<<1), + LOGIN_OPT_p = (1<<2), + }; + char fromhost[512]; + char username[USERNAME_SIZE]; + const char *tmp; + int amroot; + unsigned opt; + int count = 0; + struct passwd *pw; + char *opt_host = NULL; + char *opt_user = NULL; + USE_SELINUX(security_context_t user_sid = NULL;) + + username[0] = '\0'; + amroot = (getuid() == 0); + signal(SIGALRM, alarm_handler); + alarm(TIMEOUT); + + opt = getopt32(argc, argv, "f:h:p", &opt_user, &opt_host); + if (opt & LOGIN_OPT_f) { + if (!amroot) + bb_error_msg_and_die("-f is for root only"); + safe_strncpy(username, opt_user, sizeof(username)); + } + if (optind < argc) /* user from command line (getty) */ + safe_strncpy(username, argv[optind], sizeof(username)); + + /* Let's find out and memorize our tty */ + if (!isatty(0) || !isatty(1) || !isatty(2)) + return EXIT_FAILURE; /* Must be a terminal */ + safe_strncpy(full_tty, "UNKNOWN", sizeof(full_tty)); + tmp = ttyname(0); + if (tmp) { + safe_strncpy(full_tty, tmp, sizeof(full_tty)); + if (strncmp(full_tty, "/dev/", 5) == 0) + short_tty = full_tty + 5; + } + + read_or_build_utent(!amroot); + + if (opt_host) { + USE_FEATURE_UTMP( + safe_strncpy(utent.ut_host, opt_host, sizeof(utent.ut_host)); + ) + snprintf(fromhost, sizeof(fromhost)-1, " on '%.100s' from " + "'%.200s'", short_tty, opt_host); + } + else + snprintf(fromhost, sizeof(fromhost)-1, " on '%.100s'", short_tty); + + bb_setpgrp; + + openlog(applet_name, LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH); + + while (1) { + if (!username[0]) + get_username_or_die(username, sizeof(username)); + + pw = getpwnam(username); + if (!pw) { + safe_strncpy(username, "UNKNOWN", sizeof(username)); + goto auth_failed; + } + + if (pw->pw_passwd[0] == '!' || pw->pw_passwd[0] == '*') + goto auth_failed; + + if (opt & LOGIN_OPT_f) + break; /* -f USER: success without asking passwd */ + + if (pw->pw_uid == 0 && !check_securetty()) + goto auth_failed; + + /* Don't check the password if password entry is empty (!) */ + if (!pw->pw_passwd[0]) + break; + + /* authorization takes place here */ + if (correct_password(pw)) + break; + +auth_failed: + opt &= ~LOGIN_OPT_f; + bb_do_delay(FAIL_DELAY); + puts("Login incorrect"); + if (++count == 3) { + syslog(LOG_WARNING, "invalid password for '%s'%s", + username, fromhost); + return EXIT_FAILURE; + } + username[0] = '\0'; + } + + alarm(0); + die_if_nologin_and_non_root(pw->pw_uid == 0); + + write_utent(username); + +#ifdef CONFIG_SELINUX + if (is_selinux_enabled()) { + security_context_t old_tty_sid, new_tty_sid; + + if (get_default_context(username, NULL, &user_sid)) { + bb_error_msg_and_die("cannot get SID for %s", + username); + } + if (getfilecon(full_tty, &old_tty_sid) < 0) { + bb_perror_msg_and_die("getfilecon(%s) failed", + full_tty); + } + if (security_compute_relabel(user_sid, old_tty_sid, + SECCLASS_CHR_FILE, &new_tty_sid) != 0) { + bb_perror_msg_and_die("security_change_sid(%s) failed", + full_tty); + } + if (setfilecon(full_tty, new_tty_sid) != 0) { + bb_perror_msg_and_die("chsid(%s, %s) failed", + full_tty, new_tty_sid); + } + } +#endif + /* Try these, but don't complain if they fail. + * _f_chown is safe wrt race t=ttyname(0);...;chown(t); */ + fchown(0, pw->pw_uid, pw->pw_gid); + fchmod(0, 0600); + + /* TODO: be nommu-friendly, use spawn? */ + if (ENABLE_LOGIN_SCRIPTS) { + char *script = getenv("LOGIN_PRE_SUID_SCRIPT"); + if (script) { + char *t_argv[2] = { script, NULL }; + switch (fork()) { + case -1: break; + case 0: /* child */ + xchdir("/"); + setenv("LOGIN_TTY", full_tty, 1); + setenv("LOGIN_USER", pw->pw_name, 1); + setenv("LOGIN_UID", utoa(pw->pw_uid), 1); + setenv("LOGIN_GID", utoa(pw->pw_gid), 1); + setenv("LOGIN_SHELL", pw->pw_shell, 1); + execvp(script, t_argv); + exit(1); + default: /* parent */ + wait(NULL); + } + } + } + + change_identity(pw); + tmp = pw->pw_shell; + if (!tmp || !*tmp) + tmp = DEFAULT_SHELL; + setup_environment(tmp, 1, !(opt & LOGIN_OPT_p), pw); + + motd(); + + if (pw->pw_uid == 0) + syslog(LOG_INFO, "root login%s", fromhost); +#ifdef CONFIG_SELINUX + /* well, a simple setexeccon() here would do the job as well, + * but let's play the game for now */ + set_current_security_context(user_sid); +#endif + + // util-linux login also does: + // /* start new session */ + // setsid(); + // /* TIOCSCTTY: steal tty from other process group */ + // if (ioctl(0, TIOCSCTTY, 1)) error_msg... + + /* set signals to defaults */ + signal(SIGALRM, SIG_DFL); + /* Is this correct? This way user can ctrl-c out of /etc/profile, + * potentially creating security breach (tested with bash 3.0). + * But without this, bash 3.0 will not enable ctrl-c either. + * Maybe bash is buggy? + * Need to find out what standards say about /bin/login - + * should it leave SIGINT etc enabled or disabled? */ + signal(SIGINT, SIG_DFL); + + run_shell(tmp, 1, 0, 0); /* exec the shell finally */ + + return EXIT_FAILURE; +} diff --git a/loginutils/passwd.c b/loginutils/passwd.c new file mode 100644 index 000000000..bcb7f2b26 --- /dev/null +++ b/loginutils/passwd.c @@ -0,0 +1,339 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + + +static void nuke_str(char *str) +{ + if (str) memset(str, 0, strlen(str)); +} + + +static int i64c(int i) +{ + i &= 0x3f; + if (i == 0) + return '.'; + if (i == 1) + return '/'; + if (i < 12) + return ('0' - 2 + i); + if (i < 38) + return ('A' - 12 + i); + return ('a' - 38 + i); +} + + +static void crypt_make_salt(char *p, int cnt) +{ + unsigned x = x; /* it's pointless to initialize it anyway :) */ + + x += getpid() + time(NULL) + clock(); + do { + /* x = (x*1664525 + 1013904223) % 2^32 generator is lame + * (low-order bit is not "random", etc...), + * but for our purposes it is good enough */ + x = x*1664525 + 1013904223; + /* BTW, Park and Miller's "minimal standard generator" is + * x = x*16807 % ((2^31)-1) + * It has no problem with visibly alternating lowest bit + * but is also weak in cryptographic sense + needs div, + * which needs more code (and slower) on many CPUs */ + *p++ = i64c(x >> 16); + *p++ = i64c(x >> 22); + } while (--cnt); + *p = '\0'; +} + + +static char* new_password(const struct passwd *pw, uid_t myuid, int algo) +{ + char salt[sizeof("$N$XXXXXXXX")]; /* "$N$XXXXXXXX" or "XX" */ + char *orig = ""; + char *newp = NULL; + char *cipher = NULL; + char *cp = NULL; + char *ret = NULL; /* failure so far */ + + if (myuid && pw->pw_passwd[0]) { + orig = bb_askpass(0, "Old password:"); /* returns ptr to static */ + if (!orig) + goto err_ret; + cipher = pw_encrypt(orig, pw->pw_passwd); /* returns ptr to static */ + if (strcmp(cipher, pw->pw_passwd) != 0) { + syslog(LOG_WARNING, "incorrect password for '%s'", + pw->pw_name); + bb_do_delay(FAIL_DELAY); + puts("Incorrect password"); + goto err_ret; + } + } + orig = xstrdup(orig); /* or else bb_askpass() will destroy it */ + newp = bb_askpass(0, "New password:"); /* returns ptr to static */ + if (!newp) + goto err_ret; + newp = xstrdup(newp); /* we are going to bb_askpass() again, so save it */ + if (obscure(orig, newp, pw) && myuid) + goto err_ret; /* non-root is not allowed to have weak passwd */ + + cp = bb_askpass(0, "Retype password:"); + if (!cp) + goto err_ret; + if (strcmp(cp, newp)) { + puts("Passwords don't match"); + goto err_ret; + } + + /*memset(salt, 0, sizeof(salt)); - why?*/ + crypt_make_salt(salt, 1); /* des */ + if (algo) { /* MD5 */ + strcpy(salt, "$1$"); + crypt_make_salt(salt + 3, 4); + } + ret = xstrdup(pw_encrypt(newp, salt)); /* returns ptr to static */ + /* whee, success! */ + + err_ret: + nuke_str(orig); + if (ENABLE_FEATURE_CLEAN_UP) free(orig); + nuke_str(newp); + if (ENABLE_FEATURE_CLEAN_UP) free(newp); + nuke_str(cipher); + nuke_str(cp); + return ret; +} + + +#if 0 +static int get_algo(char *a) +{ + /* standard: MD5 */ + int x = 1; + if (strcasecmp(a, "des") == 0) + x = 0; + return x; +} +#endif + + +static int update_passwd(const char *filename, const char *username, + const char *new_pw) +{ + struct stat sb; + struct flock lock; + FILE *old_fp; + FILE *new_fp; + char *new_name; + char *last_char; + unsigned user_len; + int old_fd; + int new_fd; + int i; + int ret = 1; /* failure */ + + logmode = LOGMODE_STDIO; + /* New passwd file, "/etc/passwd+" for now */ + new_name = xasprintf("%s+", filename); + last_char = &new_name[strlen(new_name)-1]; + username = xasprintf("%s:", username); + user_len = strlen(username); + + old_fp = fopen(filename, "r+"); + if (!old_fp) + goto free_mem; + old_fd = fileno(old_fp); + + /* Try to create "/etc/passwd+". Wait if it exists. */ + i = 30; + do { + // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC? + new_fd = open(new_name, O_WRONLY|O_CREAT|O_EXCL,0600); + if (new_fd >= 0) goto created; + if (errno != EEXIST) break; + usleep(100000); /* 0.1 sec */ + } while (--i); + bb_perror_msg("cannot create '%s'", new_name); + goto close_old_fp; + created: + if (!fstat(old_fd, &sb)) { + fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */ + fchown(new_fd, sb.st_uid, sb.st_gid); + } + new_fp = fdopen(new_fd, "w"); + if (!new_fp) { + close(new_fd); + goto unlink_new; + } + + /* Backup file is "/etc/passwd-" */ + last_char[0] = '-'; + /* Delete old one, create new as a hardlink to current */ + i = (unlink(new_name) && errno != ENOENT); + if (i || link(filename, new_name)) + bb_perror_msg("warning: cannot create backup copy '%s'", new_name); + last_char[0] = '+'; + + /* Lock the password file before updating */ + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl(old_fd, F_SETLK, &lock) < 0) + bb_perror_msg("warning: cannot lock '%s'", filename); + lock.l_type = F_UNLCK; + + /* Read current password file, write updated one */ + while (1) { + char *line = xmalloc_fgets(old_fp); + if (!line) break; /* EOF/error */ + if (strncmp(username, line, user_len) == 0) { + /* we have a match with "username:"... */ + const char *cp = line + user_len; + /* now cp -> old passwd, skip it: */ + cp = strchr(cp, ':'); + if (!cp) cp = ""; + /* now cp -> ':' after old passwd or -> "" */ + fprintf(new_fp, "%s%s%s", username, new_pw, cp); + /* Erase password in memory */ + } else + fputs(line, new_fp); + free(line); + } + fcntl(old_fd, F_SETLK, &lock); + + /* We do want all of them to execute, thus | instead of || */ + if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp)) + || rename(new_name, filename) + ) { + /* At least one of those failed */ + goto unlink_new; + } + ret = 0; /* whee, success! */ + + unlink_new: + if (ret) unlink(new_name); + + close_old_fp: + fclose(old_fp); + + free_mem: + if (ENABLE_FEATURE_CLEAN_UP) free(new_name); + if (ENABLE_FEATURE_CLEAN_UP) free((char*)username); + logmode = LOGMODE_BOTH; + return ret; +} + + +int passwd_main(int argc, char **argv) +{ + enum { + OPT_algo = 0x1, /* -a - password algorithm */ + OPT_lock = 0x2, /* -l - lock account */ + OPT_unlock = 0x4, /* -u - unlock account */ + OPT_delete = 0x8, /* -d - delete password */ + OPT_lud = 0xe, + STATE_ALGO_md5 = 0x10, + /*STATE_ALGO_des = 0x20, not needed yet */ + }; + unsigned opt; + char *opt_a = ""; + const char *filename; + char *myname; + char *name; + char *newp; + struct passwd *pw; + uid_t myuid; + struct rlimit rlimit_fsize; + char c; + + logmode = LOGMODE_BOTH; + openlog(applet_name, LOG_NOWAIT, LOG_AUTH); + opt = getopt32(argc, argv, "a:lud", &opt_a); + argc -= optind; + argv += optind; + + if (strcasecmp(opt_a, "des") != 0) /* -a */ + opt |= STATE_ALGO_md5; + //else + // opt |= STATE_ALGO_des; + myuid = getuid(); + if ((opt & OPT_lud) && (!argc || myuid)) + bb_show_usage(); + + myname = xstrdup(bb_getpwuid(NULL, myuid, -1)); + name = argc ? argv[0] : myname; + + pw = getpwnam(name); + if (!pw) bb_error_msg_and_die("unknown user %s", name); + if (myuid && pw->pw_uid != myuid) { + /* LOGMODE_BOTH */ + bb_error_msg_and_die("%s can't change password for %s", myname, name); + } + + filename = bb_path_passwd_file; + if (ENABLE_FEATURE_SHADOWPASSWDS) { + struct spwd *sp = getspnam(name); + if (!sp) { + /* LOGMODE_BOTH */ + bb_error_msg("no record of %s in %s, using %s", + name, bb_path_shadow_file, + bb_path_passwd_file); + } else { + filename = bb_path_shadow_file; + pw->pw_passwd = sp->sp_pwdp; + } + } + + /* Decide what the new password will be */ + newp = NULL; + c = pw->pw_passwd[0] - '!'; + if (!(opt & OPT_lud)) { + if (myuid && !c) { /* passwd starts with '!' */ + /* LOGMODE_BOTH */ + bb_error_msg_and_die("cannot change " + "locked password for %s", name); + } + printf("Changing password for %s\n", name); + newp = new_password(pw, myuid, opt & STATE_ALGO_md5); + if (!newp) { + logmode = LOGMODE_STDIO; + bb_error_msg_and_die("password for %s is unchanged", name); + } + } else if (opt & OPT_lock) { + if (!c) goto skip; /* passwd starts with '!' */ + newp = xasprintf("!%s", pw->pw_passwd); + } else if (opt & OPT_unlock) { + if (c) goto skip; /* not '!' */ + newp = xstrdup(&pw->pw_passwd[1]); + } else if (opt & OPT_delete) { + newp = xstrdup(""); + } + + rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * 30000; + setrlimit(RLIMIT_FSIZE, &rlimit_fsize); + signal(SIGHUP, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + umask(077); + xsetuid(0); + if (update_passwd(filename, name, newp) != 0) { + /* LOGMODE_BOTH */ + bb_error_msg_and_die("cannot update password file %s", + filename); + } + /* LOGMODE_BOTH */ + bb_info_msg("Password for %s changed by %s", name, myname); + + if (ENABLE_FEATURE_CLEAN_UP) free(newp); +skip: + if (!newp) { + bb_error_msg_and_die("password for %s is already %slocked", + name, (opt & OPT_unlock) ? "un" : ""); + } + if (ENABLE_FEATURE_CLEAN_UP) free(myname); + return 0; +} diff --git a/loginutils/su.c b/loginutils/su.c new file mode 100644 index 000000000..a23ee932b --- /dev/null +++ b/loginutils/su.c @@ -0,0 +1,90 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini su implementation for busybox + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include + +int su_main(int argc, char **argv) +{ + unsigned long flags; + char *opt_shell = 0; + char *opt_command = 0; + char *opt_username = "root"; + char **opt_args = 0; + struct passwd *pw; + uid_t cur_uid = getuid(); + const char *tty; + char *old_user; + + flags = getopt32(argc, argv, "mplc:s:", &opt_command, &opt_shell); +#define SU_OPT_mp (3) +#define SU_OPT_l (4) + + if (optind < argc && argv[optind][0] == '-' && argv[optind][1] == 0) { + flags |= SU_OPT_l; + ++optind; + } + + /* get user if specified */ + if (optind < argc) opt_username = argv [optind++]; + + if (optind < argc) opt_args = argv + optind; + + if (ENABLE_SU_SYSLOG) { + /* The utmp entry (via getlogin) is probably the best way to identify + the user, especially if someone su's from a su-shell. + But getlogin can fail -- usually due to lack of utmp entry. + in this case resort to getpwuid. */ + old_user = xstrdup(USE_FEATURE_UTMP(getlogin() ? : ) (pw = getpwuid(cur_uid)) ? pw->pw_name : ""); + tty = ttyname(2) ? : "none"; + openlog(applet_name, 0, LOG_AUTH); + } + + pw = getpwnam(opt_username); + if (!pw) bb_error_msg_and_die("unknown id: %s", opt_username); + + /* Make sure pw->pw_shell is non-NULL. It may be NULL when NEW_USER + is a username that is retrieved via NIS (YP), but that doesn't have + a default shell listed. */ + if (!pw->pw_shell || !pw->pw_shell[0]) pw->pw_shell = (char *)DEFAULT_SHELL; + + if ((cur_uid == 0) || correct_password(pw)) { + if (ENABLE_SU_SYSLOG) + syslog(LOG_NOTICE, "+ %s %s:%s", tty, old_user, opt_username); + } else { + if (ENABLE_SU_SYSLOG) + syslog(LOG_NOTICE, "- %s %s:%s", tty, old_user, opt_username); + bb_error_msg_and_die("incorrect password"); + } + + if (ENABLE_FEATURE_CLEAN_UP && ENABLE_SU_SYSLOG) { + closelog(); + free(old_user); + } + + if (!opt_shell && (flags & SU_OPT_mp)) opt_shell = getenv("SHELL"); + + if (opt_shell && cur_uid && restricted_shell(pw->pw_shell)) { + /* The user being su'd to has a nonstandard shell, and so is + probably a uucp account or has restricted access. Don't + compromise the account by allowing access with a standard + shell. */ + bb_error_msg("using restricted shell"); + opt_shell = 0; + } + + if (!opt_shell) opt_shell = pw->pw_shell; + + change_identity(pw); + setup_environment(opt_shell, flags & SU_OPT_l, !(flags & SU_OPT_mp), pw); + USE_SELINUX(set_current_security_context(NULL);) + + /* Never returns */ + run_shell(opt_shell, flags & SU_OPT_l, opt_command, (const char**)opt_args); + + return EXIT_FAILURE; +} diff --git a/loginutils/sulogin.c b/loginutils/sulogin.c new file mode 100644 index 000000000..c07264e7b --- /dev/null +++ b/loginutils/sulogin.c @@ -0,0 +1,121 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini sulogin implementation for busybox + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include + +#include "busybox.h" + +static const char * const forbid[] = { + "ENV", + "BASH_ENV", + "HOME", + "IFS", + "PATH", + "SHELL", + "LD_LIBRARY_PATH", + "LD_PRELOAD", + "LD_TRACE_LOADED_OBJECTS", + "LD_BIND_NOW", + "LD_AOUT_LIBRARY_PATH", + "LD_AOUT_PRELOAD", + "LD_NOWARN", + "LD_KEEPDIR", + (char *) 0 +}; + + +static void catchalarm(int ATTRIBUTE_UNUSED junk) +{ + exit(EXIT_FAILURE); +} + + +int sulogin_main(int argc, char **argv) +{ + char *cp; + int timeout = 0; + char *timeout_arg; + const char * const *p; + struct passwd *pwd; + struct spwd *spwd; + const char *shell; + + logmode = LOGMODE_BOTH; + openlog(applet_name, 0, LOG_AUTH); + + if (getopt32(argc, argv, "t:", &timeout_arg)) { + timeout = xatoi_u(timeout_arg); + } + + if (argv[optind]) { + close(0); + close(1); + dup(xopen(argv[optind], O_RDWR)); + close(2); + dup(0); + } + + if (!isatty(0) || !isatty(1) || !isatty(2)) { + logmode = LOGMODE_SYSLOG; + bb_error_msg_and_die("not a tty"); + } + + /* Clear out anything dangerous from the environment */ + for (p = forbid; *p; p++) + unsetenv(*p); + + signal(SIGALRM, catchalarm); + + pwd = getpwuid(0); + if (!pwd) { + goto auth_error; + } + + if (ENABLE_FEATURE_SHADOWPASSWDS) { + spwd = getspnam(pwd->pw_name); + if (!spwd) { + goto auth_error; + } + pwd->pw_passwd = spwd->sp_pwdp; + } + + while (1) { + /* cp points to a static buffer that is zeroed every time */ + cp = bb_askpass(timeout, + "Give root password for system maintenance\n" + "(or type Control-D for normal startup):"); + + if (!cp || !*cp) { + bb_info_msg("Normal startup"); + return 0; + } + if (strcmp(pw_encrypt(cp, pwd->pw_passwd), pwd->pw_passwd) == 0) { + break; + } + bb_do_delay(FAIL_DELAY); + bb_error_msg("login incorrect"); + } + memset(cp, 0, strlen(cp)); + signal(SIGALRM, SIG_DFL); + + bb_info_msg("System Maintenance Mode"); + + USE_SELINUX(renew_current_security_context()); + + shell = getenv("SUSHELL"); + if (!shell) shell = getenv("sushell"); + if (!shell) { + shell = "/bin/sh"; + if (pwd->pw_shell[0]) + shell = pwd->pw_shell; + } + run_shell(shell, 1, 0, 0); + /* never returns */ + +auth_error: + bb_error_msg_and_die("no password entry for 'root'"); +} diff --git a/loginutils/vlock.c b/loginutils/vlock.c new file mode 100644 index 000000000..7cc812fbb --- /dev/null +++ b/loginutils/vlock.c @@ -0,0 +1,118 @@ +/* vi: set sw=4 ts=4: */ + +/* + * vlock implementation for busybox + * + * Copyright (C) 2000 by spoon + * Written by spoon + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Shoutz to Michael K. Johnson , author of the + * original vlock. I snagged a bunch of his code to write this + * minimalistic vlock. + */ +/* Fixed by Erik Andersen to do passwords the tinylogin way... + * It now works with md5, sha1, etc passwords. */ + +#include "busybox.h" +#include + +static struct passwd *pw; +static struct vt_mode ovtm; +static struct termios oterm; +static int vfd; +static unsigned long o_lock_all; + +static void release_vt(int signo) +{ + ioctl(vfd, VT_RELDISP, !o_lock_all); +} + +static void acquire_vt(int signo) +{ + ioctl(vfd, VT_RELDISP, VT_ACKACQ); +} + +static void restore_terminal(void) +{ + ioctl(vfd, VT_SETMODE, &ovtm); + tcsetattr(STDIN_FILENO, TCSANOW, &oterm); +} + +int vlock_main(int argc, char **argv) +{ + sigset_t sig; + struct sigaction sa; + struct vt_mode vtm; + struct termios term; + uid_t uid = getuid(); + + pw = getpwuid(uid); + if (pw == NULL) + bb_error_msg_and_die("unknown uid %d", uid); + + if (argc > 2) { + bb_show_usage(); + } + + o_lock_all = getopt32(argc, argv, "a"); + + vfd = xopen(CURRENT_TTY, O_RDWR); + + if (ioctl(vfd, VT_GETMODE, &vtm) < 0) { + bb_perror_msg_and_die("VT_GETMODE"); + } + + /* mask a bunch of signals */ + sigprocmask(SIG_SETMASK, NULL, &sig); + sigdelset(&sig, SIGUSR1); + sigdelset(&sig, SIGUSR2); + sigaddset(&sig, SIGTSTP); + sigaddset(&sig, SIGTTIN); + sigaddset(&sig, SIGTTOU); + sigaddset(&sig, SIGHUP); + sigaddset(&sig, SIGCHLD); + sigaddset(&sig, SIGQUIT); + sigaddset(&sig, SIGINT); + + sigemptyset(&(sa.sa_mask)); + sa.sa_flags = SA_RESTART; + sa.sa_handler = release_vt; + sigaction(SIGUSR1, &sa, NULL); + sa.sa_handler = acquire_vt; + sigaction(SIGUSR2, &sa, NULL); + + /* need to handle some signals so that we don't get killed by them */ + sa.sa_handler = SIG_IGN; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTSTP, &sa, NULL); + + ovtm = vtm; + vtm.mode = VT_PROCESS; + vtm.relsig = SIGUSR1; + vtm.acqsig = SIGUSR2; + ioctl(vfd, VT_SETMODE, &vtm); + + tcgetattr(STDIN_FILENO, &oterm); + term = oterm; + term.c_iflag &= ~BRKINT; + term.c_iflag |= IGNBRK; + term.c_lflag &= ~ISIG; + term.c_lflag &= ~(ECHO | ECHOCTL); + tcsetattr(STDIN_FILENO, TCSANOW, &term); + + do { + printf("Virtual Console%s locked by %s.\n", (o_lock_all) ? "s" : "", pw->pw_name); + if (correct_password(pw)) { + break; + } + bb_do_delay(FAIL_DELAY); + puts("Password incorrect"); + } while (1); + restore_terminal(); + fflush_stdout_and_exit(0); +} diff --git a/miscutils/Config.in b/miscutils/Config.in new file mode 100644 index 000000000..90e2b6fb1 --- /dev/null +++ b/miscutils/Config.in @@ -0,0 +1,374 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Miscellaneous Utilities" + +config ADJTIMEX + bool "adjtimex" + default n + help + Adjtimex reads and optionally sets adjustment parameters for + the Linux clock adjustment algorithm. + +config BBCONFIG + bool "bbconfig" + default n + help + The bbconfig applet will print the config file with which + busybox was built. + +config CROND + bool "crond" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + Crond is a background daemon that parses individual crontab + files and executes commands on behalf of the users in question. + This is a port of dcron from slackware. It uses files of the + format /var/spool/cron/crontabs/ files, for example: + $ cat /var/spool/cron/crontabs/root + # Run daily cron jobs at 4:40 every day: + 40 4 * * * /etc/cron/daily > /dev/null 2>&1 + Note that Busybox binary must be setuid root for this applet to + work properly. + +config DEBUG_CROND_OPTION + bool "Support debug option -d" + depends on CROND + default n + help + Support option -d to enter debug mode. + +config FEATURE_CROND_CALL_SENDMAIL + bool "Using /usr/sbin/sendmail?" + default n + depends on CROND + help + Support calling /usr/sbin/sendmail for send cmd outputs. + +config CRONTAB + bool "crontab" + default n + select FEATURE_SUID + help + Crontab manipulates the crontab for a particular user. Only + the superuser may specify a different user and/or crontab directory. + +config DC + bool "dc" + default n + help + Dc is a reverse-polish desk calculator which supports unlimited + precision arithmetic. + +config DEVFSD + bool "devfsd (obsolete)" + default n + select FEATURE_SYSLOG + help + This is deprecated, and will be removed at the end of 2008. + + Provides compatibility with old device names on a devfs systems. + You should set it to true if you have devfs enabled. + The following keywords in devsfd.conf are supported: + "CLEAR_CONFIG", "INCLUDE", "OPTIONAL_INCLUDE", "RESTORE", + "PERMISSIONS", "EXECUTE", "COPY", "IGNORE", + "MKOLDCOMPAT", "MKNEWCOMPAT","RMOLDCOMPAT", "RMNEWCOMPAT". + + But only if they are written UPPERCASE!!!!!!!! + +config DEVFSD_MODLOAD + bool "Adds support for MODLOAD keyword in devsfd.conf" + default n + depends on DEVFSD + help + This actually doesn't work with busybox modutils but needs + the external modutils. + +config DEVFSD_FG_NP + bool "Enables the -fg and -np options" + default n + depends on DEVFSD + help + -fg Run the daemon in the foreground. + -np Exit after parsing the configuration file. Do not poll for events. + +config DEVFSD_VERBOSE + bool "Increases logging (and size)" + default n + depends on DEVFSD + help + Increases logging to stderr or syslog. + +config FEATURE_DEVFS + bool " Use devfs names for all devices (obsolete)" + default n + help + This is obsolete and will be going away at the end of 2008.. + + This tells busybox to look for names like /dev/loop/0 instead of + /dev/loop0. If your /dev directory has normal names instead of + devfs names, you don't want this. + +config EJECT + bool "eject" + default n + help + Used to eject cdroms. (defaults to /dev/cdrom) + +config LAST + bool "last" + default n + select FEATURE_WTMP + help + 'last' displays a list of the last users that logged into the system. + +config LESS + bool "less" + default n + help + 'less' is a pager, meaning that it displays text files. It possesses + a wide array of features, and is an improvement over 'more'. + +config FEATURE_LESS_BRACKETS + bool "Enable bracket searching" + default y + depends on LESS + help + This option adds the capability to search for matching left and right + brackets, facilitating programming. + +config FEATURE_LESS_FLAGS + bool "Enable extra flags" + default y + depends on LESS + help + The extra flags provided do the following: + + The -M flag enables a more sophisticated status line. + The -m flag enables a simpler status line with a percentage. + +config FEATURE_LESS_FLAGCS + bool "Enable flag changes" + default n + depends on LESS + help + This enables the ability to change command-line flags within + less itself. + +config FEATURE_LESS_MARKS + bool "Enable marks" + default n + depends on LESS + help + Marks enable positions in a file to be stored for easy reference. + +config FEATURE_LESS_REGEXP + bool "Enable regular expressions" + default n + depends on LESS + help + Enable regular expressions, allowing complex file searches. + +config HDPARM + bool "hdparm" + default n + help + Get/Set hard drive parameters. Primarily intended for ATA + drives. Adds about 13k (or around 30k if you enable the + FEATURE_HDPARM_GET_IDENTITY option).... + +config FEATURE_HDPARM_GET_IDENTITY + bool "Support obtaining detailed information directly from drives" + default y + depends on HDPARM + help + Enables the -I and -i options to obtain detailed information + directly from drives about their capabilities and supported ATA + feature set. If no device name is specified, hdparm will read + identify data from stdin. Enabling this option will add about 16k... + +config FEATURE_HDPARM_HDIO_SCAN_HWIF + bool "Register an IDE interface (DANGEROUS)" + default n + depends on HDPARM + help + Enables the 'hdparm -R' option to register an IDE interface. + This is dangerous stuff, so you should probably say N. + +config FEATURE_HDPARM_HDIO_UNREGISTER_HWIF + bool "Un-register an IDE interface (DANGEROUS)" + default n + depends on HDPARM + help + Enables the 'hdparm -U' option to un-register an IDE interface. + This is dangerous stuff, so you should probably say N. + +config FEATURE_HDPARM_HDIO_DRIVE_RESET + bool "perform device reset (DANGEROUS)" + default n + depends on HDPARM + help + Enables the 'hdparm -w' option to perform a device reset. + This is dangerous stuff, so you should probably say N. + +config FEATURE_HDPARM_HDIO_TRISTATE_HWIF + bool "tristate device for hotswap (DANGEROUS)" + default n + depends on HDPARM + help + Enables the 'hdparm -x' option to tristate device for hotswap, + and the '-b' option to get/set bus state. This is dangerous + stuff, so you should probably say N. + +config FEATURE_HDPARM_HDIO_GETSET_DMA + bool "get/set using_dma flag (DANGEROUS)" + default n + depends on HDPARM + help + Enables the 'hdparm -d' option to get/set using_dma flag. + This is dangerous stuff, so you should probably say N. + +config MAKEDEVS + bool "makedevs" + default n + help + 'makedevs' is a utility used to create a batch of devices with + one command. + . + There are two choices for command line behaviour, the interface + as used by LEAF/Linux Router Project, or a device table file. + . + 'leaf' is traditionally what busybox follows, it allows multiple + devices of a particluar type to be created per command. + e.g. /dev/hda[0-9] + Device properties are passed as command line arguments. + . + 'table' reads device properties from a file or stdin, allowing + a batch of unrelated devices to be made with one command. + User/group names are allowed as an alternative to uid/gid. + +choice + prompt "Choose makedevs behaviour" + depends MAKEDEVS + default FEATURE_MAKEDEVS_TABLE + +config FEATURE_MAKEDEVS_LEAF + bool "leaf" + +config FEATURE_MAKEDEVS_TABLE + bool "table" + +endchoice + +config MOUNTPOINT + bool "mountpoint" + default n + help + mountpoint checks if the directory is a mountpoint. + +config MT + bool "mt" + default n + help + mt is used to control tape devices. You can use the mt utility + to advance or rewind a tape past a specified number of archive + files on the tape. + +config NMETER + bool "nmeter" + default n + help + nmeter prints various system parameters continuously. + +config RAIDAUTORUN + bool "raidautorun" + default n + help + raidautorun tells the kernel md driver to + search and start RAID arrays. + +config READAHEAD + bool "readahead" + default n + help + Preload the files listed on the command line into RAM cache so that + subsequent reads on these files will not block on disk I/O. + + This applet just calls the readahead(2) system call on each file. + It is mainly useful in system startup scripts to preload files + or executables before they are used. When used at the right time + (in particular when a CPU boundprocess is running) it can + significantly speed up system startup. + + As readahead(2) blocks until each file has been read, it is best to + run this applet as a background job. + +config RUNLEVEL + bool "runlevel" + default n + help + find the current and previous system runlevel. + + This applet uses utmp but does not rely on busybox supporing + utmp on purpose. It is used by e.g. emdebian via /etc/init.d/rc. + +config RX + bool "rx" + default n + help + Receive files using the Xmodem protocol. + +config STRINGS + bool "strings" + default n + help + strings prints the printable character sequences for each file + specified. + +config SETSID + bool "setsid" + default n + help + setsid runs a program in a new session + +config TASKSET + bool "taskset" + default n + help + Retrieve or set a processes's CPU affinity. + This requires sched_{g,s}etaffinity support in your libc. + +config FEATURE_TASKSET_FANCY + bool "fancy output" + default y + depends on TASKSET + help + Add code for fancy output. This merely silences a compiler-warning + and adds about 135 Bytes. May be needed for machines with alot + of CPUs. + +config TIME + bool "time" + default n + help + The time command runs the specified program with the given arguments. + When the command finishes, time writes a message to standard output + giving timing statistics about this program run. + +config WATCHDOG + bool "watchdog" + default n + help + The watchdog utility is used with hardware or software watchdog + device drivers. It opens the specified watchdog device special file + and periodically writes a magic character to the device. If the + watchdog applet ever fails to write the magic character within a + certain amount of time, the watchdog device assumes the system has + hung, and will cause the hardware to reboot. + +endmenu + diff --git a/miscutils/Kbuild b/miscutils/Kbuild new file mode 100644 index 000000000..8d1b9f484 --- /dev/null +++ b/miscutils/Kbuild @@ -0,0 +1,30 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_ADJTIMEX) += adjtimex.o +lib-$(CONFIG_CROND) += crond.o +lib-$(CONFIG_CRONTAB) += crontab.o +lib-$(CONFIG_BBCONFIG) += bbconfig.o +lib-$(CONFIG_DC) += dc.o +lib-$(CONFIG_DEVFSD) += devfsd.o +lib-$(CONFIG_EJECT) += eject.o +lib-$(CONFIG_HDPARM) += hdparm.o +lib-$(CONFIG_LAST) += last.o +lib-$(CONFIG_LESS) += less.o +lib-$(CONFIG_MAKEDEVS) += makedevs.o +lib-$(CONFIG_MOUNTPOINT) += mountpoint.o +lib-$(CONFIG_MT) += mt.o +lib-$(CONFIG_NMETER) += nmeter.o +lib-$(CONFIG_RAIDAUTORUN) += raidautorun.o +lib-$(CONFIG_READAHEAD) += readahead.o +lib-$(CONFIG_RUNLEVEL) += runlevel.o +lib-$(CONFIG_RX) += rx.o +lib-$(CONFIG_SETSID) += setsid.o +lib-$(CONFIG_STRINGS) += strings.o +lib-$(CONFIG_TASKSET) += taskset.o +lib-$(CONFIG_TIME) += time.o +lib-$(CONFIG_WATCHDOG) += watchdog.o diff --git a/miscutils/adjtimex.c b/miscutils/adjtimex.c new file mode 100644 index 000000000..b35538a84 --- /dev/null +++ b/miscutils/adjtimex.c @@ -0,0 +1,117 @@ +/* vi: set sw=4 ts=4: */ +/* + * adjtimex.c - read, and possibly modify, the Linux kernel `timex' variables. + * + * Originally written: October 1997 + * Last hack: March 2001 + * Copyright 1997, 2000, 2001 Larry Doolittle + * + * busyboxed 20 March 2001, Larry Doolittle + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ + +#include "busybox.h" +#include + +static const struct {int bit; const char *name;} statlist[] = { + { STA_PLL, "PLL" }, + { STA_PPSFREQ, "PPSFREQ" }, + { STA_PPSTIME, "PPSTIME" }, + { STA_FLL, "FFL" }, + { STA_INS, "INS" }, + { STA_DEL, "DEL" }, + { STA_UNSYNC, "UNSYNC" }, + { STA_FREQHOLD, "FREQHOLD" }, + { STA_PPSSIGNAL, "PPSSIGNAL" }, + { STA_PPSJITTER, "PPSJITTER" }, + { STA_PPSWANDER, "PPSWANDER" }, + { STA_PPSERROR, "PPSERROR" }, + { STA_CLOCKERR, "CLOCKERR" }, + { 0, NULL } }; + +static const char * const ret_code_descript[] = { + "clock synchronized", + "insert leap second", + "delete leap second", + "leap second in progress", + "leap second has occurred", + "clock not synchronized" }; + +int adjtimex_main(int argc, char **argv) +{ + enum { + OPT_quiet = 0x1 + }; + unsigned opt; + char *opt_o, *opt_f, *opt_p, *opt_t; + struct timex txc; + int i, ret, sep; + const char *descript; + txc.modes=0; + + opt = getopt32(argc, argv, "qo:f:p:t:", + &opt_o, &opt_f, &opt_p, &opt_t); + //if (opt & 0x1) // -q + if (opt & 0x2) { // -o + txc.offset = xatoi(opt_o); + txc.modes |= ADJ_OFFSET_SINGLESHOT; + } + if (opt & 0x4) { // -f + txc.freq = xatou(opt_f); + txc.modes |= ADJ_FREQUENCY; + } + if (opt & 0x8) { // -p + txc.constant = xatoi(opt_p); + txc.modes |= ADJ_TIMECONST; + } + if (opt & 0x10) { // -t + txc.tick = xatoi(opt_t); + txc.modes |= ADJ_TICK; + } + if (argc != optind) { /* no valid non-option parameters */ + bb_show_usage(); + } + + ret = adjtimex(&txc); + + if (ret < 0) perror("adjtimex"); + + if (!(opt & OPT_quiet) && ret>=0) { + printf( + " mode: %d\n" + "-o offset: %ld\n" + "-f frequency: %ld\n" + " maxerror: %ld\n" + " esterror: %ld\n" + " status: %d ( ", + txc.modes, txc.offset, txc.freq, txc.maxerror, + txc.esterror, txc.status); + + /* representative output of next code fragment: + "PLL | PPSTIME" */ + sep=0; + for (i=0; statlist[i].name; i++) { + if (txc.status & statlist[i].bit) { + if (sep) fputs(" | ",stdout); + fputs(statlist[i].name,stdout); + sep=1; + } + } + + descript = "error"; + if (ret >= 0 && ret <= 5) descript = ret_code_descript[ret]; + printf(" )\n" + "-p timeconstant: %ld\n" + " precision: %ld\n" + " tolerance: %ld\n" + "-t tick: %ld\n" + " time.tv_sec: %ld\n" + " time.tv_usec: %ld\n" + " return value: %d (%s)\n", + txc.constant, + txc.precision, txc.tolerance, txc.tick, + (long)txc.time.tv_sec, (long)txc.time.tv_usec, ret, descript); + } + return (ret<0); +} diff --git a/miscutils/bbconfig.c b/miscutils/bbconfig.c new file mode 100644 index 000000000..ce14d4a1f --- /dev/null +++ b/miscutils/bbconfig.c @@ -0,0 +1,11 @@ +/* vi: set sw=4 ts=4: */ +/* This file was released into the public domain by Paul Fox. + */ +#include "busybox.h" +#include "bbconfigopts.h" + +int bbconfig_main(int argc, char **argv) +{ + printf(bbconfig_config); + return 0; +} diff --git a/miscutils/crond.c b/miscutils/crond.c new file mode 100644 index 000000000..fa7964e4e --- /dev/null +++ b/miscutils/crond.c @@ -0,0 +1,1016 @@ +/* vi: set sw=4 ts=4: */ +/* + * crond -d[#] -c -f -b + * + * run as root, but NOT setuid root + * + * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) + * Vladimir Oleynik (C) 2002 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#define VERSION "2.3.2" + +#include "busybox.h" +#include + +#define arysize(ary) (sizeof(ary)/sizeof((ary)[0])) + +#ifndef CRONTABS +#define CRONTABS "/var/spool/cron/crontabs" +#endif +#ifndef TMPDIR +#define TMPDIR "/var/spool/cron" +#endif +#ifndef SENDMAIL +#define SENDMAIL "/usr/sbin/sendmail" +#endif +#ifndef SENDMAIL_ARGS +#define SENDMAIL_ARGS "-ti", "oem" +#endif +#ifndef CRONUPDATE +#define CRONUPDATE "cron.update" +#endif +#ifndef MAXLINES +#define MAXLINES 256 /* max lines in non-root crontabs */ +#endif + +typedef struct CronFile { + struct CronFile *cf_Next; + struct CronLine *cf_LineBase; + char *cf_User; /* username */ + int cf_Ready; /* bool: one or more jobs ready */ + int cf_Running; /* bool: one or more jobs running */ + int cf_Deleted; /* marked for deletion, ignore */ +} CronFile; + +typedef struct CronLine { + struct CronLine *cl_Next; + char *cl_Shell; /* shell command */ + pid_t cl_Pid; /* running pid, 0, or armed (-1) */ + int cl_MailFlag; /* running pid is for mail */ + int cl_MailPos; /* 'empty file' size */ + char cl_Mins[60]; /* 0-59 */ + char cl_Hrs[24]; /* 0-23 */ + char cl_Days[32]; /* 1-31 */ + char cl_Mons[12]; /* 0-11 */ + char cl_Dow[7]; /* 0-6, beginning sunday */ +} CronLine; + +#define RUN_RANOUT 1 +#define RUN_RUNNING 2 +#define RUN_FAILED 3 + +#define DaemonUid 0 + +#if ENABLE_DEBUG_CROND_OPTION +static unsigned DebugOpt; +#endif + +static unsigned LogLevel = 8; +static const char *LogFile; +static const char *CDir = CRONTABS; + +static void startlogger(void); + +static void CheckUpdates(void); +static void SynchronizeDir(void); +static int TestJobs(time_t t1, time_t t2); +static void RunJobs(void); +static int CheckJobs(void); + +static void RunJob(const char *user, CronLine * line); + +#if ENABLE_FEATURE_CROND_CALL_SENDMAIL +static void EndJob(const char *user, CronLine * line); +#else +#define EndJob(user, line) line->cl_Pid = 0 +#endif + +static void DeleteFile(const char *userName); + +static CronFile *FileBase; + + +static void crondlog(const char *ctl, ...) +{ + va_list va; + const char *fmt; + int level = (int) (ctl[0] & 0xf); + int type = level == 20 ? + LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE); + + + va_start(va, ctl); + fmt = ctl + 1; + if (level >= LogLevel) { + +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + vfprintf(stderr, fmt, va); + } else +#endif + if (LogFile == 0) { + vsyslog(type, fmt, va); + } else { + int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); + if (logfd >= 0) { + vdprintf(logfd, fmt, va); + close(logfd); +#if ENABLE_DEBUG_CROND_OPTION + } else { + bb_perror_msg("can't open log file"); +#endif + } + } + } + va_end(va); + if (ctl[0] & 0200) { + exit(20); + } +} + +int crond_main(int ac, char **av) +{ + unsigned opt; + char *lopt, *Lopt, *copt; + +#if ENABLE_DEBUG_CROND_OPTION + char *dopt; + + opt_complementary = "f-b:b-f:S-L:L-S:d-l"; +#else + opt_complementary = "f-b:b-f:S-L:L-S"; +#endif + + opterr = 0; /* disable getopt 'errors' message. */ + opt = getopt32(ac, av, "l:L:fbSc:" +#if ENABLE_DEBUG_CROND_OPTION + "d:" +#endif + , &lopt, &Lopt, &copt +#if ENABLE_DEBUG_CROND_OPTION + , &dopt +#endif + ); + if (opt & 1) { + LogLevel = xatou(lopt); + } + if (opt & 2) { + if (*Lopt != 0) { + LogFile = Lopt; + } + } + if (opt & 32) { + if (*copt != 0) { + CDir = copt; + } + } +#if ENABLE_DEBUG_CROND_OPTION + if (opt & 64) { + DebugOpt = xatou(dopt); + LogLevel = 0; + } +#endif + + /* + * change directory + */ + + xchdir(CDir); + signal(SIGHUP, SIG_IGN); /* hmm.. but, if kill -HUP original + * version - his died. ;( + */ + /* + * close stdin and stdout, stderr. + * close unused descriptors - don't need. + * optional detach from controlling terminal + */ + + if (!(opt & 4)) { +#ifdef BB_NOMMU + /* reexec for vfork() do continue parent */ + vfork_daemon_rexec(1, 0, ac, av, "-f"); +#else + xdaemon(1, 0); +#endif + } + + (void) startlogger(); /* need if syslog mode selected */ + + /* + * main loop - synchronize to 1 second after the minute, minimum sleep + * of 1 second. + */ + + crondlog("\011%s " VERSION " dillon, started, log level %d\n", + applet_name, LogLevel); + + SynchronizeDir(); + + { + time_t t1 = time(NULL); + time_t t2; + long dt; + int rescan = 60; + short sleep_time = 60; + + for (;;) { + sleep((sleep_time + 1) - (short) (time(NULL) % sleep_time)); + + t2 = time(NULL); + dt = t2 - t1; + + /* + * The file 'cron.update' is checked to determine new cron + * jobs. The directory is rescanned once an hour to deal + * with any screwups. + * + * check for disparity. Disparities over an hour either way + * result in resynchronization. A reverse-indexed disparity + * less then an hour causes us to effectively sleep until we + * match the original time (i.e. no re-execution of jobs that + * have just been run). A forward-indexed disparity less then + * an hour causes intermediate jobs to be run, but only once + * in the worst case. + * + * when running jobs, the inequality used is greater but not + * equal to t1, and less then or equal to t2. + */ + + if (--rescan == 0) { + rescan = 60; + SynchronizeDir(); + } + CheckUpdates(); +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) + crondlog("\005Wakeup dt=%d\n", dt); +#endif + if (dt < -60 * 60 || dt > 60 * 60) { + t1 = t2; + crondlog("\111time disparity of %d minutes detected\n", dt / 60); + } else if (dt > 0) { + TestJobs(t1, t2); + RunJobs(); + sleep(5); + if (CheckJobs() > 0) { + sleep_time = 10; + } else { + sleep_time = 60; + } + t1 = t2; + } + } + } + return 0; /* not reached */ +} + +static int ChangeUser(const char *user) +{ + struct passwd *pas; + const char *err_msg; + + /* + * Obtain password entry and change privileges + */ + pas = getpwnam(user); + if (pas == 0) { + crondlog("\011failed to get uid for %s", user); + return -1; + } + setenv("USER", pas->pw_name, 1); + setenv("HOME", pas->pw_dir, 1); + setenv("SHELL", DEFAULT_SHELL, 1); + + /* + * Change running state to the user in question + */ + err_msg = change_identity_e2str(pas); + if (err_msg) { + crondlog("\011%s for user %s", err_msg, user); + return -1; + } + if (chdir(pas->pw_dir) < 0) { + crondlog("\011chdir failed: %s: %m", pas->pw_dir); + if (chdir(TMPDIR) < 0) { + crondlog("\011chdir failed: %s: %m", TMPDIR); + return -1; + } + } + return pas->pw_uid; +} + +static void startlogger(void) +{ + if (LogFile == 0) { + openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON); + } +#if ENABLE_DEBUG_CROND_OPTION + else { /* test logfile */ + int logfd; + + if ((logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600)) >= 0) { + close(logfd); + } else { + bb_perror_msg("failed to open log file '%s': ", LogFile); + } + } +#endif +} + + +static const char *const DowAry[] = { + "sun", + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", + + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + NULL +}; + +static const char *const MonAry[] = { + "jan", + "feb", + "mar", + "apr", + "may", + "jun", + "jul", + "aug", + "sep", + "oct", + "nov", + "dec", + + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + NULL +}; + +static char *ParseField(char *user, char *ary, int modvalue, int off, + const char *const *names, char *ptr) +{ + char *base = ptr; + int n1 = -1; + int n2 = -1; + + if (base == NULL) { + return NULL; + } + + while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { + int skip = 0; + + /* Handle numeric digit or symbol or '*' */ + + if (*ptr == '*') { + n1 = 0; /* everything will be filled */ + n2 = modvalue - 1; + skip = 1; + ++ptr; + } else if (*ptr >= '0' && *ptr <= '9') { + if (n1 < 0) { + n1 = strtol(ptr, &ptr, 10) + off; + } else { + n2 = strtol(ptr, &ptr, 10) + off; + } + skip = 1; + } else if (names) { + int i; + + for (i = 0; names[i]; ++i) { + if (strncmp(ptr, names[i], strlen(names[i])) == 0) { + break; + } + } + if (names[i]) { + ptr += strlen(names[i]); + if (n1 < 0) { + n1 = i; + } else { + n2 = i; + } + skip = 1; + } + } + + /* handle optional range '-' */ + + if (skip == 0) { + crondlog("\111failed user %s parsing %s\n", user, base); + return NULL; + } + if (*ptr == '-' && n2 < 0) { + ++ptr; + continue; + } + + /* + * collapse single-value ranges, handle skipmark, and fill + * in the character array appropriately. + */ + + if (n2 < 0) { + n2 = n1; + } + if (*ptr == '/') { + skip = strtol(ptr + 1, &ptr, 10); + } + /* + * fill array, using a failsafe is the easiest way to prevent + * an endless loop + */ + + { + int s0 = 1; + int failsafe = 1024; + + --n1; + do { + n1 = (n1 + 1) % modvalue; + + if (--s0 == 0) { + ary[n1 % modvalue] = 1; + s0 = skip; + } + } + while (n1 != n2 && --failsafe); + + if (failsafe == 0) { + crondlog("\111failed user %s parsing %s\n", user, base); + return NULL; + } + } + if (*ptr != ',') { + break; + } + ++ptr; + n1 = -1; + n2 = -1; + } + + if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { + crondlog("\111failed user %s parsing %s\n", user, base); + return NULL; + } + + while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') { + ++ptr; + } +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + int i; + + for (i = 0; i < modvalue; ++i) { + crondlog("\005%d", ary[i]); + } + crondlog("\005\n"); + } +#endif + + return ptr; +} + +static void FixDayDow(CronLine * line) +{ + int i; + int weekUsed = 0; + int daysUsed = 0; + + for (i = 0; i < (int)(arysize(line->cl_Dow)); ++i) { + if (line->cl_Dow[i] == 0) { + weekUsed = 1; + break; + } + } + for (i = 0; i < (int)(arysize(line->cl_Days)); ++i) { + if (line->cl_Days[i] == 0) { + daysUsed = 1; + break; + } + } + if (weekUsed && !daysUsed) { + memset(line->cl_Days, 0, sizeof(line->cl_Days)); + } + if (daysUsed && !weekUsed) { + memset(line->cl_Dow, 0, sizeof(line->cl_Dow)); + } +} + + + +static void SynchronizeFile(const char *fileName) +{ + int maxEntries = MAXLINES; + int maxLines; + char buf[1024]; + + if (strcmp(fileName, "root") == 0) { + maxEntries = 65535; + } + maxLines = maxEntries * 10; + + if (fileName) { + FILE *fi; + + DeleteFile(fileName); + + fi = fopen(fileName, "r"); + if (fi != NULL) { + struct stat sbuf; + + if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) { + CronFile *file = calloc(1, sizeof(CronFile)); + CronLine **pline; + + file->cf_User = strdup(fileName); + pline = &file->cf_LineBase; + + while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) { + CronLine line; + char *ptr; + + trim(buf); + if (buf[0] == 0 || buf[0] == '#') { + continue; + } + if (--maxEntries == 0) { + break; + } + memset(&line, 0, sizeof(line)); + +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + crondlog("\111User %s Entry %s\n", fileName, buf); + } +#endif + + /* parse date ranges */ + ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf); + ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr); + ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr); + ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr); + ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr); + + /* check failure */ + if (ptr == NULL) { + continue; + } + + /* + * fix days and dow - if one is not * and the other + * is *, the other is set to 0, and vise-versa + */ + + FixDayDow(&line); + + *pline = calloc(1, sizeof(CronLine)); + **pline = line; + + /* copy command */ + (*pline)->cl_Shell = strdup(ptr); + +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + crondlog("\111 Command %s\n", ptr); + } +#endif + + pline = &((*pline)->cl_Next); + } + *pline = NULL; + + file->cf_Next = FileBase; + FileBase = file; + + if (maxLines == 0 || maxEntries == 0) { + crondlog("\111Maximum number of lines reached for user %s\n", fileName); + } + } + fclose(fi); + } + } +} + +static void CheckUpdates(void) +{ + FILE *fi; + char buf[256]; + + fi = fopen(CRONUPDATE, "r"); + if (fi != NULL) { + remove(CRONUPDATE); + while (fgets(buf, sizeof(buf), fi) != NULL) { + SynchronizeFile(strtok(buf, " \t\r\n")); + } + fclose(fi); + } +} + +static void SynchronizeDir(void) +{ + /* Attempt to delete the database. */ + + for (;;) { + CronFile *file; + + for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next); + if (file == NULL) { + break; + } + DeleteFile(file->cf_User); + } + + /* + * Remove cron update file + * + * Re-chdir, in case directory was renamed & deleted, or otherwise + * screwed up. + * + * scan directory and add associated users + */ + + remove(CRONUPDATE); + if (chdir(CDir) < 0) { + crondlog("\311cannot find %s\n", CDir); + } + { + DIR *dir = opendir("."); + struct dirent *den; + + if (dir) { + while ((den = readdir(dir))) { + if (strchr(den->d_name, '.') != NULL) { + continue; + } + if (getpwnam(den->d_name)) { + SynchronizeFile(den->d_name); + } else { + crondlog("\007ignoring %s\n", den->d_name); + } + } + closedir(dir); + } else { + crondlog("\311cannot open current dir!\n"); + } + } +} + + +/* + * DeleteFile() - delete user database + * + * Note: multiple entries for same user may exist if we were unable to + * completely delete a database due to running processes. + */ + +static void DeleteFile(const char *userName) +{ + CronFile **pfile = &FileBase; + CronFile *file; + + while ((file = *pfile) != NULL) { + if (strcmp(userName, file->cf_User) == 0) { + CronLine **pline = &file->cf_LineBase; + CronLine *line; + + file->cf_Running = 0; + file->cf_Deleted = 1; + + while ((line = *pline) != NULL) { + if (line->cl_Pid > 0) { + file->cf_Running = 1; + pline = &line->cl_Next; + } else { + *pline = line->cl_Next; + free(line->cl_Shell); + free(line); + } + } + if (file->cf_Running == 0) { + *pfile = file->cf_Next; + free(file->cf_User); + free(file); + } else { + pfile = &file->cf_Next; + } + } else { + pfile = &file->cf_Next; + } + } +} + +/* + * TestJobs() + * + * determine which jobs need to be run. Under normal conditions, the + * period is about a minute (one scan). Worst case it will be one + * hour (60 scans). + */ + +static int TestJobs(time_t t1, time_t t2) +{ + int nJobs = 0; + time_t t; + + /* Find jobs > t1 and <= t2 */ + + for (t = t1 - t1 % 60; t <= t2; t += 60) { + if (t > t1) { + struct tm *tp = localtime(&t); + CronFile *file; + CronLine *line; + + for (file = FileBase; file; file = file->cf_Next) { +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) + crondlog("\005FILE %s:\n", file->cf_User); +#endif + if (file->cf_Deleted) + continue; + for (line = file->cf_LineBase; line; line = line->cl_Next) { +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) + crondlog("\005 LINE %s\n", line->cl_Shell); +#endif + if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] && + (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) + && line->cl_Mons[tp->tm_mon]) { +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + crondlog("\005 JobToDo: %d %s\n", + line->cl_Pid, line->cl_Shell); + } +#endif + if (line->cl_Pid > 0) { + crondlog("\010 process already running: %s %s\n", + file->cf_User, line->cl_Shell); + } else if (line->cl_Pid == 0) { + line->cl_Pid = -1; + file->cf_Ready = 1; + ++nJobs; + } + } + } + } + } + } + return nJobs; +} + +static void RunJobs(void) +{ + CronFile *file; + CronLine *line; + + for (file = FileBase; file; file = file->cf_Next) { + if (file->cf_Ready) { + file->cf_Ready = 0; + + for (line = file->cf_LineBase; line; line = line->cl_Next) { + if (line->cl_Pid < 0) { + + RunJob(file->cf_User, line); + + crondlog("\010USER %s pid %3d cmd %s\n", + file->cf_User, line->cl_Pid, line->cl_Shell); + if (line->cl_Pid < 0) { + file->cf_Ready = 1; + } + else if (line->cl_Pid > 0) { + file->cf_Running = 1; + } + } + } + } + } +} + +/* + * CheckJobs() - check for job completion + * + * Check for job completion, return number of jobs still running after + * all done. + */ + +static int CheckJobs(void) +{ + CronFile *file; + CronLine *line; + int nStillRunning = 0; + + for (file = FileBase; file; file = file->cf_Next) { + if (file->cf_Running) { + file->cf_Running = 0; + + for (line = file->cf_LineBase; line; line = line->cl_Next) { + if (line->cl_Pid > 0) { + int status; + int r = wait4(line->cl_Pid, &status, WNOHANG, NULL); + + if (r < 0 || r == line->cl_Pid) { + EndJob(file->cf_User, line); + if (line->cl_Pid) { + file->cf_Running = 1; + } + } else if (r == 0) { + file->cf_Running = 1; + } + } + } + } + nStillRunning += file->cf_Running; + } + return nStillRunning; +} + + +#if ENABLE_FEATURE_CROND_CALL_SENDMAIL +static void +ForkJob(const char *user, CronLine * line, int mailFd, + const char *prog, const char *cmd, const char *arg, const char *mailf) +{ + /* Fork as the user in question and run program */ + pid_t pid = fork(); + + line->cl_Pid = pid; + if (pid == 0) { + /* CHILD */ + + /* Change running state to the user in question */ + + if (ChangeUser(user) < 0) { + exit(0); + } +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + crondlog("\005Child Running %s\n", prog); + } +#endif + + if (mailFd >= 0) { + dup2(mailFd, mailf != NULL); + dup2((mailf ? mailFd : 1), 2); + close(mailFd); + } + execl(prog, prog, cmd, arg, NULL); + crondlog("\024cannot exec, user %s cmd %s %s %s\n", user, prog, cmd, arg); + if (mailf) { + fdprintf(1, "Exec failed: %s -c %s\n", prog, arg); + } + exit(0); + } else if (pid < 0) { + /* FORK FAILED */ + crondlog("\024cannot fork, user %s\n", user); + line->cl_Pid = 0; + if (mailf) { + remove(mailf); + } + } else if (mailf) { + /* PARENT, FORK SUCCESS + * rename mail-file based on pid of process + */ + char mailFile2[128]; + + snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid); + rename(mailf, mailFile2); + } + /* + * Close the mail file descriptor.. we can't just leave it open in + * a structure, closing it later, because we might run out of descriptors + */ + + if (mailFd >= 0) { + close(mailFd); + } +} + +static void RunJob(const char *user, CronLine * line) +{ + char mailFile[128]; + int mailFd; + + line->cl_Pid = 0; + line->cl_MailFlag = 0; + + /* open mail file - owner root so nobody can screw with it. */ + + snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, getpid()); + mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); + + if (mailFd >= 0) { + line->cl_MailFlag = 1; + fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user, + line->cl_Shell); + line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR); + } else { + crondlog("\024cannot create mail file user %s file %s, output to /dev/null\n", user, mailFile); + } + + ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile); +} + +/* + * EndJob - called when job terminates and when mail terminates + */ + +static void EndJob(const char *user, CronLine * line) +{ + int mailFd; + char mailFile[128]; + struct stat sbuf; + + /* No job */ + + if (line->cl_Pid <= 0) { + line->cl_Pid = 0; + return; + } + + /* + * End of job and no mail file + * End of sendmail job + */ + + snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, line->cl_Pid); + line->cl_Pid = 0; + + if (line->cl_MailFlag != 1) { + return; + } + line->cl_MailFlag = 0; + + /* + * End of primary job - check for mail file. If size has increased and + * the file is still valid, we sendmail it. + */ + + mailFd = open(mailFile, O_RDONLY); + remove(mailFile); + if (mailFd < 0) { + return; + } + + if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid || sbuf.st_nlink != 0 || + sbuf.st_size == line->cl_MailPos || !S_ISREG(sbuf.st_mode)) { + close(mailFd); + return; + } + ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL); +} +#else +/* crond without sendmail */ + +static void RunJob(const char *user, CronLine * line) +{ + /* Fork as the user in question and run program */ + pid_t pid = fork(); + + if (pid == 0) { + /* CHILD */ + + /* Change running state to the user in question */ + + if (ChangeUser(user) < 0) { + exit(0); + } +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + crondlog("\005Child Running %s\n", DEFAULT_SHELL); + } +#endif + + execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL); + crondlog("\024cannot exec, user %s cmd %s -c %s\n", user, + DEFAULT_SHELL, line->cl_Shell); + exit(0); + } else if (pid < 0) { + /* FORK FAILED */ + crondlog("\024cannot, user %s\n", user); + pid = 0; + } + line->cl_Pid = pid; +} +#endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */ diff --git a/miscutils/crontab.c b/miscutils/crontab.c new file mode 100644 index 000000000..39d3aae41 --- /dev/null +++ b/miscutils/crontab.c @@ -0,0 +1,344 @@ +/* vi: set sw=4 ts=4: */ +/* + * CRONTAB + * + * usually setuid root, -c option only works if getuid() == geteuid() + * + * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) + * Vladimir Oleynik (C) 2002 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +#ifndef CRONTABS +#define CRONTABS "/var/spool/cron/crontabs" +#endif +#ifndef TMPDIR +#define TMPDIR "/var/spool/cron" +#endif +#ifndef CRONUPDATE +#define CRONUPDATE "cron.update" +#endif +#ifndef PATH_VI +#define PATH_VI "/bin/vi" /* location of vi */ +#endif + +static const char *CDir = CRONTABS; + +static void EditFile(const char *user, const char *file); +static int GetReplaceStream(const char *user, const char *file); +static int ChangeUser(const char *user, short dochdir); + +int crontab_main(int ac, char **av) +{ + enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE; + const struct passwd *pas; + const char *repFile = NULL; + int repFd = 0; + int i; + char caller[256]; /* user that ran program */ + char buf[1024]; + int UserId; + + UserId = getuid(); + pas = getpwuid(UserId); + if (pas == NULL) + bb_perror_msg_and_die("getpwuid"); + + safe_strncpy(caller, pas->pw_name, sizeof(caller)); + + i = 1; + if (ac > 1) { + if (av[1][0] == '-' && av[1][1] == 0) { + option = REPLACE; + ++i; + } else if (av[1][0] != '-') { + option = REPLACE; + ++i; + repFile = av[1]; + } + } + + for (; i < ac; ++i) { + char *ptr = av[i]; + + if (*ptr != '-') + break; + ptr += 2; + + switch (ptr[-1]) { + case 'l': + if (ptr[-1] == 'l') + option = LIST; + /* fall through */ + case 'e': + if (ptr[-1] == 'e') + option = EDIT; + /* fall through */ + case 'd': + if (ptr[-1] == 'd') + option = DELETE; + /* fall through */ + case 'u': + if (i + 1 < ac && av[i+1][0] != '-') { + ++i; + if (getuid() == geteuid()) { + pas = getpwnam(av[i]); + if (pas) { + UserId = pas->pw_uid; + } else { + bb_error_msg_and_die("user %s unknown", av[i]); + } + } else { + bb_error_msg_and_die("only the superuser may specify a user"); + } + } + break; + case 'c': + if (getuid() == geteuid()) { + CDir = (*ptr) ? ptr : av[++i]; + } else { + bb_error_msg_and_die("-c option: superuser only"); + } + break; + default: + i = ac; + break; + } + } + if (i != ac || option == NONE) + bb_show_usage(); + + /* + * Get password entry + */ + + pas = getpwuid(UserId); + if (pas == NULL) + bb_perror_msg_and_die("getpwuid"); + + /* + * If there is a replacement file, obtain a secure descriptor to it. + */ + + if (repFile) { + repFd = GetReplaceStream(caller, repFile); + if (repFd < 0) + bb_error_msg_and_die("cannot read replacement file"); + } + + /* + * Change directory to our crontab directory + */ + + xchdir(CDir); + + /* + * Handle options as appropriate + */ + + switch (option) { + case LIST: + { + FILE *fi; + + fi = fopen(pas->pw_name, "r"); + if (fi) { + while (fgets(buf, sizeof(buf), fi) != NULL) + fputs(buf, stdout); + fclose(fi); + } else { + bb_error_msg("no crontab for %s", pas->pw_name); + } + } + break; + case EDIT: + { +/* FIXME: messy code here! we have file copying helpers for this! */ + FILE *fi; + int fd; + int n; + char tmp[128]; + + snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid()); + fd = xopen3(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600); +/* race, use fchown */ + chown(tmp, getuid(), getgid()); + fi = fopen(pas->pw_name, "r"); + if (fi) { + while ((n = fread(buf, 1, sizeof(buf), fi)) > 0) + full_write(fd, buf, n); + } + EditFile(caller, tmp); + remove(tmp); + lseek(fd, 0L, SEEK_SET); + repFd = fd; + } + option = REPLACE; + /* fall through */ + case REPLACE: + { +/* same here */ + char path[1024]; + int fd; + int n; + + snprintf(path, sizeof(path), "%s.new", pas->pw_name); + fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600); + if (fd >= 0) { + while ((n = read(repFd, buf, sizeof(buf))) > 0) { + full_write(fd, buf, n); + } + close(fd); + rename(path, pas->pw_name); + } else { + bb_error_msg("cannot create %s/%s", CDir, path); + } + close(repFd); + } + break; + case DELETE: + remove(pas->pw_name); + break; + case NONE: + default: + break; + } + + /* + * Bump notification file. Handle window where crond picks file up + * before we can write our entry out. + */ + + if (option == REPLACE || option == DELETE) { + FILE *fo; + struct stat st; + + while ((fo = fopen(CRONUPDATE, "a"))) { + fprintf(fo, "%s\n", pas->pw_name); + fflush(fo); + if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) { + fclose(fo); + break; + } + fclose(fo); + /* loop */ + } + if (fo == NULL) { + bb_error_msg("cannot append to %s/%s", CDir, CRONUPDATE); + } + } + return 0; +} + +static int GetReplaceStream(const char *user, const char *file) +{ + int filedes[2]; + int pid; + int fd; + int n; + char buf[1024]; + + if (pipe(filedes) < 0) { + perror("pipe"); + return -1; + } + pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + if (pid > 0) { + /* + * PARENT + */ + + close(filedes[1]); + if (read(filedes[0], buf, 1) != 1) { + close(filedes[0]); + filedes[0] = -1; + } + return filedes[0]; + } + + /* + * CHILD + */ + + close(filedes[0]); + + if (ChangeUser(user, 0) < 0) + exit(0); + + xfunc_error_retval = 0; + fd = xopen(file, O_RDONLY); + buf[0] = 0; + write(filedes[1], buf, 1); + while ((n = read(fd, buf, sizeof(buf))) > 0) { + write(filedes[1], buf, n); + } + exit(0); +} + +static void EditFile(const char *user, const char *file) +{ + int pid = fork(); + + if (pid == 0) { + /* + * CHILD - change user and run editor + */ + char *ptr; + char visual[1024]; + + if (ChangeUser(user, 1) < 0) + exit(0); + ptr = getenv("VISUAL"); + if (ptr == NULL || strlen(ptr) > 256) + ptr = PATH_VI; + + snprintf(visual, sizeof(visual), "%s %s", ptr, file); + execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", visual, NULL); + perror("exec"); + exit(0); + } + if (pid < 0) { + /* + * PARENT - failure + */ + bb_perror_msg_and_die("fork"); + } + wait4(pid, NULL, 0, NULL); +} + +static int ChangeUser(const char *user, short dochdir) +{ + struct passwd *pas; + + /* + * Obtain password entry and change privileges + */ + + pas = getpwnam(user); + if (pas == NULL) { + bb_perror_msg_and_die("failed to get uid for %s", user); + } + setenv("USER", pas->pw_name, 1); + setenv("HOME", pas->pw_dir, 1); + setenv("SHELL", DEFAULT_SHELL, 1); + + /* + * Change running state to the user in question + */ + change_identity(pas); + + if (dochdir) { + if (chdir(pas->pw_dir) < 0) { + bb_perror_msg("chdir(%s) by %s failed", pas->pw_dir, user); + xchdir(TMPDIR); + } + } + return pas->pw_uid; +} diff --git a/miscutils/dc.c b/miscutils/dc.c new file mode 100644 index 000000000..7b6405754 --- /dev/null +++ b/miscutils/dc.c @@ -0,0 +1,231 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include +#include +#include +#include + +/* Tiny RPN calculator, because "expr" didn't give me bitwise operations. */ + +static double stack[100]; +static unsigned int pointer; +static unsigned char base; + +static void push(double a) +{ + if (pointer >= (sizeof(stack) / sizeof(*stack))) + bb_error_msg_and_die("stack overflow"); + stack[pointer++] = a; +} + +static double pop(void) +{ + if (pointer == 0) + bb_error_msg_and_die("stack underflow"); + return stack[--pointer]; +} + +static void add(void) +{ + push(pop() + pop()); +} + +static void sub(void) +{ + double subtrahend = pop(); + + push(pop() - subtrahend); +} + +static void mul(void) +{ + push(pop() * pop()); +} + +static void power(void) +{ + double topower = pop(); + + push(pow(pop(), topower)); +} + +static void divide(void) +{ + double divisor = pop(); + + push(pop() / divisor); +} + +static void mod(void) +{ + unsigned int d = pop(); + + push((unsigned int) pop() % d); +} + +static void and(void) +{ + push((unsigned int) pop() & (unsigned int) pop()); +} + +static void or(void) +{ + push((unsigned int) pop() | (unsigned int) pop()); +} + +static void eor(void) +{ + push((unsigned int) pop() ^ (unsigned int) pop()); +} + +static void not(void) +{ + push(~(unsigned int) pop()); +} + +static void set_output_base(void) +{ + base=(unsigned char)pop(); + if ((base != 10) && (base != 16)) { + fprintf(stderr, "Error: base = %d is not supported.\n", base); + base=10; + } +} + +static void print_base(double print) +{ + if (base == 16) + printf("%x\n", (unsigned int)print); + else + printf("%g\n", print); +} + +static void print_stack_no_pop(void) +{ + unsigned int i=pointer; + while (i) + print_base(stack[--i]); +} + +static void print_no_pop(void) +{ + print_base(stack[pointer-1]); +} + +struct op { + const char *name; + void (*function) (void); +}; + +static const struct op operators[] = { + {"+", add}, + {"add", add}, + {"-", sub}, + {"sub", sub}, + {"*", mul}, + {"mul", mul}, + {"/", divide}, + {"div", divide}, + {"**", power}, + {"exp", power}, + {"pow", power}, + {"%", mod}, + {"mod", mod}, + {"and", and}, + {"or", or}, + {"not", not}, + {"eor", eor}, + {"xor", eor}, + {"p", print_no_pop}, + {"f", print_stack_no_pop}, + {"o", set_output_base}, + {0, 0} +}; + +static void stack_machine(const char *argument) +{ + char *endPointer = 0; + double d; + const struct op *o = operators; + + if (argument == 0) + return; + + d = strtod(argument, &endPointer); + + if (endPointer != argument) { + push(d); + return; + } + + while (o->name != 0) { + if (strcmp(o->name, argument) == 0) { + (*(o->function)) (); + return; + } + o++; + } + bb_error_msg_and_die("%s: syntax error", argument); +} + +/* return pointer to next token in buffer and set *buffer to one char + * past the end of the above mentioned token + */ +static char *get_token(char **buffer) +{ + char *start = NULL; + char *current; + + current = skip_whitespace(*buffer); + if (*current != 0) { + start = current; + while (!isspace(*current) && *current != 0) { current++; } + *buffer = current; + } + return start; +} + +/* In Perl one might say, scalar m|\s*(\S+)\s*|g */ +static int number_of_tokens(char *buffer) +{ + int i = 0; + char *b = buffer; + while (get_token(&b)) { i++; } + return i; +} + +int dc_main(int argc, char **argv) +{ + /* take stuff from stdin if no args are given */ + if (argc <= 1) { + int i, len; + char *line = NULL; + char *cursor = NULL; + char *token = NULL; + while ((line = xmalloc_getline(stdin))) { + cursor = line; + len = number_of_tokens(line); + for (i = 0; i < len; i++) { + token = get_token(&cursor); + *cursor++ = 0; + stack_machine(token); + } + free(line); + } + } else { + if (*argv[1]=='-') + bb_show_usage(); + while (argc >= 2) { + stack_machine(argv[1]); + argv++; + argc--; + } + } + return EXIT_SUCCESS; +} diff --git a/miscutils/devfsd.c b/miscutils/devfsd.c new file mode 100644 index 000000000..f39bb7e3f --- /dev/null +++ b/miscutils/devfsd.c @@ -0,0 +1,2032 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + devfsd implementation for busybox + + Copyright (C) 2003 by Tito Ragusa + + Busybox version is based on some previous work and ideas + Copyright (C) [2003] by [Matteo Croce] <3297627799@wind.it> + + devfsd.c + + Main file for devfsd (devfs daemon for Linux). + + Copyright (C) 1998-2002 Richard Gooch + + devfsd.h + + Header file for devfsd (devfs daemon for Linux). + + Copyright (C) 1998-2000 Richard Gooch + + compat_name.c + + Compatibility name file for devfsd (build compatibility names). + + Copyright (C) 1998-2002 Richard Gooch + + expression.c + + This code provides Borne Shell-like expression expansion. + + Copyright (C) 1997-1999 Richard Gooch + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + Richard Gooch may be reached by email at rgooch@atnf.csiro.au + The postal address is: + Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia. +*/ + +#include "busybox.h" +#include "xregex.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* Various defines taken from linux/major.h */ +#define IDE0_MAJOR 3 +#define IDE1_MAJOR 22 +#define IDE2_MAJOR 33 +#define IDE3_MAJOR 34 +#define IDE4_MAJOR 56 +#define IDE5_MAJOR 57 +#define IDE6_MAJOR 88 +#define IDE7_MAJOR 89 +#define IDE8_MAJOR 90 +#define IDE9_MAJOR 91 + + +/* Various defines taken from linux/devfs_fs.h */ +#define DEVFSD_PROTOCOL_REVISION_KERNEL 5 +#define DEVFSD_IOCTL_BASE 'd' +/* These are the various ioctls */ +#define DEVFSDIOC_GET_PROTO_REV _IOR(DEVFSD_IOCTL_BASE, 0, int) +#define DEVFSDIOC_SET_EVENT_MASK _IOW(DEVFSD_IOCTL_BASE, 2, int) +#define DEVFSDIOC_RELEASE_EVENT_QUEUE _IOW(DEVFSD_IOCTL_BASE, 3, int) +#define DEVFSDIOC_SET_CONFIG_DEBUG_MASK _IOW(DEVFSD_IOCTL_BASE, 4, int) +#define DEVFSD_NOTIFY_REGISTERED 0 +#define DEVFSD_NOTIFY_UNREGISTERED 1 +#define DEVFSD_NOTIFY_ASYNC_OPEN 2 +#define DEVFSD_NOTIFY_CLOSE 3 +#define DEVFSD_NOTIFY_LOOKUP 4 +#define DEVFSD_NOTIFY_CHANGE 5 +#define DEVFSD_NOTIFY_CREATE 6 +#define DEVFSD_NOTIFY_DELETE 7 +#define DEVFS_PATHLEN 1024 +/* Never change this otherwise the binary interface will change */ + +struct devfsd_notify_struct +{ /* Use native C types to ensure same types in kernel and user space */ + unsigned int type; /* DEVFSD_NOTIFY_* value */ + unsigned int mode; /* Mode of the inode or device entry */ + unsigned int major; /* Major number of device entry */ + unsigned int minor; /* Minor number of device entry */ + unsigned int uid; /* Uid of process, inode or device entry */ + unsigned int gid; /* Gid of process, inode or device entry */ + unsigned int overrun_count; /* Number of lost events */ + unsigned int namelen; /* Number of characters not including '\0' */ + /* The device name MUST come last */ + char devname[DEVFS_PATHLEN]; /* This will be '\0' terminated */ +}; + +#define BUFFER_SIZE 16384 +#define DEVFSD_VERSION "1.3.25" +#define CONFIG_FILE "/etc/devfsd.conf" +#define MODPROBE "/sbin/modprobe" +#define MODPROBE_SWITCH_1 "-k" +#define MODPROBE_SWITCH_2 "-C" +#define CONFIG_MODULES_DEVFS "/etc/modules.devfs" +#define MAX_ARGS (6 + 1) +#define MAX_SUBEXPR 10 +#define STRING_LENGTH 255 + +/* for get_uid_gid() */ +#define UID 0 +#define GID 1 + +/* fork_and_execute() */ +# define DIE 1 +# define NO_DIE 0 + +/* for dir_operation() */ +#define RESTORE 0 +#define SERVICE 1 +#define READ_CONFIG 2 + +/* Update only after changing code to reflect new protocol */ +#define DEVFSD_PROTOCOL_REVISION_DAEMON 5 + +/* Compile-time check */ +#if DEVFSD_PROTOCOL_REVISION_KERNEL != DEVFSD_PROTOCOL_REVISION_DAEMON +#error protocol version mismatch. Update your kernel headers +#endif + +#define AC_PERMISSIONS 0 +#define AC_MODLOAD 1 +#define AC_EXECUTE 2 +#define AC_MFUNCTION 3 /* not supported by busybox */ +#define AC_CFUNCTION 4 /* not supported by busybox */ +#define AC_COPY 5 +#define AC_IGNORE 6 +#define AC_MKOLDCOMPAT 7 +#define AC_MKNEWCOMPAT 8 +#define AC_RMOLDCOMPAT 9 +#define AC_RMNEWCOMPAT 10 +#define AC_RESTORE 11 + +struct permissions_type +{ + mode_t mode; + uid_t uid; + gid_t gid; +}; + +struct execute_type +{ + char *argv[MAX_ARGS + 1]; /* argv[0] must always be the programme */ +}; + +struct copy_type +{ + const char *source; + const char *destination; +}; + +struct action_type +{ + unsigned int what; + unsigned int when; +}; + +struct config_entry_struct +{ + struct action_type action; + regex_t preg; + union + { + struct permissions_type permissions; + struct execute_type execute; + struct copy_type copy; + } + u; + struct config_entry_struct *next; +}; + +struct get_variable_info +{ + const struct devfsd_notify_struct *info; + const char *devname; + char devpath[STRING_LENGTH]; +}; + +static void dir_operation(int , const char * , int, unsigned long* ); +static void service(struct stat statbuf, char *path); +static int st_expr_expand(char *, unsigned, const char *, const char *(*) (const char *, void *), void *); +static const char *get_old_name(const char *, unsigned, char *, unsigned, unsigned); +static int mksymlink (const char *oldpath, const char *newpath); +static void read_config_file (char *path, int optional, unsigned long *event_mask); +static void process_config_line (const char *, unsigned long *); +static int do_servicing (int, unsigned long); +static void service_name (const struct devfsd_notify_struct *); +static void action_permissions (const struct devfsd_notify_struct *, const struct config_entry_struct *); +static void action_execute (const struct devfsd_notify_struct *, const struct config_entry_struct *, + const regmatch_t *, unsigned); +static void action_modload (const struct devfsd_notify_struct *info, const struct config_entry_struct *entry); +static void action_copy (const struct devfsd_notify_struct *, const struct config_entry_struct *, + const regmatch_t *, unsigned); +static void action_compat (const struct devfsd_notify_struct *, unsigned); +static void free_config (void); +static void restore(char *spath, struct stat source_stat, int rootlen); +static int copy_inode (const char *, const struct stat *, mode_t, const char *, const struct stat *); +static mode_t get_mode (const char *); +static void signal_handler (int); +static const char *get_variable (const char *, void *); +static int make_dir_tree (const char *); +static int expand_expression(char *, unsigned, const char *, const char *(*)(const char *, void *), void *, + const char *, const regmatch_t *, unsigned ); +static void expand_regexp (char *, size_t, const char *, const char *, const regmatch_t *, unsigned ); +static const char *expand_variable( char *, unsigned, unsigned *, const char *, + const char *(*) (const char *, void *), void * ); +static const char *get_variable_v2(const char *, const char *(*) (const char *, void *), void *); +static char get_old_ide_name (unsigned , unsigned); +static char *write_old_sd_name (char *, unsigned, unsigned, char *); + +/* busybox functions */ +static void msg_logger(int pri, const char * fmt, ... )__attribute__ ((format (printf, 2, 3))); +static void msg_logger_and_die(int pri, const char * fmt, ... )__attribute__ ((noreturn, format (printf, 2, 3))); +static void do_ioctl_and_die(int fd, int request, unsigned long event_mask_flag); +static void fork_and_execute(int die, char *arg0, char **arg ); +static int get_uid_gid ( int, const char *); +static void safe_memcpy( char * dest, const char * src, int len); +static unsigned int scan_dev_name_common(const char *d, unsigned int n, int addendum, char *ptr); +static unsigned int scan_dev_name(const char *d, unsigned int n, char *ptr); + +/* Structs and vars */ +static struct config_entry_struct *first_config = NULL; +static struct config_entry_struct *last_config = NULL; +static const char *mount_point = NULL; +static volatile int caught_signal = FALSE; +static volatile int caught_sighup = FALSE; +static struct initial_symlink_struct +{ + char *dest; + char *name; +} initial_symlinks[] = +{ + {"/proc/self/fd", "fd"}, + {"fd/0", "stdin"}, + {"fd/1", "stdout"}, + {"fd/2", "stderr"}, + {NULL, NULL}, +}; + +static struct event_type +{ + unsigned int type; /* The DEVFSD_NOTIFY_* value */ + const char *config_name; /* The name used in the config file */ +} event_types[] = +{ + {DEVFSD_NOTIFY_REGISTERED, "REGISTER"}, + {DEVFSD_NOTIFY_UNREGISTERED, "UNREGISTER"}, + {DEVFSD_NOTIFY_ASYNC_OPEN, "ASYNC_OPEN"}, + {DEVFSD_NOTIFY_CLOSE, "CLOSE"}, + {DEVFSD_NOTIFY_LOOKUP, "LOOKUP"}, + {DEVFSD_NOTIFY_CHANGE, "CHANGE"}, + {DEVFSD_NOTIFY_CREATE, "CREATE"}, + {DEVFSD_NOTIFY_DELETE, "DELETE"}, + {0xffffffff, NULL} +}; + +/* Busybox messages */ + +static const char * const bb_msg_proto_rev = "protocol revision"; +static const char * const bb_msg_bad_config = "bad %s config file: %s"; +static const char * const bb_msg_small_buffer = "buffer too small"; +static const char * const bb_msg_variable_not_found = "variable: %s not found"; + +/* Busybox functions */ +static void msg_logger(int pri, const char * fmt, ... ) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = access ("/dev/log", F_OK); + if (ret == 0) { + openlog(applet_name, 0, LOG_DAEMON); + vsyslog( pri , fmt, ap); + /* Man: A trailing newline is added when needed. */ + closelog(); + } + /* ENABLE_DEVFSD_VERBOSE is always enabled if msg_logger is used */ + if ((ENABLE_DEVFSD_VERBOSE && ret) || ENABLE_DEBUG) { + bb_error_msg(fmt, ap); + } + va_end(ap); +} + +static void msg_logger_and_die(int pri, const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + msg_logger(pri, fmt, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +/* Busybox stuff */ +#if defined(CONFIG_DEVFSD_VERBOSE) || defined(CONFIG_DEBUG) +#define devfsd_error_msg(fmt, args...) bb_error_msg(fmt, ## args) +#define devfsd_perror_msg_and_die(fmt, args...) bb_perror_msg_and_die(fmt, ## args) +#define devfsd_error_msg_and_die(fmt, args...) bb_error_msg_and_die(fmt, ## args) +#if defined(CONFIG_DEBUG) +#define debug_msg_logger(x, fmt, args...) msg_logger(x, fmt, ## args) +#else +#define debug_msg_logger(x, fmt, args...) +#endif +#else +#define debug_msg_logger(x, fmt, args...) +#define msg_logger(p, fmt, args...) +#define msg_logger_and_die(p, fmt, args...) exit(1) +#define devfsd_perror_msg_and_die(fmt, args...) exit(1) +#define devfsd_error_msg_and_die(fmt, args...) exit(1) +#define devfsd_error_msg(fmt, args...) +#endif + +static void do_ioctl_and_die(int fd, int request, unsigned long event_mask_flag) +{ + if (ioctl (fd, request, event_mask_flag) == -1) + msg_logger_and_die(LOG_ERR, "ioctl"); +} + +static void fork_and_execute(int die, char *arg0, char **arg ) +{ + switch ( fork () ) + { + case 0: + /* Child */ + break; + case -1: + /* Parent: Error : die or return */ + msg_logger(LOG_ERR,(char *) bb_msg_memory_exhausted); + if(die) + exit(EXIT_FAILURE); + return; + default: + /* Parent : ok : return or exit */ + if(arg0 != NULL) + { + wait (NULL); + return; + } + exit (EXIT_SUCCESS); + } + /* Child : if arg0 != NULL do execvp */ + if(arg0 != NULL ) + { + execvp (arg0, arg); + msg_logger_and_die(LOG_ERR, "execvp"); + } +} + +static void safe_memcpy( char *dest, const char *src, int len) +{ + memcpy (dest , src , len ); + dest[len] = '\0'; +} + +static unsigned int scan_dev_name_common(const char *d, unsigned int n, int addendum, char *ptr) +{ + if(d[n - 4]=='d' && d[n - 3]=='i' && d[n - 2]=='s' && d[n - 1]=='c') + return 2 + addendum; + if(d[n - 2]=='c' && d[n - 1]=='d') + return 3 + addendum; + if(ptr[0]=='p' && ptr[1]=='a' && ptr[2]=='r' && ptr[3]=='t') + return 4 + addendum; + if(ptr[n - 2]=='m' && ptr[n - 1]=='t') + return 5 + addendum; + return 0; +} + +static unsigned int scan_dev_name(const char *d, unsigned int n, char *ptr) +{ + if(d[0]=='s' && d[1]=='c' && d[2]=='s' && d[3]=='i' && d[4]=='/') { + if( d[n - 7]=='g' && d[n - 6]=='e' && d[n - 5]=='n' && + d[n - 4]=='e' && d[n - 3]=='r' && d[n - 2]=='i' && + d[n - 1]=='c' ) + return 1; + return scan_dev_name_common(d, n, 0, ptr); + } + if(d[0]=='i' && d[1]=='d' && d[2]=='e' && d[3]=='/' && + d[4]=='h' && d[5]=='o' && d[6]=='s' && d[7]=='t') + return scan_dev_name_common(d, n, 4, ptr); + if(d[0]=='s' && d[1]=='b' && d[2]=='p' && d[3]=='/') + return 10; + if(d[0]=='v' && d[1]=='c' && d[2]=='c' && d[3]=='/') + return 11; + if(d[0]=='p' && d[1]=='t' && d[2]=='y' && d[3]=='/') + return 12; + return 0; +} + +/* Public functions follow */ + +int devfsd_main (int argc, char **argv) +{ + int print_version = FALSE; + int do_daemon = TRUE; + int no_polling = FALSE; + int do_scan; + int fd, proto_rev, count; + unsigned long event_mask = 0; + struct sigaction new_action; + struct initial_symlink_struct *curr; + + if (argc < 2) + bb_show_usage(); + + for (count = 2; count < argc; ++count) + { + if(argv[count][0] == '-') + { + if(argv[count][1]=='v' && !argv[count][2]) /* -v */ + print_version = TRUE; + else if(ENABLE_DEVFSD_FG_NP && argv[count][1]=='f' + && argv[count][2]=='g' && !argv[count][3]) /* -fg */ + do_daemon = FALSE; + else if(ENABLE_DEVFSD_FG_NP && argv[count][1]=='n' + && argv[count][2]=='p' && !argv[count][3]) /* -np */ + no_polling = TRUE; + else + bb_show_usage(); + } + } + + /* strip last / from mount point, so we don't need to check for it later */ + while( argv[1][1]!='\0' && argv[1][strlen(argv[1])-1] == '/' ) + argv[1][strlen(argv[1]) -1] = '\0'; + + mount_point = argv[1]; + + if (chdir (mount_point) != 0) + devfsd_perror_msg_and_die(mount_point); + + fd = xopen (".devfsd", O_RDONLY); + + if (fcntl (fd, F_SETFD, FD_CLOEXEC) != 0) + devfsd_perror_msg_and_die("FD_CLOEXEC"); + + if (ioctl (fd, DEVFSDIOC_GET_PROTO_REV, &proto_rev) == -1) + msg_logger_and_die(LOG_ERR, "ioctl"); + + /*setup initial entries */ + for (curr = initial_symlinks; curr->dest != NULL; ++curr) + symlink (curr->dest, curr->name); + + /* NB: The check for CONFIG_FILE is done in read_config_file() */ + + if ( print_version || (DEVFSD_PROTOCOL_REVISION_DAEMON != proto_rev) ) + { + printf( "%s v%s\nDaemon %s:\t%d\nKernel-side %s:\t%d\n", + applet_name,DEVFSD_VERSION,bb_msg_proto_rev, + DEVFSD_PROTOCOL_REVISION_DAEMON,bb_msg_proto_rev, proto_rev); + if (DEVFSD_PROTOCOL_REVISION_DAEMON != proto_rev) + bb_error_msg_and_die( "%s mismatch!",bb_msg_proto_rev); + exit(EXIT_SUCCESS); /* -v */ + } + /* Tell kernel we are special (i.e. we get to see hidden entries) */ + do_ioctl_and_die(fd, DEVFSDIOC_SET_EVENT_MASK, 0); + + sigemptyset (&new_action.sa_mask); + new_action.sa_flags = 0; + + /* Set up SIGHUP and SIGUSR1 handlers */ + new_action.sa_handler = signal_handler; + if (sigaction (SIGHUP, &new_action, NULL) != 0 || sigaction (SIGUSR1, &new_action, NULL) != 0 ) + devfsd_error_msg_and_die( "sigaction"); + + printf("%s v%s started for %s\n",applet_name, DEVFSD_VERSION, mount_point); + + /* Set umask so that mknod(2), open(2) and mkdir(2) have complete control over permissions */ + umask (0); + read_config_file (CONFIG_FILE, FALSE, &event_mask); + /* Do the scan before forking, so that boot scripts see the finished product */ + dir_operation(SERVICE,mount_point,0,NULL); + + if (ENABLE_DEVFSD_FG_NP && no_polling) + exit (0); + if (do_daemon) + { + /* Release so that the child can grab it */ + do_ioctl_and_die(fd, DEVFSDIOC_RELEASE_EVENT_QUEUE, 0); + fork_and_execute(DIE, NULL, NULL); + setsid (); /* Prevent hangups and become pgrp leader */ + } else if(ENABLE_DEVFSD_FG_NP) { + setpgid (0, 0); /* Become process group leader */ + } + + while (TRUE) + { + do_scan = do_servicing (fd, event_mask); + + free_config (); + read_config_file (CONFIG_FILE, FALSE, &event_mask); + if (do_scan) + dir_operation(SERVICE,mount_point,0,NULL); + } +} /* End Function main */ + + +/* Private functions follow */ + +static void read_config_file (char *path, int optional, unsigned long *event_mask) +/* [SUMMARY] Read a configuration database. + The path to read the database from. If this is a directory, all + entries in that directory will be read (except hidden entries). + If TRUE, the routine will silently ignore a missing config file. + The event mask is written here. This is not initialised. + [RETURNS] Nothing. +*/ +{ + struct stat statbuf; + FILE *fp; + char buf[STRING_LENGTH]; + char *line=NULL; + + debug_msg_logger(LOG_INFO, "%s: %s", __FUNCTION__, path); + + if (stat (path, &statbuf) == 0 ) + { + /* Don't read 0 length files: ignored */ + /*if( statbuf.st_size == 0 ) + return;*/ + if ( S_ISDIR (statbuf.st_mode) ) + { + /* strip last / from dirname so we don't need to check for it later */ + while( path && path[1]!='\0' && path[strlen(path)-1] == '/') + path[strlen(path) -1] = '\0'; + + dir_operation(READ_CONFIG, path, 0, event_mask); + return; + } + if ( ( fp = fopen (path, "r") ) != NULL ) + { + while (fgets (buf, STRING_LENGTH, fp) != NULL) + { + /* Skip whitespace */ + for (line = buf; isspace (*line); ++line) + /*VOID*/; + if (line[0] == '\0' || line[0] == '#' ) + continue; + process_config_line (line, event_mask); + } + fclose (fp); + } else { + goto read_config_file_err; + } + } else { +read_config_file_err: + if(optional == 0 && errno == ENOENT) + msg_logger_and_die(LOG_ERR, "read config file: %s: %m", path); + } + return; +} /* End Function read_config_file */ + +static void process_config_line (const char *line, unsigned long *event_mask) +/* [SUMMARY] Process a line from a configuration file. + The configuration line. + The event mask is written here. This is not initialised. + [RETURNS] Nothing. +*/ +{ + int num_args, count; + struct config_entry_struct *new; + char p[MAX_ARGS][STRING_LENGTH]; + char when[STRING_LENGTH], what[STRING_LENGTH]; + char name[STRING_LENGTH]; + char * msg=""; + char *ptr; + int i; + + /* !!!! Only Uppercase Keywords in devsfd.conf */ + static const char *const options[] = { + "CLEAR_CONFIG", "INCLUDE", "OPTIONAL_INCLUDE", + "RESTORE", "PERMISSIONS", "MODLOAD", "EXECUTE", + "COPY", "IGNORE", "MKOLDCOMPAT", "MKNEWCOMPAT", + "RMOLDCOMPAT", "RMNEWCOMPAT", 0 + }; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + for (count = 0; count < MAX_ARGS; ++count) p[count][0] = '\0'; + num_args = sscanf (line, "%s %s %s %s %s %s %s %s %s %s", + when, name, what, + p[0], p[1], p[2], p[3], p[4], p[5], p[6]); + + i = index_in_str_array(options, when ); + + /*"CLEAR_CONFIG"*/ + if( i == 0) + { + free_config (); + *event_mask = 0; + return; + } + + if ( num_args < 2) + goto process_config_line_err; + + /* "INCLUDE" & "OPTIONAL_INCLUDE" */ + if( i == 1 || i == 2 ) + { + st_expr_expand (name, STRING_LENGTH, name, get_variable, NULL ); + msg_logger(LOG_INFO, "%sinclude: %s",(toupper (when[0]) == 'I') ? "": "optional_", name); + read_config_file (name, (toupper (when[0]) == 'I') ? FALSE : TRUE, event_mask); + return; + } + /* "RESTORE" */ + if( i == 3) + { + dir_operation(RESTORE,name, strlen (name),NULL); + return; + } + if (num_args < 3) + goto process_config_line_err; + + new = xmalloc (sizeof *new); + memset (new, 0, sizeof *new); + + for (count = 0; event_types[count].config_name != NULL; ++count) + { + if (strcasecmp (when, event_types[count].config_name) != 0) + continue; + new->action.when = event_types[count].type; + break; + } + if (event_types[count].config_name == NULL) + { + msg="WHEN in"; + goto process_config_line_err; + } + + i = index_in_str_array(options, what ); + + switch(i) + { + case 4: /* "PERMISSIONS" */ + new->action.what = AC_PERMISSIONS; + /* Get user and group */ + if ( ( ptr = strchr (p[0], '.') ) == NULL ) + { + msg="UID.GID"; + goto process_config_line_err; /*"missing '.' in UID.GID"*/ + } + + *ptr++ = '\0'; + new->u.permissions.uid = get_uid_gid (UID, p[0]); + new->u.permissions.gid = get_uid_gid (GID, ptr); + /* Get mode */ + new->u.permissions.mode = get_mode (p[1]); + break; + case 5: /* MODLOAD */ + /*This action will pass "/dev/$devname" (i.e. "/dev/" prefixed to + the device name) to the module loading facility. In addition, + the /etc/modules.devfs configuration file is used.*/ + if (ENABLE_DEVFSD_MODLOAD) + new->action.what = AC_MODLOAD; + break; + case 6: /* EXECUTE */ + new->action.what = AC_EXECUTE; + num_args -= 3; + + for (count = 0; count < num_args; ++count) + new->u.execute.argv[count] = xstrdup (p[count]); + + new->u.execute.argv[num_args] = NULL; + break; + case 7: /* COPY */ + new->action.what = AC_COPY; + num_args -= 3; + if (num_args != 2) + goto process_config_line_err; /* missing path and function in line */ + + new->u.copy.source = xstrdup (p[0]); + new->u.copy.destination = xstrdup (p[1]); + break; + case 8: /* IGNORE */ + /* FALLTROUGH */ + case 9: /* MKOLDCOMPAT */ + /* FALLTROUGH */ + case 10: /* MKNEWCOMPAT */ + /* FALLTROUGH */ + case 11:/* RMOLDCOMPAT */ + /* FALLTROUGH */ + case 12: /* RMNEWCOMPAT */ + /* AC_IGNORE 6 + AC_MKOLDCOMPAT 7 + AC_MKNEWCOMPAT 8 + AC_RMOLDCOMPAT 9 + AC_RMNEWCOMPAT 10*/ + new->action.what = i - 2; + break; + default: + msg ="WHAT in"; + goto process_config_line_err; + /*esac*/ + } /* switch (i) */ + + xregcomp( &new->preg, name, REG_EXTENDED); + + *event_mask |= 1 << new->action.when; + new->next = NULL; + if (first_config == NULL) + first_config = new; + else + last_config->next = new; + last_config = new; + return; +process_config_line_err: + msg_logger_and_die(LOG_ERR, bb_msg_bad_config, msg , line); +} /* End Function process_config_line */ + +static int do_servicing (int fd, unsigned long event_mask) +/* [SUMMARY] Service devfs changes until a signal is received. + The open control file. + The event mask. + [RETURNS] TRUE if SIGHUP was caught, else FALSE. +*/ +{ + ssize_t bytes; + struct devfsd_notify_struct info; + unsigned long tmp_event_mask; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + /* Tell devfs what events we care about */ + tmp_event_mask = event_mask; + do_ioctl_and_die(fd, DEVFSDIOC_SET_EVENT_MASK, tmp_event_mask); + while (!caught_signal) + { + errno = 0; + bytes = read (fd, (char *) &info, sizeof info); + if (caught_signal) + break; /* Must test for this first */ + if (errno == EINTR) + continue; /* Yes, the order is important */ + if (bytes < 1) + break; + service_name (&info); + } + if (caught_signal) + { + int c_sighup = caught_sighup; + + caught_signal = FALSE; + caught_sighup = FALSE; + return c_sighup; + } + msg_logger_and_die(LOG_ERR, "read error on control file"); +} /* End Function do_servicing */ + +static void service_name (const struct devfsd_notify_struct *info) +/* [SUMMARY] Service a single devfs change. + The devfs change. + [RETURNS] Nothing. +*/ +{ + unsigned int n; + regmatch_t mbuf[MAX_SUBEXPR]; + struct config_entry_struct *entry; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + if (ENABLE_DEBUG && info->overrun_count > 0) + debug_msg_logger(LOG_ERR, "lost %u events", info->overrun_count); + + /* Discard lookups on "/dev/log" and "/dev/initctl" */ + if( info->type == DEVFSD_NOTIFY_LOOKUP && + ((info->devname[0]=='l' && info->devname[1]=='o' && + info->devname[2]=='g' && !info->devname[3]) || + ( info->devname[0]=='i' && info->devname[1]=='n' && + info->devname[2]=='i' && info->devname[3]=='t' && + info->devname[4]=='c' && info->devname[5]=='t' && + info->devname[6]=='l' && !info->devname[7]))) + return; + for (entry = first_config; entry != NULL; entry = entry->next) + { + /* First check if action matches the type, then check if name matches */ + if (info->type != entry->action.when || regexec (&entry->preg, info->devname, MAX_SUBEXPR, mbuf, 0) != 0 ) + continue; + for (n = 0; (n < MAX_SUBEXPR) && (mbuf[n].rm_so != -1); ++n) + /* VOID */; + + debug_msg_logger(LOG_INFO, "%s: action.what %d", __FUNCTION__, entry->action.what); + + switch (entry->action.what) + { + case AC_PERMISSIONS: + action_permissions (info, entry); + break; + case AC_MODLOAD: + if(ENABLE_DEVFSD_MODLOAD) + action_modload (info, entry); + break; + case AC_EXECUTE: + action_execute (info, entry, mbuf, n); + break; + case AC_COPY: + action_copy (info, entry, mbuf, n); + break; + case AC_IGNORE: + return; + /*break;*/ + case AC_MKOLDCOMPAT: + case AC_MKNEWCOMPAT: + case AC_RMOLDCOMPAT: + case AC_RMNEWCOMPAT: + action_compat (info, entry->action.what); + break; + default: + msg_logger_and_die(LOG_ERR, "Unknown action"); + } + } +} /* End Function service_name */ + +static void action_permissions (const struct devfsd_notify_struct *info, + const struct config_entry_struct *entry) +/* [SUMMARY] Update permissions for a device entry. + The devfs change. + The config file entry. + [RETURNS] Nothing. +*/ +{ + struct stat statbuf; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if ( stat (info->devname, &statbuf) != 0 || + chmod (info->devname,(statbuf.st_mode & S_IFMT) | (entry->u.permissions.mode & ~S_IFMT)) != 0 || + chown (info->devname, entry->u.permissions.uid, entry->u.permissions.gid) != 0) + { + msg_logger(LOG_ERR, "Can't chmod or chown: %s: %m",info->devname); + } +} /* End Function action_permissions */ + +static void action_modload (const struct devfsd_notify_struct *info, + const struct config_entry_struct *entry ATTRIBUTE_UNUSED) +/* [SUMMARY] Load a module. + The devfs change. + The config file entry. + [RETURNS] Nothing. +*/ +{ + char *argv[6]; + char device[STRING_LENGTH]; + + argv[0] = MODPROBE; + argv[1] = MODPROBE_SWITCH_1; /* "-k" */ + argv[2] = MODPROBE_SWITCH_2; /* "-C" */ + argv[3] = CONFIG_MODULES_DEVFS; + argv[4] = device; + argv[5] = NULL; + + snprintf (device, sizeof (device), "/dev/%s", info->devname); + debug_msg_logger(LOG_INFO, "%s: %s %s %s %s %s",__FUNCTION__, argv[0],argv[1],argv[2],argv[3],argv[4]); + fork_and_execute(DIE, argv[0], argv); +} /* End Function action_modload */ + +static void action_execute (const struct devfsd_notify_struct *info, + const struct config_entry_struct *entry, + const regmatch_t *regexpr, unsigned int numexpr) +/* [SUMMARY] Execute a programme. + The devfs change. + The config file entry. + The number of subexpression (start, end) offsets within the + device name. + The number of elements within <>. + [RETURNS] Nothing. +*/ +{ + unsigned int count; + struct get_variable_info gv_info; + char *argv[MAX_ARGS + 1]; + char largv[MAX_ARGS + 1][STRING_LENGTH]; + + debug_msg_logger(LOG_INFO ,__FUNCTION__); + gv_info.info = info; + gv_info.devname = info->devname; + snprintf (gv_info.devpath, sizeof (gv_info.devpath), "%s/%s", mount_point, info->devname); + for (count = 0; entry->u.execute.argv[count] != NULL; ++count) + { + expand_expression (largv[count], STRING_LENGTH, + entry->u.execute.argv[count], + get_variable, &gv_info, + gv_info.devname, regexpr, numexpr ); + argv[count] = largv[count]; + } + argv[count] = NULL; + fork_and_execute(NO_DIE, argv[0], argv); +} /* End Function action_execute */ + + +static void action_copy (const struct devfsd_notify_struct *info, + const struct config_entry_struct *entry, + const regmatch_t *regexpr, unsigned int numexpr) +/* [SUMMARY] Copy permissions. + The devfs change. + The config file entry. + This list of subexpression (start, end) offsets within the + device name. + The number of elements in <>. + [RETURNS] Nothing. +*/ +{ + mode_t new_mode; + struct get_variable_info gv_info; + struct stat source_stat, dest_stat; + char source[STRING_LENGTH], destination[STRING_LENGTH]; + int ret = 0; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + dest_stat.st_mode = 0; + + if ( (info->type == DEVFSD_NOTIFY_CHANGE) && S_ISLNK (info->mode) ) + return; + gv_info.info = info; + gv_info.devname = info->devname; + + snprintf (gv_info.devpath, sizeof (gv_info.devpath), "%s/%s", mount_point, info->devname); + expand_expression (source, STRING_LENGTH, entry->u.copy.source, + get_variable, &gv_info, gv_info.devname, + regexpr, numexpr); + + expand_expression (destination, STRING_LENGTH, entry->u.copy.destination, + get_variable, &gv_info, gv_info.devname, + regexpr, numexpr); + + if ( !make_dir_tree (destination) || lstat (source, &source_stat) != 0) + return; + lstat (destination, &dest_stat); + new_mode = source_stat.st_mode & ~S_ISVTX; + if (info->type == DEVFSD_NOTIFY_CREATE) + new_mode |= S_ISVTX; + else if ( (info->type == DEVFSD_NOTIFY_CHANGE) && (dest_stat.st_mode & S_ISVTX) ) + new_mode |= S_ISVTX; + ret = copy_inode (destination, &dest_stat, new_mode, source, &source_stat); + if (ENABLE_DEBUG && ret && (errno != EEXIST)) + debug_msg_logger(LOG_ERR, "copy_inode: %s to %s: %m", source, destination); + return; +} /* End Function action_copy */ + +static void action_compat (const struct devfsd_notify_struct *info, unsigned int action) +/* [SUMMARY] Process a compatibility request. + The devfs change. + The action to take. + [RETURNS] Nothing. +*/ +{ + int ret; + const char *compat_name = NULL; + const char *dest_name = info->devname; + char *ptr=NULL; + char compat_buf[STRING_LENGTH], dest_buf[STRING_LENGTH]; + int mode, host, bus, target, lun; + unsigned int i; + char rewind_; + /* 1 to 5 "scsi/" , 6 to 9 "ide/host" */ + static const char *const fmt[] = { + NULL , + "sg/c%db%dt%du%d", /* scsi/generic */ + "sd/c%db%dt%du%d", /* scsi/disc */ + "sr/c%db%dt%du%d", /* scsi/cd */ + "sd/c%db%dt%du%dp%d", /* scsi/part */ + "st/c%db%dt%du%dm%d%c", /* scsi/mt */ + "ide/hd/c%db%dt%du%d", /* ide/host/disc */ + "ide/cd/c%db%dt%du%d", /* ide/host/cd */ + "ide/hd/c%db%dt%du%dp%d", /* ide/host/part */ + "ide/mt/c%db%dt%du%d%s", /* ide/host/mt */ + NULL + }; + + /* First construct compatibility name */ + switch (action) + { + case AC_MKOLDCOMPAT: + case AC_RMOLDCOMPAT: + compat_name = get_old_name (info->devname, info->namelen, compat_buf, info->major, info->minor); + break; + case AC_MKNEWCOMPAT: + case AC_RMNEWCOMPAT: + ptr = strrchr (info->devname, '/') + 1; + i=scan_dev_name(info->devname, info->namelen, ptr); + + debug_msg_logger(LOG_INFO, "%s: scan_dev_name = %d", __FUNCTION__, i); + + /* nothing found */ + if(i==0 || i > 9) + return; + + sscanf (info->devname +((i<6)?5:4), "host%d/bus%d/target%d/lun%d/", &host, &bus, &target, &lun); + snprintf (dest_buf, sizeof (dest_buf), "../%s", info->devname + ((i>5)?4:0)); + dest_name = dest_buf; + compat_name = compat_buf; + + + /* 1 == scsi/generic 2 == scsi/disc 3 == scsi/cd 6 == ide/host/disc 7 == ide/host/cd */ + if( i == 1 || i == 2 || i == 3 || i == 6 || i ==7 ) + sprintf ( compat_buf, fmt[i], host, bus, target, lun); + + /* 4 == scsi/part 8 == ide/host/part */ + if( i == 4 || i == 8) + sprintf ( compat_buf, fmt[i], host, bus, target, lun, atoi (ptr + 4) ); + + /* 5 == scsi/mt */ + if( i == 5) + { + rewind_ = info->devname[info->namelen - 1]; + if (rewind_ != 'n') + rewind_ = '\0'; + mode=0; + if(ptr[2] == 'l' /*108*/ || ptr[2] == 'm'/*109*/) + mode = ptr[2] - 107; /* 1 or 2 */ + if(ptr[2] == 'a') + mode = 3; + sprintf (compat_buf, fmt [i], host, bus, target, lun, mode, rewind_); + } + + /* 9 == ide/host/mt */ + if( i == 9 ) + snprintf (compat_buf, sizeof (compat_buf), fmt[i], host, bus, target, lun, ptr + 2); + /* esac */ + } /* switch(action) */ + + if(compat_name == NULL ) + return; + + debug_msg_logger( LOG_INFO, "%s: %s", __FUNCTION__, compat_name); + + /* Now decide what to do with it */ + switch (action) + { + case AC_MKOLDCOMPAT: + case AC_MKNEWCOMPAT: + mksymlink (dest_name, compat_name); + break; + case AC_RMOLDCOMPAT: + case AC_RMNEWCOMPAT: + ret = unlink (compat_name); + if (ENABLE_DEBUG && ret) + debug_msg_logger(LOG_ERR, "unlink: %s: %m", compat_name); + break; + /*esac*/ + } /* switch(action) */ +} /* End Function action_compat */ + +static void restore(char *spath, struct stat source_stat, int rootlen) +{ + char dpath[STRING_LENGTH]; + struct stat dest_stat; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + dest_stat.st_mode = 0; + snprintf (dpath, sizeof dpath, "%s%s", mount_point, spath + rootlen); + lstat (dpath, &dest_stat); + + if ( S_ISLNK (source_stat.st_mode) || (source_stat.st_mode & S_ISVTX) ) + copy_inode (dpath, &dest_stat, (source_stat.st_mode & ~S_ISVTX) , spath, &source_stat); + + if ( S_ISDIR (source_stat.st_mode) ) + dir_operation(RESTORE, spath, rootlen,NULL); +} + + +static int copy_inode (const char *destpath, const struct stat *dest_stat, + mode_t new_mode, + const char *sourcepath, const struct stat *source_stat) +/* [SUMMARY] Copy an inode. + The destination path. An existing inode may be deleted. + The destination stat(2) information. + The desired new mode for the destination. + The source path. + The source stat(2) information. + [RETURNS] TRUE on success, else FALSE. +*/ +{ + int source_len, dest_len; + char source_link[STRING_LENGTH], dest_link[STRING_LENGTH]; + int fd, val; + struct sockaddr_un un_addr; + char symlink_val[STRING_LENGTH]; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if ( (source_stat->st_mode & S_IFMT) == (dest_stat->st_mode & S_IFMT) ) + { + /* Same type */ + if ( S_ISLNK (source_stat->st_mode) ) + { + if (( source_len = readlink (sourcepath, source_link, STRING_LENGTH - 1) ) < 0 || + ( dest_len = readlink (destpath , dest_link , STRING_LENGTH - 1) ) < 0 ) + return FALSE; + source_link[source_len] = '\0'; + dest_link[dest_len] = '\0'; + if ( (source_len != dest_len) || (strcmp (source_link, dest_link) != 0) ) + { + unlink (destpath); + symlink (source_link, destpath); + } + return TRUE; + } /* Else not a symlink */ + chmod (destpath, new_mode & ~S_IFMT); + chown (destpath, source_stat->st_uid, source_stat->st_gid); + return TRUE; + } + /* Different types: unlink and create */ + unlink (destpath); + switch (source_stat->st_mode & S_IFMT) + { + case S_IFSOCK: + if ( ( fd = socket (AF_UNIX, SOCK_STREAM, 0) ) < 0 ) + break; + un_addr.sun_family = AF_UNIX; + snprintf (un_addr.sun_path, sizeof (un_addr.sun_path), "%s", destpath); + val = bind (fd, (struct sockaddr *) &un_addr, (int) sizeof un_addr); + close (fd); + if (val != 0 || chmod (destpath, new_mode & ~S_IFMT) != 0) + break; + goto do_chown; + case S_IFLNK: + if ( ( val = readlink (sourcepath, symlink_val, STRING_LENGTH - 1) ) < 0 ) + break; + symlink_val[val] = '\0'; + if (symlink (symlink_val, destpath) == 0) + return TRUE; + break; + case S_IFREG: + if ( ( fd = open (destpath, O_RDONLY | O_CREAT, new_mode & ~S_IFMT) ) < 0 ) + break; + close (fd); + if (chmod (destpath, new_mode & ~S_IFMT) != 0) + break; + goto do_chown; + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + if (mknod (destpath, new_mode, source_stat->st_rdev) != 0) + break; + goto do_chown; + case S_IFDIR: + if (mkdir (destpath, new_mode & ~S_IFMT) != 0) + break; +do_chown: + if (chown (destpath, source_stat->st_uid, source_stat->st_gid) == 0) + return TRUE; + /*break;*/ + } + return FALSE; +} /* End Function copy_inode */ + +static void free_config (void) +/* [SUMMARY] Free the configuration information. + [RETURNS] Nothing. +*/ +{ + struct config_entry_struct *c_entry; + void *next; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + for (c_entry = first_config; c_entry != NULL; c_entry = next) + { + unsigned int count; + + next = c_entry->next; + regfree (&c_entry->preg); + if (c_entry->action.what == AC_EXECUTE) + { + for (count = 0; count < MAX_ARGS; ++count) + { + if (c_entry->u.execute.argv[count] == NULL) + break; + free (c_entry->u.execute.argv[count]); + } + } + free (c_entry); + } + first_config = NULL; + last_config = NULL; +} /* End Function free_config */ + +static int get_uid_gid (int flag, const char *string) +/* [SUMMARY] Convert a string to a UID or GID value. + "UID" or "GID". + The string. + [RETURNS] The UID or GID value. +*/ +{ + struct passwd *pw_ent; + struct group *grp_ent; + static char *msg; + + if (ENABLE_DEVFSD_VERBOSE) + msg="user"; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if(ENABLE_DEBUG && flag != UID && flag != GID) + msg_logger_and_die(LOG_ERR,"%s: flag != UID && flag != GID", __FUNCTION__); + + if ( isdigit (string[0]) || ( (string[0] == '-') && isdigit (string[1]) ) ) + return atoi(string); + + if ( flag == UID && ( pw_ent = getpwnam (string) ) != NULL ) + return pw_ent->pw_uid; + + if ( flag == GID && ( grp_ent = getgrnam (string) ) != NULL ) + return grp_ent->gr_gid; + else if(ENABLE_DEVFSD_VERBOSE) + msg="group"; + + if(ENABLE_DEVFSD_VERBOSE) + msg_logger(LOG_ERR,"unknown %s: %s, defaulting to %cid=0", msg, string, msg[0]); + return 0; +}/* End Function get_uid_gid */ + +static mode_t get_mode (const char *string) +/* [SUMMARY] Convert a string to a mode value. + The string. + [RETURNS] The mode value. +*/ +{ + mode_t mode; + int i; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if ( isdigit (string[0]) ) + return strtoul(string, NULL, 8); + if (strlen (string) != 9) + msg_logger_and_die(LOG_ERR, "bad mode: %s", string); + + mode = 0; + i= S_IRUSR; + while(i>0) + { + if(string[0]=='r'||string[0]=='w'||string[0]=='x') + mode+=i; + i=i/2; + string++; + } + return mode; +} /* End Function get_mode */ + +static void signal_handler (int sig) +{ + debug_msg_logger(LOG_INFO, __FUNCTION__); + + caught_signal = TRUE; + if (sig == SIGHUP) + caught_sighup = TRUE; + + msg_logger(LOG_INFO, "Caught signal %d", sig); +} /* End Function signal_handler */ + +static const char *get_variable (const char *variable, void *info) +{ + struct get_variable_info *gv_info = info; + static char hostname[STRING_LENGTH], sbuf[STRING_LENGTH]; + const char *field_names[] = { "hostname", "mntpt", "devpath", "devname", + "uid", "gid", "mode", hostname, mount_point, + gv_info->devpath, gv_info->devname, 0 }; + int i; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if (gethostname (hostname, STRING_LENGTH - 1) != 0) + msg_logger_and_die(LOG_ERR, "gethostname: %m"); + + /* Here on error we should do exit(RV_SYS_ERROR), instead we do exit(EXIT_FAILURE) */ + hostname[STRING_LENGTH - 1] = '\0'; + + /* index_in_str_array returns i>=0 */ + i=index_in_str_array(field_names, variable); + + if ( i > 6 || i < 0 || (i > 1 && gv_info == NULL)) + return NULL; + if( i >= 0 && i <= 3) + { + debug_msg_logger(LOG_INFO, "%s: i=%d %s", __FUNCTION__, i ,field_names[i+7]); + return field_names[i+7]; + } + + if(i == 4 ) + sprintf (sbuf, "%u", gv_info->info->uid); + else if(i == 5) + sprintf (sbuf, "%u", gv_info->info->gid); + else if(i == 6) + sprintf (sbuf, "%o", gv_info->info->mode); + + debug_msg_logger(LOG_INFO, "%s: %s", __FUNCTION__, sbuf); + + return sbuf; +} /* End Function get_variable */ + +static void service(struct stat statbuf, char *path) +{ + struct devfsd_notify_struct info; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + memset (&info, 0, sizeof info); + info.type = DEVFSD_NOTIFY_REGISTERED; + info.mode = statbuf.st_mode; + info.major = major (statbuf.st_rdev); + info.minor = minor (statbuf.st_rdev); + info.uid = statbuf.st_uid; + info.gid = statbuf.st_gid; + snprintf (info.devname, sizeof (info.devname), "%s", path + strlen (mount_point) + 1); + info.namelen = strlen (info.devname); + service_name (&info); + if ( S_ISDIR (statbuf.st_mode) ) + dir_operation(SERVICE,path,0,NULL); +} + +static void dir_operation(int type, const char * dir_name, int var, unsigned long *event_mask) +/* [SUMMARY] Scan a directory tree and generate register events on leaf nodes. + To choose which function to perform + The directory pointer. This is closed upon completion. + The name of the directory. + string length parameter. + [RETURNS] Nothing. +*/ +{ + struct stat statbuf; + DIR *dp; + struct dirent *de; + char path[STRING_LENGTH]; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if((dp = opendir( dir_name))==NULL) + { + debug_msg_logger(LOG_ERR, "opendir: %s: %m", dir_name); + return; + } + + while ( (de = readdir (dp) ) != NULL ) + { + + if(de->d_name && *de->d_name == '.' && (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2]))) + continue; + snprintf (path, sizeof (path), "%s/%s", dir_name, de->d_name); + debug_msg_logger(LOG_ERR, "%s: %s", __FUNCTION__, path); + + if (lstat (path, &statbuf) != 0) + { + debug_msg_logger(LOG_ERR, "%s: %s: %m", __FUNCTION__, path); + continue; + } + switch(type) + { + case SERVICE: + service(statbuf,path); + break; + case RESTORE: + restore(path, statbuf, var); + break; + case READ_CONFIG: + read_config_file (path, var, event_mask); + break; + } + } + closedir (dp); +} /* End Function do_scan_and_service */ + +static int mksymlink (const char *oldpath, const char *newpath) +/* [SUMMARY] Create a symlink, creating intervening directories as required. + The string contained in the symlink. + The name of the new symlink. + [RETURNS] 0 on success, else -1. +*/ +{ + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if ( !make_dir_tree (newpath) ) + return -1; + + if (symlink (oldpath, newpath) != 0) { + if (errno != EEXIST) { + debug_msg_logger(LOG_ERR, "%s: %s to %s: %m", __FUNCTION__, oldpath, newpath); + return -1; + } + } + return 0; +} /* End Function mksymlink */ + + +static int make_dir_tree (const char *path) +/* [SUMMARY] Creating intervening directories for a path as required. + The full pathname (including the leaf node). + [RETURNS] TRUE on success, else FALSE. +*/ +{ + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if (bb_make_directory( dirname((char *)path), -1, FILEUTILS_RECUR )==-1) + { + debug_msg_logger(LOG_ERR, "%s: %s: %m",__FUNCTION__, path); + return FALSE; + } + return TRUE; +} /* End Function make_dir_tree */ + +static int expand_expression(char *output, unsigned int outsize, + const char *input, + const char *(*get_variable_func)(const char *variable, void *info), + void *info, + const char *devname, + const regmatch_t *ex, unsigned int numexp) +/* [SUMMARY] Expand environment variables and regular subexpressions in string. + The output expanded expression is written here. + The size of the output buffer. + The input expression. This may equal <>. + A function which will be used to get variable values. If + this returns NULL, the environment is searched instead. If this is NULL, + only the environment is searched. + An arbitrary pointer passed to <>. + Device name; specifically, this is the string that contains all + of the regular subexpressions. + Array of start / end offsets into info->devname for each subexpression + Number of regular subexpressions found in <>. + [RETURNS] TRUE on success, else FALSE. +*/ +{ + char temp[STRING_LENGTH]; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if ( !st_expr_expand (temp, STRING_LENGTH, input, get_variable_func, info) ) + return FALSE; + expand_regexp (output, outsize, temp, devname, ex, numexp); + return TRUE; +} /* End Function expand_expression */ + +static void expand_regexp (char *output, size_t outsize, const char *input, + const char *devname, + const regmatch_t *ex, unsigned int numex ) +/* [SUMMARY] Expand all occurrences of the regular subexpressions \0 to \9. + The output expanded expression is written here. + The size of the output buffer. + The input expression. This may NOT equal <>, because + supporting that would require yet another string-copy. However, it's not + hard to write a simple wrapper function to add this functionality for those + few cases that need it. + Device name; specifically, this is the string that contains all + of the regular subexpressions. + An array of start and end offsets into <>, one for each + subexpression + Number of subexpressions in the offset-array <>. + [RETURNS] Nothing. +*/ +{ + const char last_exp = '0' - 1 + numex; + int c = -1; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + /* Guarantee NULL termination by writing an explicit '\0' character into + the very last byte */ + if (outsize) + output[--outsize] = '\0'; + /* Copy the input string into the output buffer, replacing '\\' with '\' + and '\0' .. '\9' with subexpressions 0 .. 9, if they exist. Other \x + codes are deleted */ + while ( (c != '\0') && (outsize != 0) ) + { + c = *input; + ++input; + if (c == '\\') + { + c = *input; + ++input; + if (c != '\\') + { + if ((c >= '0') && (c <= last_exp)) + { + const regmatch_t *subexp = ex + (c - '0'); + unsigned int sublen = subexp->rm_eo - subexp->rm_so; + + /* Range checking */ + if (sublen > outsize) + sublen = outsize; + strncpy (output, devname + subexp->rm_so, sublen); + output += sublen; + outsize -= sublen; + } + continue; + } + } + *output = c; + ++output; + --outsize; + } /* while */ +} /* End Function expand_regexp */ + + +/* from compat_name.c */ + +struct translate_struct +{ + char *match; /* The string to match to (up to length) */ + char *format; /* Format of output, "%s" takes data past match string, + NULL is effectively "%s" (just more efficient) */ +}; + +static struct translate_struct translate_table[] = +{ + {"sound/", NULL}, + {"printers/", "lp%s"}, + {"v4l/", NULL}, + {"parports/", "parport%s"}, + {"fb/", "fb%s"}, + {"netlink/", NULL}, + {"loop/", "loop%s"}, + {"floppy/", "fd%s"}, + {"rd/", "ram%s"}, + {"md/", "md%s"}, /* Meta-devices */ + {"vc/", "tty%s"}, + {"misc/", NULL}, + {"isdn/", NULL}, + {"pg/", "pg%s"}, /* Parallel port generic ATAPI interface*/ + {"i2c/", "i2c-%s"}, + {"staliomem/", "staliomem%s"}, /* Stallion serial driver control */ + {"tts/E", "ttyE%s"}, /* Stallion serial driver */ + {"cua/E", "cue%s"}, /* Stallion serial driver callout */ + {"tts/R", "ttyR%s"}, /* Rocketport serial driver */ + {"cua/R", "cur%s"}, /* Rocketport serial driver callout */ + {"ip2/", "ip2%s"}, /* Computone serial driver control */ + {"tts/F", "ttyF%s"}, /* Computone serial driver */ + {"cua/F", "cuf%s"}, /* Computone serial driver callout */ + {"tts/C", "ttyC%s"}, /* Cyclades serial driver */ + {"cua/C", "cub%s"}, /* Cyclades serial driver callout */ + {"tts/", "ttyS%s"}, /* Generic serial: must be after others */ + {"cua/", "cua%s"}, /* Generic serial: must be after others */ + {"input/js", "js%s"}, /* Joystick driver */ + {NULL, NULL} +}; + +const char *get_old_name (const char *devname, unsigned int namelen, + char *buffer, unsigned int major, unsigned int minor) +/* [SUMMARY] Translate a kernel-supplied name into an old name. + The device name provided by the kernel. + The length of the name. + A buffer that may be used. This should be at least 128 bytes long. + The major number for the device. + The minor number for the device. + [RETURNS] A pointer to the old name if known, else NULL. +*/ +{ + const char *compat_name = NULL; + char *ptr; + struct translate_struct *trans; + unsigned int i; + char mode; + int indexx; + const char *pty1; + const char *pty2; + size_t len; + /* 1 to 5 "scsi/" , 6 to 9 "ide/host", 10 sbp/, 11 vcc/, 12 pty/ */ + static const char *const fmt[] = { + NULL , + "sg%u", /* scsi/generic */ + NULL, /* scsi/disc */ + "sr%u", /* scsi/cd */ + NULL, /* scsi/part */ + "nst%u%c", /* scsi/mt */ + "hd%c" , /* ide/host/disc */ + "hd%c" , /* ide/host/cd */ + "hd%c%s", /* ide/host/part */ + "%sht%d", /* ide/host/mt */ + "sbpcd%u", /* sbp/ */ + "vcs%s", /* vcc/ */ + "%cty%c%c", /* pty/ */ + NULL + }; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + for (trans = translate_table; trans->match != NULL; ++trans) + { + len = strlen (trans->match); + + if (strncmp (devname, trans->match, len) == 0) + { + if (trans->format == NULL) + return devname + len; + sprintf (buffer, trans->format, devname + len); + return buffer; + } + } + + ptr = (strrchr (devname, '/') + 1); + i = scan_dev_name(devname, namelen, ptr); + + if( i > 0 && i < 13) + compat_name = buffer; + else + return NULL; + + debug_msg_logger(LOG_INFO, "%s: scan_dev_name = %d", __FUNCTION__, i); + + /* 1 == scsi/generic, 3 == scsi/cd, 10 == sbp/ */ + if( i == 1 || i == 3 || i == 10 ) + sprintf (buffer, fmt[i], minor); + + /* 2 ==scsi/disc, 4 == scsi/part */ + if( i == 2 || i == 4) + compat_name = write_old_sd_name (buffer, major, minor,((i == 2)?"":(ptr + 4))); + + /* 5 == scsi/mt */ + if( i == 5) + { + mode = ptr[2]; + if (mode == 'n') + mode = '\0'; + sprintf (buffer, fmt[i], minor & 0x1f, mode); + if (devname[namelen - 1] != 'n') + ++compat_name; + } + /* 6 == ide/host/disc, 7 == ide/host/cd, 8 == ide/host/part */ + if( i == 6 || i == 7 || i == 8 ) + /* last arg should be ignored for i == 6 or i== 7 */ + sprintf (buffer, fmt[i] , get_old_ide_name (major, minor), ptr + 4); + + /* 9 == ide/host/mt */ + if( i == 9 ) + sprintf (buffer, fmt[i], ptr + 2, minor & 0x7f); + + /* 11 == vcc/ */ + if( i == 11 ) + { + sprintf (buffer, fmt[i], devname + 4); + if (buffer[3] == '0') + buffer[3] = '\0'; + } + /* 12 == pty/ */ + if( i == 12 ) + { + pty1 = "pqrstuvwxyzabcde"; + pty2 = "0123456789abcdef"; + indexx = atoi (devname + 5); + sprintf (buffer, fmt[i], (devname[4] == 'm') ? 'p' : 't', pty1[indexx >> 4], pty2[indexx & 0x0f]); + } + + if(ENABLE_DEBUG && compat_name!=NULL) + msg_logger(LOG_INFO, "%s: compat_name %s", __FUNCTION__, compat_name); + + return compat_name; +} /* End Function get_old_name */ + +static char get_old_ide_name (unsigned int major, unsigned int minor) +/* [SUMMARY] Get the old IDE name for a device. + The major number for the device. + The minor number for the device. + [RETURNS] The drive letter. +*/ +{ + char letter='y'; /* 121 */ + char c='a'; /* 97 */ + int i=IDE0_MAJOR; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + /* I hope it works like the previous code as it saves a few bytes. Tito ;P */ + do { + if( i==IDE0_MAJOR || i==IDE1_MAJOR || i==IDE2_MAJOR || + i==IDE3_MAJOR || i==IDE4_MAJOR || i==IDE5_MAJOR || + i==IDE6_MAJOR || i==IDE7_MAJOR || i==IDE8_MAJOR || + i==IDE9_MAJOR ) + { + if((unsigned int)i==major) + { + letter=c; + break; + } + c+=2; + } + i++; + } while(i<=IDE9_MAJOR); + + if (minor > 63) + ++letter; + return letter; +} /* End Function get_old_ide_name */ + +static char *write_old_sd_name (char *buffer, + unsigned int major, unsigned int minor, + char *part) +/* [SUMMARY] Write the old SCSI disc name to a buffer. + The buffer to write to. + The major number for the device. + The minor number for the device. + The partition string. Must be "" for a whole-disc entry. + [RETURNS] A pointer to the buffer on success, else NULL. +*/ +{ + unsigned int disc_index; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if (major == 8) + { + sprintf (buffer, "sd%c%s", 'a' + (minor >> 4), part); + return buffer; + } + if ( (major > 64) && (major < 72) ) + { + disc_index = ( (major - 64) << 4 ) + (minor >> 4); + if (disc_index < 26) + sprintf (buffer, "sd%c%s", 'a' + disc_index, part); + else + sprintf (buffer, "sd%c%c%s", 'a' + (disc_index / 26) - 1, 'a' + disc_index % 26,part); + return buffer; + } + return NULL; +} /* End Function write_old_sd_name */ + + +/* expression.c */ + +/*EXPERIMENTAL_FUNCTION*/ + +int st_expr_expand (char *output, unsigned int length, const char *input, + const char *(*get_variable_func) (const char *variable, + void *info), + void *info) +/* [SUMMARY] Expand an expression using Borne Shell-like unquoted rules. + The output expanded expression is written here. + The size of the output buffer. + The input expression. This may equal <>. + A function which will be used to get variable values. If + this returns NULL, the environment is searched instead. If this is NULL, + only the environment is searched. + An arbitrary pointer passed to <>. + [RETURNS] TRUE on success, else FALSE. +*/ +{ + char ch; + unsigned int len; + unsigned int out_pos = 0; + const char *env; + const char *ptr; + struct passwd *pwent; + char buffer[BUFFER_SIZE], tmp[STRING_LENGTH]; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if (length > BUFFER_SIZE) + length = BUFFER_SIZE; + for (; TRUE; ++input) + { + switch (ch = *input) + { + case '$': + /* Variable expansion */ + input = expand_variable (buffer, length, &out_pos, ++input, get_variable_func, info); + if (input == NULL) + return FALSE; + break; + case '~': + /* Home directory expansion */ + ch = input[1]; + if ( isspace (ch) || (ch == '/') || (ch == '\0') ) + { + /* User's own home directory: leave separator for next time */ + if ( ( env = getenv ("HOME") ) == NULL ) + { + msg_logger(LOG_INFO, bb_msg_variable_not_found, "HOME"); + return FALSE; + } + len = strlen (env); + if (len + out_pos >= length) + goto st_expr_expand_out; + memcpy (buffer + out_pos, env, len + 1); + out_pos += len; + continue; + } + /* Someone else's home directory */ + for (ptr = ++input; !isspace (ch) && (ch != '/') && (ch != '\0'); ch = *++ptr) + /* VOID */ ; + len = ptr - input; + if (len >= sizeof tmp) + goto st_expr_expand_out; + safe_memcpy (tmp, input, len); + input = ptr - 1; + if ( ( pwent = getpwnam (tmp) ) == NULL ) + { + msg_logger(LOG_INFO, "no pwent for: %s", tmp); + return FALSE; + } + len = strlen (pwent->pw_dir); + if (len + out_pos >= length) + goto st_expr_expand_out; + memcpy (buffer + out_pos, pwent->pw_dir, len + 1); + out_pos += len; + break; + case '\0': + /* Falltrough */ + default: + if (out_pos >= length) + goto st_expr_expand_out; + buffer[out_pos++] = ch; + if (ch == '\0') + { + memcpy (output, buffer, out_pos); + return TRUE; + } + break; + /* esac */ + } + } + return FALSE; +st_expr_expand_out: + msg_logger(LOG_INFO, bb_msg_small_buffer); + return FALSE; +} /* End Function st_expr_expand */ + + +/* Private functions follow */ + +static const char *expand_variable (char *buffer, unsigned int length, + unsigned int *out_pos, const char *input, + const char *(*func) (const char *variable, + void *info), + void *info) +/* [SUMMARY] Expand a variable. + The buffer to write to. + The length of the output buffer. + The current output position. This is updated. + A pointer to the input character pointer. + A function which will be used to get variable values. If this + returns NULL, the environment is searched instead. If this is NULL, only + the environment is searched. + An arbitrary pointer passed to <>. + Diagnostic messages are written here. + [RETURNS] A pointer to the end of this subexpression on success, else NULL. +*/ +{ + char ch; + int len; + unsigned int open_braces; + const char *env, *ptr; + char tmp[STRING_LENGTH]; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + ch = input[0]; + if (ch == '$') + { + /* Special case for "$$": PID */ + sprintf ( tmp, "%d", (int) getpid () ); + len = strlen (tmp); + if (len + *out_pos >= length) + goto expand_variable_out; + + memcpy (buffer + *out_pos, tmp, len + 1); + out_pos += len; + return input; + } + /* Ordinary variable expansion, possibly in braces */ + if (ch != '{') + { + /* Simple variable expansion */ + for (ptr = input; isalnum (ch) || (ch == '_') || (ch == ':');ch = *++ptr) + /* VOID */ ; + len = ptr - input; + if ((size_t)len >= sizeof tmp) + goto expand_variable_out; + + safe_memcpy (tmp, input, len); + input = ptr - 1; + if ( ( env = get_variable_v2 (tmp, func, info) ) == NULL ) + { + msg_logger(LOG_INFO, bb_msg_variable_not_found, tmp); + return NULL; + } + len = strlen (env); + if (len + *out_pos >= length) + goto expand_variable_out; + + memcpy (buffer + *out_pos, env, len + 1); + *out_pos += len; + return input; + } + /* Variable in braces: check for ':' tricks */ + ch = *++input; + for (ptr = input; isalnum (ch) || (ch == '_'); ch = *++ptr) + /* VOID */; + if (ch == '}') + { + /* Must be simple variable expansion with "${var}" */ + len = ptr - input; + if ((size_t)len >= sizeof tmp) + goto expand_variable_out; + + safe_memcpy (tmp, input, len); + ptr = expand_variable (buffer, length, out_pos, tmp, func, info ); + if (ptr == NULL) + return NULL; + return input + len; + } + if (ch != ':' || ptr[1] != '-' ) + { + msg_logger(LOG_INFO, "illegal char in var name"); + return NULL; + } + /* It's that handy "${var:-word}" expression. Check if var is defined */ + len = ptr - input; + if ((size_t)len >= sizeof tmp) + goto expand_variable_out; + + safe_memcpy (tmp, input, len); + /* Move input pointer to ':' */ + input = ptr; + /* First skip to closing brace, taking note of nested expressions */ + ptr += 2; + ch = ptr[0]; + for (open_braces = 1; open_braces > 0; ch = *++ptr) + { + switch (ch) + { + case '{': + ++open_braces; + break; + case '}': + --open_braces; + break; + case '\0': + msg_logger(LOG_INFO,"\"}\" not found in: %s", input); + return NULL; + default: + break; + } + } + --ptr; + /* At this point ptr should point to closing brace of "${var:-word}" */ + if ( ( env = get_variable_v2 (tmp, func, info) ) != NULL ) + { + /* Found environment variable, so skip the input to the closing brace + and return the variable */ + input = ptr; + len = strlen (env); + if (len + *out_pos >= length) + goto expand_variable_out; + + memcpy (buffer + *out_pos, env, len + 1); + *out_pos += len; + return input; + } + /* Environment variable was not found, so process word. Advance input + pointer to start of word in "${var:-word}" */ + input += 2; + len = ptr - input; + if ((size_t)len >= sizeof tmp) + goto expand_variable_out; + + safe_memcpy (tmp, input, len); + input = ptr; + if ( !st_expr_expand (tmp, STRING_LENGTH, tmp, func, info ) ) + return NULL; + len = strlen (tmp); + if (len + *out_pos >= length) + goto expand_variable_out; + + memcpy (buffer + *out_pos, tmp, len + 1); + *out_pos += len; + return input; +expand_variable_out: + msg_logger(LOG_INFO, bb_msg_small_buffer); + return NULL; +} /* End Function expand_variable */ + + +static const char *get_variable_v2 (const char *variable, + const char *(*func) (const char *variable, void *info), + void *info) +/* [SUMMARY] Get a variable from the environment or . + The variable name. + A function which will be used to get the variable. If this returns + NULL, the environment is searched instead. If this is NULL, only the + environment is searched. + [RETURNS] The value of the variable on success, else NULL. +*/ +{ + const char *value; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if (func != NULL) + { + value = (*func) (variable, info); + if (value != NULL) + return value; + } + return getenv(variable); +} /* End Function get_variable */ + +/* END OF CODE */ diff --git a/miscutils/eject.c b/miscutils/eject.c new file mode 100644 index 000000000..ff23b1666 --- /dev/null +++ b/miscutils/eject.c @@ -0,0 +1,60 @@ +/* vi: set sw=4 ts=4: */ +/* + * eject implementation for busybox + * + * Copyright (C) 2004 Peter Willis + * Copyright (C) 2005 Tito Ragusa + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* + * This is a simple hack of eject based on something Erik posted in #uclibc. + * Most of the dirty work blatantly ripped off from cat.c =) + */ + +#include "busybox.h" + +/* various defines swiped from linux/cdrom.h */ +#define CDROMCLOSETRAY 0x5319 /* pendant of CDROMEJECT */ +#define CDROMEJECT 0x5309 /* Ejects the cdrom media */ +#define CDROM_DRIVE_STATUS 0x5326 /* Get tray position, etc. */ +/* drive status possibilities returned by CDROM_DRIVE_STATUS ioctl */ +#define CDS_TRAY_OPEN 2 + +#define FLAG_CLOSE 1 +#define FLAG_SMART 2 + +int eject_main(int argc, char **argv) +{ + unsigned long flags; + char *device; + int dev, cmd; + + opt_complementary = "?:?1:t--T:T--t"; + flags = getopt32(argc, argv, "tT"); + device = argv[optind] ? : "/dev/cdrom"; + + // We used to do "umount " here, but it was buggy + // if something was mounted OVER cdrom and + // if cdrom is mounted many times. + // + // This works equally well (or better): + // #!/bin/sh + // umount /dev/cdrom + // eject + + dev = xopen(device, O_RDONLY|O_NONBLOCK); + cmd = CDROMEJECT; + if (flags & FLAG_CLOSE + || (flags & FLAG_SMART && ioctl(dev, CDROM_DRIVE_STATUS) == CDS_TRAY_OPEN)) + cmd = CDROMCLOSETRAY; + if (ioctl(dev, cmd)) { + bb_perror_msg_and_die("%s", device); + } + + if (ENABLE_FEATURE_CLEAN_UP) + close(dev); + + return EXIT_SUCCESS; +} diff --git a/miscutils/hdparm.c b/miscutils/hdparm.c new file mode 100644 index 000000000..90c163973 --- /dev/null +++ b/miscutils/hdparm.c @@ -0,0 +1,2184 @@ +/* vi: set sw=4 ts=4: */ +/* + * hdparm implementation for busybox + * + * Copyright (C) [2003] by [Matteo Croce] <3297627799@wind.it> + * Hacked by Tito for size optimization. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * This program is based on the source code of hdparm: see below... + * hdparm.c - Command line interface to get/set hard disk parameters + * - by Mark Lord (C) 1994-2002 -- freely distributable + */ + +#include "busybox.h" +#include + +/* device types */ +/* ------------ */ +#define NO_DEV 0xffff +#define ATA_DEV 0x0000 +#define ATAPI_DEV 0x0001 + +/* word definitions */ +/* ---------------- */ +#define GEN_CONFIG 0 /* general configuration */ +#define LCYLS 1 /* number of logical cylinders */ +#define CONFIG 2 /* specific configuration */ +#define LHEADS 3 /* number of logical heads */ +#define TRACK_BYTES 4 /* number of bytes/track (ATA-1) */ +#define SECT_BYTES 5 /* number of bytes/sector (ATA-1) */ +#define LSECTS 6 /* number of logical sectors/track */ +#define START_SERIAL 10 /* ASCII serial number */ +#define LENGTH_SERIAL 10 /* 10 words (20 bytes or characters) */ +#define BUF_TYPE 20 /* buffer type (ATA-1) */ +#define BUFFER__SIZE 21 /* buffer size (ATA-1) */ +#define RW_LONG 22 /* extra bytes in R/W LONG cmd ( < ATA-4)*/ +#define START_FW_REV 23 /* ASCII firmware revision */ +#define LENGTH_FW_REV 4 /* 4 words (8 bytes or characters) */ +#define START_MODEL 27 /* ASCII model number */ +#define LENGTH_MODEL 20 /* 20 words (40 bytes or characters) */ +#define SECTOR_XFER_MAX 47 /* r/w multiple: max sectors xfered */ +#define DWORD_IO 48 /* can do double-word IO (ATA-1 only) */ +#define CAPAB_0 49 /* capabilities */ +#define CAPAB_1 50 +#define PIO_MODE 51 /* max PIO mode supported (obsolete)*/ +#define DMA_MODE 52 /* max Singleword DMA mode supported (obs)*/ +#define WHATS_VALID 53 /* what fields are valid */ +#define LCYLS_CUR 54 /* current logical cylinders */ +#define LHEADS_CUR 55 /* current logical heads */ +#define LSECTS_CUR 56 /* current logical sectors/track */ +#define CAPACITY_LSB 57 /* current capacity in sectors */ +#define CAPACITY_MSB 58 +#define SECTOR_XFER_CUR 59 /* r/w multiple: current sectors xfered */ +#define LBA_SECTS_LSB 60 /* LBA: total number of user */ +#define LBA_SECTS_MSB 61 /* addressable sectors */ +#define SINGLE_DMA 62 /* singleword DMA modes */ +#define MULTI_DMA 63 /* multiword DMA modes */ +#define ADV_PIO_MODES 64 /* advanced PIO modes supported */ + /* multiword DMA xfer cycle time: */ +#define DMA_TIME_MIN 65 /* - minimum */ +#define DMA_TIME_NORM 66 /* - manufacturer's recommended */ + /* minimum PIO xfer cycle time: */ +#define PIO_NO_FLOW 67 /* - without flow control */ +#define PIO_FLOW 68 /* - with IORDY flow control */ +#define PKT_REL 71 /* typical #ns from PKT cmd to bus rel */ +#define SVC_NBSY 72 /* typical #ns from SERVICE cmd to !BSY */ +#define CDR_MAJOR 73 /* CD ROM: major version number */ +#define CDR_MINOR 74 /* CD ROM: minor version number */ +#define QUEUE_DEPTH 75 /* queue depth */ +#define MAJOR 80 /* major version number */ +#define MINOR 81 /* minor version number */ +#define CMDS_SUPP_0 82 /* command/feature set(s) supported */ +#define CMDS_SUPP_1 83 +#define CMDS_SUPP_2 84 +#define CMDS_EN_0 85 /* command/feature set(s) enabled */ +#define CMDS_EN_1 86 +#define CMDS_EN_2 87 +#define ULTRA_DMA 88 /* ultra DMA modes */ + /* time to complete security erase */ +#define ERASE_TIME 89 /* - ordinary */ +#define ENH_ERASE_TIME 90 /* - enhanced */ +#define ADV_PWR 91 /* current advanced power management level + in low byte, 0x40 in high byte. */ +#define PSWD_CODE 92 /* master password revision code */ +#define HWRST_RSLT 93 /* hardware reset result */ +#define ACOUSTIC 94 /* acoustic mgmt values ( >= ATA-6) */ +#define LBA_LSB 100 /* LBA: maximum. Currently only 48 */ +#define LBA_MID 101 /* bits are used, but addr 103 */ +#define LBA_48_MSB 102 /* has been reserved for LBA in */ +#define LBA_64_MSB 103 /* the future. */ +#define RM_STAT 127 /* removable media status notification feature set support */ +#define SECU_STATUS 128 /* security status */ +#define CFA_PWR_MODE 160 /* CFA power mode 1 */ +#define START_MEDIA 176 /* media serial number */ +#define LENGTH_MEDIA 20 /* 20 words (40 bytes or characters)*/ +#define START_MANUF 196 /* media manufacturer I.D. */ +#define LENGTH_MANUF 10 /* 10 words (20 bytes or characters) */ +#define INTEGRITY 255 /* integrity word */ + +/* bit definitions within the words */ +/* -------------------------------- */ + +/* many words are considered valid if bit 15 is 0 and bit 14 is 1 */ +#define VALID 0xc000 +#define VALID_VAL 0x4000 +/* many words are considered invalid if they are either all-0 or all-1 */ +#define NOVAL_0 0x0000 +#define NOVAL_1 0xffff + +/* word 0: gen_config */ +#define NOT_ATA 0x8000 +#define NOT_ATAPI 0x4000 /* (check only if bit 15 == 1) */ +#define MEDIA_REMOVABLE 0x0080 +#define DRIVE_NOT_REMOVABLE 0x0040 /* bit obsoleted in ATA 6 */ +#define INCOMPLETE 0x0004 +#define CFA_SUPPORT_VAL 0x848a /* 848a=CFA feature set support */ +#define DRQ_RESPONSE_TIME 0x0060 +#define DRQ_3MS_VAL 0x0000 +#define DRQ_INTR_VAL 0x0020 +#define DRQ_50US_VAL 0x0040 +#define PKT_SIZE_SUPPORTED 0x0003 +#define PKT_SIZE_12_VAL 0x0000 +#define PKT_SIZE_16_VAL 0x0001 +#define EQPT_TYPE 0x1f00 +#define SHIFT_EQPT 8 + +#define CDROM 0x0005 + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static const char * const pkt_str[] = { + "Direct-access device", /* word 0, bits 12-8 = 00 */ + "Sequential-access device", /* word 0, bits 12-8 = 01 */ + "Printer", /* word 0, bits 12-8 = 02 */ + "Processor", /* word 0, bits 12-8 = 03 */ + "Write-once device", /* word 0, bits 12-8 = 04 */ + "CD-ROM", /* word 0, bits 12-8 = 05 */ + "Scanner", /* word 0, bits 12-8 = 06 */ + "Optical memory", /* word 0, bits 12-8 = 07 */ + "Medium changer", /* word 0, bits 12-8 = 08 */ + "Communications device", /* word 0, bits 12-8 = 09 */ + "ACS-IT8 device", /* word 0, bits 12-8 = 0a */ + "ACS-IT8 device", /* word 0, bits 12-8 = 0b */ + "Array controller", /* word 0, bits 12-8 = 0c */ + "Enclosure services", /* word 0, bits 12-8 = 0d */ + "Reduced block command device", /* word 0, bits 12-8 = 0e */ + "Optical card reader/writer", /* word 0, bits 12-8 = 0f */ + "", /* word 0, bits 12-8 = 10 */ + "", /* word 0, bits 12-8 = 11 */ + "", /* word 0, bits 12-8 = 12 */ + "", /* word 0, bits 12-8 = 13 */ + "", /* word 0, bits 12-8 = 14 */ + "", /* word 0, bits 12-8 = 15 */ + "", /* word 0, bits 12-8 = 16 */ + "", /* word 0, bits 12-8 = 17 */ + "", /* word 0, bits 12-8 = 18 */ + "", /* word 0, bits 12-8 = 19 */ + "", /* word 0, bits 12-8 = 1a */ + "", /* word 0, bits 12-8 = 1b */ + "", /* word 0, bits 12-8 = 1c */ + "", /* word 0, bits 12-8 = 1d */ + "", /* word 0, bits 12-8 = 1e */ + "Unknown", /* word 0, bits 12-8 = 1f */ +}; + +static const char * const ata1_cfg_str[] = { /* word 0 in ATA-1 mode */ + "Reserved", /* bit 0 */ + "hard sectored", /* bit 1 */ + "soft sectored", /* bit 2 */ + "not MFM encoded ", /* bit 3 */ + "head switch time > 15us", /* bit 4 */ + "spindle motor control option", /* bit 5 */ + "fixed drive", /* bit 6 */ + "removable drive", /* bit 7 */ + "disk xfer rate <= 5Mbs", /* bit 8 */ + "disk xfer rate > 5Mbs, <= 10Mbs", /* bit 9 */ + "disk xfer rate > 5Mbs", /* bit 10 */ + "rotational speed tol.", /* bit 11 */ + "data strobe offset option", /* bit 12 */ + "track offset option", /* bit 13 */ + "format speed tolerance gap reqd", /* bit 14 */ + "ATAPI" /* bit 14 */ +}; +#endif + +/* word 1: number of logical cylinders */ +#define LCYLS_MAX 0x3fff /* maximum allowable value */ + +/* word 2: specific configuration + * (a) require SET FEATURES to spin-up + * (b) require spin-up to fully reply to IDENTIFY DEVICE + */ +#define STBY_NID_VAL 0x37c8 /* (a) and (b) */ +#define STBY_ID_VAL 0x738c /* (a) and not (b) */ +#define PWRD_NID_VAL 0x8c73 /* not (a) and (b) */ +#define PWRD_ID_VAL 0xc837 /* not (a) and not (b) */ + +/* words 47 & 59: sector_xfer_max & sector_xfer_cur */ +#define SECTOR_XFER 0x00ff /* sectors xfered on r/w multiple cmds*/ +#define MULTIPLE_SETTING_VALID 0x0100 /* 1=multiple sector setting is valid */ + +/* word 49: capabilities 0 */ +#define STD_STBY 0x2000 /* 1=standard values supported (ATA); + 0=vendor specific values */ +#define IORDY_SUP 0x0800 /* 1=support; 0=may be supported */ +#define IORDY_OFF 0x0400 /* 1=may be disabled */ +#define LBA_SUP 0x0200 /* 1=Logical Block Address support */ +#define DMA_SUP 0x0100 /* 1=Direct Memory Access support */ +#define DMA_IL_SUP 0x8000 /* 1=interleaved DMA support (ATAPI) */ +#define CMD_Q_SUP 0x4000 /* 1=command queuing support (ATAPI) */ +#define OVLP_SUP 0x2000 /* 1=overlap operation support (ATAPI) */ +#define SWRST_REQ 0x1000 /* 1=ATA SW reset required (ATAPI, obsolete */ + +/* word 50: capabilities 1 */ +#define MIN_STANDBY_TIMER 0x0001 /* 1=device specific standby timer value minimum */ + +/* words 51 & 52: PIO & DMA cycle times */ +#define MODE 0xff00 /* the mode is in the MSBs */ + +/* word 53: whats_valid */ +#define OK_W88 0x0004 /* the ultra_dma info is valid */ +#define OK_W64_70 0x0002 /* see above for word descriptions */ +#define OK_W54_58 0x0001 /* current cyl, head, sector, cap. info valid */ + +/*word 63,88: dma_mode, ultra_dma_mode*/ +#define MODE_MAX 7 /* bit definitions force udma <=7 (when + * udma >=8 comes out it'll have to be + * defined in a new dma_mode word!) */ + +/* word 64: PIO transfer modes */ +#define PIO_SUP 0x00ff /* only bits 0 & 1 are used so far, */ +#define PIO_MODE_MAX 8 /* but all 8 bits are defined */ + +/* word 75: queue_depth */ +#define DEPTH_BITS 0x001f /* bits used for queue depth */ + +/* words 80-81: version numbers */ +/* NOVAL_0 or NOVAL_1 means device does not report version */ + +/* word 81: minor version number */ +#define MINOR_MAX 0x22 +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static const char *minor_str[MINOR_MAX+2] = { /* word 81 value: */ + "Unspecified", /* 0x0000 */ + "ATA-1 X3T9.2 781D prior to rev.4", /* 0x0001 */ + "ATA-1 published, ANSI X3.221-1994", /* 0x0002 */ + "ATA-1 X3T9.2 781D rev.4", /* 0x0003 */ + "ATA-2 published, ANSI X3.279-1996", /* 0x0004 */ + "ATA-2 X3T10 948D prior to rev.2k", /* 0x0005 */ + "ATA-3 X3T10 2008D rev.1", /* 0x0006 */ + "ATA-2 X3T10 948D rev.2k", /* 0x0007 */ + "ATA-3 X3T10 2008D rev.0", /* 0x0008 */ + "ATA-2 X3T10 948D rev.3", /* 0x0009 */ + "ATA-3 published, ANSI X3.298-199x", /* 0x000a */ + "ATA-3 X3T10 2008D rev.6", /* 0x000b */ + "ATA-3 X3T13 2008D rev.7 and 7a", /* 0x000c */ + "ATA/ATAPI-4 X3T13 1153D rev.6", /* 0x000d */ + "ATA/ATAPI-4 T13 1153D rev.13", /* 0x000e */ + "ATA/ATAPI-4 X3T13 1153D rev.7", /* 0x000f */ + "ATA/ATAPI-4 T13 1153D rev.18", /* 0x0010 */ + "ATA/ATAPI-4 T13 1153D rev.15", /* 0x0011 */ + "ATA/ATAPI-4 published, ANSI INCITS 317-1998", /* 0x0012 */ + "ATA/ATAPI-5 T13 1321D rev.3", + "ATA/ATAPI-4 T13 1153D rev.14", /* 0x0014 */ + "ATA/ATAPI-5 T13 1321D rev.1", /* 0x0015 */ + "ATA/ATAPI-5 published, ANSI INCITS 340-2000", /* 0x0016 */ + "ATA/ATAPI-4 T13 1153D rev.17", /* 0x0017 */ + "ATA/ATAPI-6 T13 1410D rev.0", /* 0x0018 */ + "ATA/ATAPI-6 T13 1410D rev.3a", /* 0x0019 */ + "ATA/ATAPI-7 T13 1532D rev.1", /* 0x001a */ + "ATA/ATAPI-6 T13 1410D rev.2", /* 0x001b */ + "ATA/ATAPI-6 T13 1410D rev.1", /* 0x001c */ + "ATA/ATAPI-7 published, ANSI INCITS 397-2005", /* 0x001d */ + "ATA/ATAPI-7 T13 1532D rev.0", /* 0x001e */ + "Reserved" /* 0x001f */ + "Reserved" /* 0x0020 */ + "ATA/ATAPI-7 T13 1532D rev.4a", /* 0x0021 */ + "ATA/ATAPI-6 published, ANSI INCITS 361-2002", /* 0x0022 */ + "Reserved" /* 0x0023-0xfffe*/ +}; +#endif +static const char actual_ver[MINOR_MAX+2] = { + /* word 81 value: */ + 0, /* 0x0000 WARNING: */ + 1, /* 0x0001 WARNING: */ + 1, /* 0x0002 WARNING: */ + 1, /* 0x0003 WARNING: */ + 2, /* 0x0004 WARNING: This array */ + 2, /* 0x0005 WARNING: corresponds */ + 3, /* 0x0006 WARNING: *exactly* */ + 2, /* 0x0007 WARNING: to the ATA/ */ + 3, /* 0x0008 WARNING: ATAPI version */ + 2, /* 0x0009 WARNING: listed in */ + 3, /* 0x000a WARNING: the */ + 3, /* 0x000b WARNING: minor_str */ + 3, /* 0x000c WARNING: array */ + 4, /* 0x000d WARNING: above. */ + 4, /* 0x000e WARNING: */ + 4, /* 0x000f WARNING: if you change */ + 4, /* 0x0010 WARNING: that one, */ + 4, /* 0x0011 WARNING: change this one */ + 4, /* 0x0012 WARNING: too!!! */ + 5, /* 0x0013 WARNING: */ + 4, /* 0x0014 WARNING: */ + 5, /* 0x0015 WARNING: */ + 5, /* 0x0016 WARNING: */ + 4, /* 0x0017 WARNING: */ + 6, /* 0x0018 WARNING: */ + 6, /* 0x0019 WARNING: */ + 7, /* 0x001a WARNING: */ + 6, /* 0x001b WARNING: */ + 6, /* 0x001c WARNING: */ + 7, /* 0x001d WARNING: */ + 7, /* 0x001e WARNING: */ + 0, /* 0x001f WARNING: */ + 0, /* 0x0020 WARNING: */ + 7, /* 0x0021 WARNING: */ + 6, /* 0x0022 WARNING: */ + 0 /* 0x0023-0xfffe */ +}; + +/* words 82-84: cmds/feats supported */ +#define CMDS_W82 0x77ff /* word 82: defined command locations*/ +#define CMDS_W83 0x3fff /* word 83: defined command locations*/ +#define CMDS_W84 0x002f /* word 83: defined command locations*/ +#define SUPPORT_48_BIT 0x0400 +#define NUM_CMD_FEAT_STR 48 + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static const char * const cmd_feat_str[] = { + "", /* word 82 bit 15: obsolete */ + "NOP cmd", /* word 82 bit 14 */ + "READ BUFFER cmd", /* word 82 bit 13 */ + "WRITE BUFFER cmd", /* word 82 bit 12 */ + "", /* word 82 bit 11: obsolete */ + "Host Protected Area feature set", /* word 82 bit 10 */ + "DEVICE RESET cmd", /* word 82 bit 9 */ + "SERVICE interrupt", /* word 82 bit 8 */ + "Release interrupt", /* word 82 bit 7 */ + "Look-ahead", /* word 82 bit 6 */ + "Write cache", /* word 82 bit 5 */ + "PACKET command feature set", /* word 82 bit 4 */ + "Power Management feature set", /* word 82 bit 3 */ + "Removable Media feature set", /* word 82 bit 2 */ + "Security Mode feature set", /* word 82 bit 1 */ + "SMART feature set", /* word 82 bit 0 */ + /* --------------*/ + "", /* word 83 bit 15: !valid bit */ + "", /* word 83 bit 14: valid bit */ + "FLUSH CACHE EXT cmd", /* word 83 bit 13 */ + "Mandatory FLUSH CACHE cmd ", /* word 83 bit 12 */ + "Device Configuration Overlay feature set ", + "48-bit Address feature set ", /* word 83 bit 10 */ + "", + "SET MAX security extension", /* word 83 bit 8 */ + "Address Offset Reserved Area Boot", /* word 83 bit 7 */ + "SET FEATURES subcommand required to spinup after power up", + "Power-Up In Standby feature set", /* word 83 bit 5 */ + "Removable Media Status Notification feature set", + "Adv. Power Management feature set",/* word 83 bit 3 */ + "CFA feature set", /* word 83 bit 2 */ + "READ/WRITE DMA QUEUED", /* word 83 bit 1 */ + "DOWNLOAD MICROCODE cmd", /* word 83 bit 0 */ + /* --------------*/ + "", /* word 84 bit 15: !valid bit */ + "", /* word 84 bit 14: valid bit */ + "", /* word 84 bit 13: reserved */ + "", /* word 84 bit 12: reserved */ + "", /* word 84 bit 11: reserved */ + "", /* word 84 bit 10: reserved */ + "", /* word 84 bit 9: reserved */ + "", /* word 84 bit 8: reserved */ + "", /* word 84 bit 7: reserved */ + "", /* word 84 bit 6: reserved */ + "General Purpose Logging feature set", /* word 84 bit 5 */ + "", /* word 84 bit 4: reserved */ + "Media Card Pass Through Command feature set ", + "Media serial number ", /* word 84 bit 2 */ + "SMART self-test ", /* word 84 bit 1 */ + "SMART error logging " /* word 84 bit 0 */ +}; + +static void identify(uint16_t *id_supplied) ATTRIBUTE_NORETURN; +static void identify_from_stdin(void) ATTRIBUTE_NORETURN; +#else +void identify_from_stdin(void); +#endif + + +/* words 85-87: cmds/feats enabled */ +/* use cmd_feat_str[] to display what commands and features have + * been enabled with words 85-87 + */ + +/* words 89, 90, SECU ERASE TIME */ +#define ERASE_BITS 0x00ff + +/* word 92: master password revision */ +/* NOVAL_0 or NOVAL_1 means no support for master password revision */ + +/* word 93: hw reset result */ +#define CBLID 0x2000 /* CBLID status */ +#define RST0 0x0001 /* 1=reset to device #0 */ +#define DEV_DET 0x0006 /* how device num determined */ +#define JUMPER_VAL 0x0002 /* device num determined by jumper */ +#define CSEL_VAL 0x0004 /* device num determined by CSEL_VAL */ + +/* word 127: removable media status notification feature set support */ +#define RM_STAT_BITS 0x0003 +#define RM_STAT_SUP 0x0001 + +/* word 128: security */ +#define SECU_ENABLED 0x0002 +#define SECU_LEVEL 0x0010 +#define NUM_SECU_STR 6 +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static const char * const secu_str[] = { + "supported", /* word 128, bit 0 */ + "enabled", /* word 128, bit 1 */ + "locked", /* word 128, bit 2 */ + "frozen", /* word 128, bit 3 */ + "expired: security count", /* word 128, bit 4 */ + "supported: enhanced erase" /* word 128, bit 5 */ +}; +#endif + +/* word 160: CFA power mode */ +#define VALID_W160 0x8000 /* 1=word valid */ +#define PWR_MODE_REQ 0x2000 /* 1=CFA power mode req'd by some cmds*/ +#define PWR_MODE_OFF 0x1000 /* 1=CFA power moded disabled */ +#define MAX_AMPS 0x0fff /* value = max current in ma */ + +/* word 255: integrity */ +#define SIG 0x00ff /* signature location */ +#define SIG_VAL 0x00A5 /* signature value */ + +#define TIMING_MB 64 +#define TIMING_BUF_MB 1 +#define TIMING_BUF_BYTES (TIMING_BUF_MB * 1024 * 1024) +#define TIMING_BUF_COUNT (timing_MB / TIMING_BUF_MB) +#define BUFCACHE_FACTOR 2 + +#undef DO_FLUSHCACHE /* under construction: force cache flush on -W0 */ + +/* Busybox messages and functions */ +static int bb_ioctl(int fd, int request, void *argp, const char *string) +{ + int e = ioctl(fd, request, argp); + if (e && string) + bb_perror_msg(" %s", string); + return e; +} + +static int bb_ioctl_alt(int fd, int cmd, unsigned char *args, int alt, const char *string) +{ + if (!ioctl(fd, cmd, args)) + return 0; + args[0] = alt; + return bb_ioctl(fd, cmd, args, string); +} + +static void on_off(unsigned int value); + +static void print_flag_on_off(unsigned long get_arg, const char *s, unsigned long arg) +{ + + if (get_arg) + { + printf(" setting %s to %ld", s, arg); + on_off(arg); + } +} + +static void bb_ioctl_on_off(int fd, int request, void *argp, const char *string, + const char * str) +{ + if (ioctl(fd, request, &argp) != 0) + bb_perror_msg(" %s", string); + else + { + printf(" %s\t= %2ld", str, (unsigned long) argp); + on_off((unsigned long) argp); + } +} + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static void print_ascii(uint16_t *p, uint8_t length); + +static void xprint_ascii(uint16_t *val ,int i, char * string, int n) +{ + if (val[i]) + { + printf("\t%-20s",string); + print_ascii(&val[i], n); + } +} +#endif +/* end of busybox specific stuff */ + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static uint8_t mode_loop(uint16_t mode_sup, uint16_t mode_sel, int cc, uint8_t *have_mode) +{ + uint16_t ii; + uint8_t err_dma = 0; + + for (ii = 0; ii <= MODE_MAX; ii++) + { + if (mode_sel & 0x0001) + { + printf("*%cdma%u ",cc,ii); + if (*have_mode) + err_dma = 1; + *have_mode = 1; + } + else if (mode_sup & 0x0001) + printf("%cdma%u ",cc,ii); + + mode_sup >>=1; + mode_sel >>=1; + } + return err_dma; +} + +static void print_ascii(uint16_t *p, uint8_t length) { + uint8_t ii; + char cl; + + /* find first non-space & print it */ + for (ii = 0; ii< length; ii++) + { + if (((char) 0x00ff&((*p)>>8)) != ' ') + break; + if ((cl = (char) 0x00ff&(*p)) != ' ') + { + if (cl != '\0') printf("%c",cl); + p++; + ii++; + break; + } + p++; + } + /* print the rest */ + for (; ii< length; ii++) + { + if (!(*p)) + break; /* some older devices have NULLs */ + printf("%c%c",(char)0x00ff&((*p)>>8),(char)(*p)&0x00ff); + p++; + } + puts(""); +} + +// Parse 512 byte disk identification block and print much crap. + +static void identify(uint16_t *id_supplied) +{ + uint16_t buf[256]; + uint16_t *val, ii, jj, kk; + uint16_t like_std = 1, std = 0, min_std = 0xffff; + uint16_t dev = NO_DEV, eqpt = NO_DEV; + uint8_t have_mode = 0, err_dma = 0; + uint8_t chksum = 0; + uint32_t ll, mm, nn, oo; + uint64_t bbbig; /* (:) */ + const char *strng; + + // Adjust for endianness if necessary. + + if (BB_BIG_ENDIAN) { + swab(id_supplied, buf, sizeof(buf)); + val = buf; + } else val = id_supplied; + + chksum &= 0xff; + + /* check if we recognise the device type */ + puts(""); + if (!(val[GEN_CONFIG] & NOT_ATA)) + { + dev = ATA_DEV; + printf("ATA device, with "); + } + else if (val[GEN_CONFIG]==CFA_SUPPORT_VAL) + { + dev = ATA_DEV; + like_std = 4; + printf("CompactFlash ATA device, with "); + } + else if (!(val[GEN_CONFIG] & NOT_ATAPI)) + { + dev = ATAPI_DEV; + eqpt = (val[GEN_CONFIG] & EQPT_TYPE) >> SHIFT_EQPT; + printf("ATAPI %s, with ", pkt_str[eqpt]); + like_std = 3; + } + else + /*"Unknown device type:\n\tbits 15&14 of general configuration word 0 both set to 1.\n"*/ + bb_error_msg_and_die("unknown device type"); + + printf("%sremovable media\n", !(val[GEN_CONFIG] & MEDIA_REMOVABLE) ? "non-" : ""); + /* Info from the specific configuration word says whether or not the + * ID command completed correctly. It is only defined, however in + * ATA/ATAPI-5 & 6; it is reserved (value theoretically 0) in prior + * standards. Since the values allowed for this word are extremely + * specific, it should be safe to check it now, even though we don't + * know yet what standard this device is using. + */ + if ((val[CONFIG]==STBY_NID_VAL) || (val[CONFIG]==STBY_ID_VAL) || + (val[CONFIG]==PWRD_NID_VAL) || (val[CONFIG]==PWRD_ID_VAL) ) + { + like_std = 5; + if ((val[CONFIG]==STBY_NID_VAL) || (val[CONFIG]==STBY_ID_VAL)) + printf("powers-up in standby; SET FEATURES subcmd spins-up.\n"); + if (((val[CONFIG]==STBY_NID_VAL) || (val[CONFIG]==PWRD_NID_VAL)) && (val[GEN_CONFIG] & INCOMPLETE)) + printf("\n\tWARNING: ID response incomplete.\n\tFollowing data may be incorrect.\n\n"); + } + + /* output the model and serial numbers and the fw revision */ + xprint_ascii(val, START_MODEL, "Model Number:", LENGTH_MODEL); + xprint_ascii(val, START_SERIAL, "Serial Number:", LENGTH_SERIAL); + xprint_ascii(val, START_FW_REV, "Firmware Revision:", LENGTH_FW_REV); + xprint_ascii(val, START_MEDIA, "Media Serial Num:", LENGTH_MEDIA); + xprint_ascii(val, START_MANUF, "Media Manufacturer:", LENGTH_MANUF); + + /* major & minor standards version number (Note: these words were not + * defined until ATA-3 & the CDROM std uses different words.) */ + printf("Standards:"); + if (eqpt != CDROM) + { + if (val[MINOR] && (val[MINOR] <= MINOR_MAX)) + { + if (like_std < 3) like_std = 3; + std = actual_ver[val[MINOR]]; + if (std) printf("\n\tUsed: %s ",minor_str[val[MINOR]]); + + } + /* looks like when they up-issue the std, they obsolete one; + * thus, only the newest 4 issues need be supported. (That's + * what "kk" and "min_std" are all about.) */ + if (val[MAJOR] && (val[MAJOR] !=NOVAL_1)) + { + printf("\n\tSupported: "); + jj = val[MAJOR] << 1; + kk = like_std >4 ? like_std-4: 0; + for (ii = 14; (ii >0)&&(ii>kk); ii--) + { + if (jj & 0x8000) + { + printf("%u ", ii); + if (like_std < ii) + { + like_std = ii; + kk = like_std >4 ? like_std-4: 0; + } + if (min_std > ii) min_std = ii; + } + jj <<= 1; + } + if (like_std < 3) like_std = 3; + } + /* Figure out what standard the device is using if it hasn't told + * us. If we know the std, check if the device is using any of + * the words from the next level up. It happens. + */ + if (like_std < std) like_std = std; + + if (((std == 5) || (!std && (like_std < 6))) && + ((((val[CMDS_SUPP_1] & VALID) == VALID_VAL) && + (( val[CMDS_SUPP_1] & CMDS_W83) > 0x00ff)) || + ((( val[CMDS_SUPP_2] & VALID) == VALID_VAL) && + ( val[CMDS_SUPP_2] & CMDS_W84) ) ) ) + { + like_std = 6; + } + else if (((std == 4) || (!std && (like_std < 5))) && + ((((val[INTEGRITY] & SIG) == SIG_VAL) && !chksum) || + (( val[HWRST_RSLT] & VALID) == VALID_VAL) || + ((( val[CMDS_SUPP_1] & VALID) == VALID_VAL) && + (( val[CMDS_SUPP_1] & CMDS_W83) > 0x001f)) ) ) + { + like_std = 5; + } + else if (((std == 3) || (!std && (like_std < 4))) && + ((((val[CMDS_SUPP_1] & VALID) == VALID_VAL) && + ((( val[CMDS_SUPP_1] & CMDS_W83) > 0x0000) || + (( val[CMDS_SUPP_0] & CMDS_W82) > 0x000f))) || + (( val[CAPAB_1] & VALID) == VALID_VAL) || + (( val[WHATS_VALID] & OK_W88) && val[ULTRA_DMA]) || + (( val[RM_STAT] & RM_STAT_BITS) == RM_STAT_SUP) ) ) + { + like_std = 4; + } + else if (((std == 2) || (!std && (like_std < 3))) && + ((val[CMDS_SUPP_1] & VALID) == VALID_VAL) ) + { + like_std = 3; + } + else if (((std == 1) || (!std && (like_std < 2))) && + ((val[CAPAB_0] & (IORDY_SUP | IORDY_OFF)) || + (val[WHATS_VALID] & OK_W64_70)) ) + { + like_std = 2; + } + if (!std) + printf("\n\tLikely used: %u\n",like_std); + else if (like_std > std) + printf("& some of %u\n",like_std); + else + puts(""); + } + else + { + /* TBD: do CDROM stuff more thoroughly. For now... */ + kk = 0; + if (val[CDR_MINOR] == 9) + { + kk = 1; + printf("\n\tUsed: ATAPI for CD-ROMs, SFF-8020i, r2.5"); + } + if (val[CDR_MAJOR] && (val[CDR_MAJOR] !=NOVAL_1)) + { + kk = 1; + printf("\n\tSupported: CD-ROM ATAPI"); + jj = val[CDR_MAJOR] >> 1; + for (ii = 1; ii <15; ii++) + { + if (jj & 0x0001) printf("-%u ", ii); + jj >>= 1; + } + } + printf("%s\n", (!kk) ? "\n\tLikely used CD-ROM ATAPI-1" : "" ); + /* the cdrom stuff is more like ATA-2 than anything else, so: */ + like_std = 2; + } + + if (min_std == 0xffff) + min_std = like_std > 4 ? like_std - 3 : 1; + + printf("Configuration:\n"); + /* more info from the general configuration word */ + if ((eqpt != CDROM) && (like_std == 1)) + { + jj = val[GEN_CONFIG] >> 1; + for (ii = 1; ii < 15; ii++) + { + if (jj & 0x0001) printf("\t%s\n",ata1_cfg_str[ii]); + jj >>=1; + } + } + if (dev == ATAPI_DEV) + { + if ((val[GEN_CONFIG] & DRQ_RESPONSE_TIME) == DRQ_3MS_VAL) + strng = "3ms"; + else if ((val[GEN_CONFIG] & DRQ_RESPONSE_TIME) == DRQ_INTR_VAL) + strng = "<=10ms with INTRQ"; + else if ((val[GEN_CONFIG] & DRQ_RESPONSE_TIME) == DRQ_50US_VAL) + strng ="50us"; + else + strng = "Unknown"; + printf("\tDRQ response: %s\n\tPacket size: ", strng); /* Data Request (DRQ) */ + + if ((val[GEN_CONFIG] & PKT_SIZE_SUPPORTED) == PKT_SIZE_12_VAL) + strng = "12 bytes"; + else if ((val[GEN_CONFIG] & PKT_SIZE_SUPPORTED) == PKT_SIZE_16_VAL) + strng = "16 bytes"; + else + strng = "Unknown"; + puts(strng); + } + else + { + /* addressing...CHS? See section 6.2 of ATA specs 4 or 5 */ + ll = (uint32_t)val[LBA_SECTS_MSB] << 16 | val[LBA_SECTS_LSB]; + mm = 0; bbbig = 0; + if ( (ll > 0x00FBFC10) && (!val[LCYLS])) + printf("\tCHS addressing not supported\n"); + else + { + jj = val[WHATS_VALID] & OK_W54_58; + printf("\tLogical\t\tmax\tcurrent\n\tcylinders\t%u\t%u\n\theads\t\t%u\t%u\n\tsectors/track\t%u\t%u\n\t--\n", + val[LCYLS],jj?val[LCYLS_CUR]:0, val[LHEADS],jj?val[LHEADS_CUR]:0, val[LSECTS],jj?val[LSECTS_CUR]:0); + + if ((min_std == 1) && (val[TRACK_BYTES] || val[SECT_BYTES])) + printf("\tbytes/track: %u\tbytes/sector: %u\n",val[TRACK_BYTES], val[SECT_BYTES]); + + if (jj) + { + mm = (uint32_t)val[CAPACITY_MSB] << 16 | val[CAPACITY_LSB]; + if (like_std < 3) + { + /* check Endian of capacity bytes */ + nn = val[LCYLS_CUR] * val[LHEADS_CUR] * val[LSECTS_CUR]; + oo = (uint32_t)val[CAPACITY_LSB] << 16 | val[CAPACITY_MSB]; + if (abs(mm - nn) > abs(oo - nn)) + mm = oo; + } + printf("\tCHS current addressable sectors:%11u\n",mm); + } + } + /* LBA addressing */ + printf("\tLBA user addressable sectors:%11u\n",ll); + if ( ((val[CMDS_SUPP_1] & VALID) == VALID_VAL) && + (val[CMDS_SUPP_1] & SUPPORT_48_BIT) ) + { + bbbig = (uint64_t)val[LBA_64_MSB] << 48 | + (uint64_t)val[LBA_48_MSB] << 32 | + (uint64_t)val[LBA_MID] << 16 | + val[LBA_LSB] ; + printf("\tLBA48 user addressable sectors:%11"PRIu64"\n",bbbig); + } + + if (!bbbig) + bbbig = (uint64_t)(ll>mm ? ll : mm); /* # 512 byte blocks */ + printf("\tdevice size with M = 1024*1024: %11"PRIu64" MBytes\n",bbbig>>11); + bbbig = (bbbig<<9)/1000000; + printf("\tdevice size with M = 1000*1000: %11"PRIu64" MBytes ",bbbig); + + if (bbbig > 1000) + printf("(%"PRIu64" GB)\n", bbbig/1000); + else + puts(""); + } + + /* hw support of commands (capabilities) */ + printf("Capabilities:\n\t"); + + if (dev == ATAPI_DEV) + { + if (eqpt != CDROM && (val[CAPAB_0] & CMD_Q_SUP)) printf("Cmd queuing, "); + if (val[CAPAB_0] & OVLP_SUP) printf("Cmd overlap, "); + } + if (val[CAPAB_0] & LBA_SUP) printf("LBA, "); + + if (like_std != 1) + { + printf("IORDY%s(can%s be disabled)\n", + !(val[CAPAB_0] & IORDY_SUP) ? "(may be)" : "", + (val[CAPAB_0] & IORDY_OFF) ? "" :"not"); + } + else + printf("no IORDY\n"); + + if ((like_std == 1) && val[BUF_TYPE]) + { + printf("\tBuffer type: %04x: %s%s\n", val[BUF_TYPE], + (val[BUF_TYPE] < 2) ? "single port, single-sector" : "dual port, multi-sector", + (val[BUF_TYPE] > 2) ? " with read caching ability" : ""); + } + + if ((min_std == 1) && (val[BUFFER__SIZE] && (val[BUFFER__SIZE] != NOVAL_1))) + { + printf("\tBuffer size: %.1fkB\n",(float)val[BUFFER__SIZE]/2); + } + if ((min_std < 4) && (val[RW_LONG])) + { + printf("\tbytes avail on r/w long: %u\n",val[RW_LONG]); + } + if ((eqpt != CDROM) && (like_std > 3)) + { + printf("\tQueue depth: %u\n",(val[QUEUE_DEPTH] & DEPTH_BITS)+1); + } + + if (dev == ATA_DEV) + { + if (like_std == 1) + printf("\tCan%s perform double-word IO\n",(!val[DWORD_IO]) ?"not":""); + else + { + printf("\tStandby timer values: spec'd by %s", (val[CAPAB_0] & STD_STBY) ? "Standard" : "Vendor"); + if ((like_std > 3) && ((val[CAPAB_1] & VALID) == VALID_VAL)) + printf(", %s device specific minimum\n",(val[CAPAB_1] & MIN_STANDBY_TIMER)?"with":"no"); + else + puts(""); + } + printf("\tR/W multiple sector transfer: "); + if ((like_std < 3) && !(val[SECTOR_XFER_MAX] & SECTOR_XFER)) + printf("not supported\n"); + else + { + printf("Max = %u\tCurrent = ",val[SECTOR_XFER_MAX] & SECTOR_XFER); + if (val[SECTOR_XFER_CUR] & MULTIPLE_SETTING_VALID) + printf("%u\n", val[SECTOR_XFER_CUR] & SECTOR_XFER); + else + printf("?\n"); + } + if ((like_std > 3) && (val[CMDS_SUPP_1] & 0x0008)) + { + /* We print out elsewhere whether the APM feature is enabled or + not. If it's not enabled, let's not repeat the info; just print + nothing here. */ + printf("\tAdvancedPM level: "); + if ( (val[ADV_PWR] & 0xFF00) == 0x4000 ) + { + uint8_t apm_level = val[ADV_PWR] & 0x00FF; + printf("%u (0x%x)\n", apm_level, apm_level); + } + else + printf("unknown setting (0x%04x)\n", val[ADV_PWR]); + } + if (like_std > 5 && val[ACOUSTIC]) { + printf("\tRecommended acoustic management value: %u, current value: %u\n", + (val[ACOUSTIC] >> 8) & 0x00ff, val[ACOUSTIC] & 0x00ff); + } + } + else + { + /* ATAPI */ + if (eqpt != CDROM && (val[CAPAB_0] & SWRST_REQ)) + printf("\tATA sw reset required\n"); + + if (val[PKT_REL] || val[SVC_NBSY]) + { + printf("\tOverlap support:"); + if (val[PKT_REL]) printf(" %uus to release bus.",val[PKT_REL]); + if (val[SVC_NBSY]) printf(" %uus to clear BSY after SERVICE cmd.",val[SVC_NBSY]); + puts(""); + } + } + + /* DMA stuff. Check that only one DMA mode is selected. */ + printf("\tDMA: "); + if (!(val[CAPAB_0] & DMA_SUP)) + printf("not supported\n"); + else + { + if (val[DMA_MODE] && !val[SINGLE_DMA] && !val[MULTI_DMA]) + printf(" sdma%u\n",(val[DMA_MODE] & MODE) >> 8); + if (val[SINGLE_DMA]) + { + jj = val[SINGLE_DMA]; + kk = val[SINGLE_DMA] >> 8; + err_dma += mode_loop(jj,kk,'s',&have_mode); + } + if (val[MULTI_DMA]) + { + jj = val[MULTI_DMA]; + kk = val[MULTI_DMA] >> 8; + err_dma += mode_loop(jj,kk,'m',&have_mode); + } + if ((val[WHATS_VALID] & OK_W88) && val[ULTRA_DMA]) + { + jj = val[ULTRA_DMA]; + kk = val[ULTRA_DMA] >> 8; + err_dma += mode_loop(jj,kk,'u',&have_mode); + } + if (err_dma || !have_mode) printf("(?)"); + puts(""); + + if ((dev == ATAPI_DEV) && (eqpt != CDROM) && (val[CAPAB_0] & DMA_IL_SUP)) + printf("\t\tInterleaved DMA support\n"); + + if ((val[WHATS_VALID] & OK_W64_70) && + (val[DMA_TIME_MIN] || val[DMA_TIME_NORM])) + { + printf("\t\tCycle time:"); + if (val[DMA_TIME_MIN]) printf(" min=%uns",val[DMA_TIME_MIN]); + if (val[DMA_TIME_NORM]) printf(" recommended=%uns",val[DMA_TIME_NORM]); + puts(""); + } + } + + /* Programmed IO stuff */ + printf("\tPIO: "); + /* If a drive supports mode n (e.g. 3), it also supports all modes less + * than n (e.g. 3, 2, 1 and 0). Print all the modes. */ + if ((val[WHATS_VALID] & OK_W64_70) && (val[ADV_PIO_MODES] & PIO_SUP)) + { + jj = ((val[ADV_PIO_MODES] & PIO_SUP) << 3) | 0x0007; + for (ii = 0; ii <= PIO_MODE_MAX ; ii++) + { + if (jj & 0x0001) printf("pio%d ",ii); + jj >>=1; + } + puts(""); + } + else if (((min_std < 5) || (eqpt == CDROM)) && (val[PIO_MODE] & MODE) ) + { + for (ii = 0; ii <= val[PIO_MODE]>>8; ii++) + printf("pio%d ",ii); + puts(""); + } + else + printf("unknown\n"); + + if (val[WHATS_VALID] & OK_W64_70) + { + if (val[PIO_NO_FLOW] || val[PIO_FLOW]) + { + printf("\t\tCycle time:"); + if (val[PIO_NO_FLOW]) printf(" no flow control=%uns", val[PIO_NO_FLOW]); + if (val[PIO_FLOW]) printf(" IORDY flow control=%uns", val[PIO_FLOW]); + puts(""); + } + } + + if ((val[CMDS_SUPP_1] & VALID) == VALID_VAL) + { + printf("Commands/features:\n\tEnabled\tSupported:\n"); + jj = val[CMDS_SUPP_0]; + kk = val[CMDS_EN_0]; + for (ii = 0; ii < NUM_CMD_FEAT_STR; ii++) + { + if ((jj & 0x8000) && (*cmd_feat_str[ii] != '\0')) + { + printf("\t%s\t%s\n", (kk & 0x8000) ? " *" : "", cmd_feat_str[ii]); + } + jj <<=1; kk<<=1; + if (ii%16 == 15) + { + jj = val[CMDS_SUPP_0+1+(ii/16)]; + kk = val[CMDS_EN_0+1+(ii/16)]; + } + if (ii == 31) + { + if ((val[CMDS_SUPP_2] & VALID) != VALID_VAL) + ii +=16; + } + } + } + /* Removable Media Status Notification feature set */ + if ((val[RM_STAT] & RM_STAT_BITS) == RM_STAT_SUP) + printf("\t%s supported\n", cmd_feat_str[27]); + + + /* security */ + if ((eqpt != CDROM) && (like_std > 3) && + (val[SECU_STATUS] || val[ERASE_TIME] || val[ENH_ERASE_TIME])) + { + printf("Security:\n"); + if (val[PSWD_CODE] && (val[PSWD_CODE] != NOVAL_1)) + printf("\tMaster password revision code = %u\n",val[PSWD_CODE]); + jj = val[SECU_STATUS]; + if (jj) + { + for (ii = 0; ii < NUM_SECU_STR; ii++) + { + printf("\t%s\t%s\n", (!(jj & 0x0001)) ? "not" : "", secu_str[ii]); + jj >>=1; + } + if (val[SECU_STATUS] & SECU_ENABLED) + { + printf("\tSecurity level %s\n", (val[SECU_STATUS] & SECU_LEVEL) ? "maximum" : "high"); + } + } + jj = val[ERASE_TIME] & ERASE_BITS; + kk = val[ENH_ERASE_TIME] & ERASE_BITS; + if (jj || kk) + { + printf("\t"); + if (jj) printf("%umin for %sSECURITY ERASE UNIT. ", jj==ERASE_BITS ? 508 : jj<<1, ""); + if (kk) printf("%umin for %sSECURITY ERASE UNIT. ", kk==ERASE_BITS ? 508 : kk<<1, "ENHANCED "); + puts(""); + } + } + + /* reset result */ + jj = val[HWRST_RSLT]; + if ((jj & VALID) == VALID_VAL) + { + if (!(oo = (jj & RST0))) + jj >>= 8; + if ((jj & DEV_DET) == JUMPER_VAL) + strng = " determined by the jumper"; + else if ((jj & DEV_DET) == CSEL_VAL) + strng = " determined by CSEL"; + else + strng = ""; + printf("HW reset results:\n\tCBLID- %s Vih\n\tDevice num = %i%s\n", + (val[HWRST_RSLT] & CBLID) ? "above" : "below", !(oo), strng); + } + + /* more stuff from std 5 */ + if ((like_std > 4) && (eqpt != CDROM)) + { + if (val[CFA_PWR_MODE] & VALID_W160) + { + printf("CFA power mode 1:\n\t%s%s\n", (val[CFA_PWR_MODE] & PWR_MODE_OFF) ? "disabled" : "enabled", + (val[CFA_PWR_MODE] & PWR_MODE_REQ) ? " and required by some commands" : ""); + + if (val[CFA_PWR_MODE] & MAX_AMPS) printf("\tMaximum current = %uma\n",val[CFA_PWR_MODE] & MAX_AMPS); + } + if ((val[INTEGRITY] & SIG) == SIG_VAL) + { + printf("Checksum: %scorrect\n", chksum ? "in" : ""); + } + } + + exit(EXIT_SUCCESS); +} +#endif + +static int get_identity, get_geom; +static int do_flush; +static int do_ctimings, do_timings; +static unsigned long set_readahead, get_readahead, Xreadahead; +static unsigned long set_readonly, get_readonly, readonly; +static unsigned long set_unmask, get_unmask, unmask; +static unsigned long set_mult, get_mult, mult; +#ifdef CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA +static unsigned long set_dma, get_dma, dma; +#endif +static unsigned long set_dma_q, get_dma_q, dma_q; +static unsigned long set_nowerr, get_nowerr, nowerr; +static unsigned long set_keep, get_keep, keep; +static unsigned long set_io32bit, get_io32bit, io32bit; +static unsigned long set_piomode, noisy_piomode; +static int piomode; +#ifdef HDIO_DRIVE_CMD +static unsigned long set_dkeep, get_dkeep, dkeep; +static unsigned long set_standby, get_standby, standby_requested; +static unsigned long set_xfermode, get_xfermode; +static int xfermode_requested; +static unsigned long set_lookahead, get_lookahead, lookahead; +static unsigned long set_prefetch, get_prefetch, prefetch; +static unsigned long set_defects, get_defects, defects; +static unsigned long set_wcache, get_wcache, wcache; +static unsigned long set_doorlock, get_doorlock, doorlock; +static unsigned long set_seagate, get_seagate; +static unsigned long set_standbynow, get_standbynow; +static unsigned long set_sleepnow, get_sleepnow; +static unsigned long get_powermode; +static unsigned long set_apmmode, get_apmmode, apmmode; +#endif +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static int get_IDentity; +#endif +#ifdef CONFIG_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF +static unsigned long unregister_hwif; +static unsigned long hwif; +#endif +#ifdef CONFIG_FEATURE_HDPARM_HDIO_SCAN_HWIF +static unsigned long scan_hwif; +static unsigned long hwif_data; +static unsigned long hwif_ctrl; +static unsigned long hwif_irq; +#endif +#ifdef CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF +static unsigned long set_busstate, get_busstate, busstate; +#endif +static int reread_partn; + +#ifdef CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET +static int perform_reset; +#endif /* CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET */ +#ifdef CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF +static unsigned long perform_tristate, tristate; +#endif /* CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF */ + +// Historically, if there was no HDIO_OBSOLETE_IDENTITY, then +// then the HDIO_GET_IDENTITY only returned 142 bytes. +// Otherwise, HDIO_OBSOLETE_IDENTITY returns 142 bytes, +// and HDIO_GET_IDENTITY returns 512 bytes. But the latest +// 2.5.xx kernels no longer define HDIO_OBSOLETE_IDENTITY +// (which they should, but they should just return -EINVAL). +// +// So.. we must now assume that HDIO_GET_IDENTITY returns 512 bytes. +// On a really old system, it will not, and we will be confused. +// Too bad, really. + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static const char * const cfg_str[] = +{ "", "HardSect", "SoftSect", "NotMFM", + "HdSw>15uSec", "SpinMotCtl", "Fixed", "Removeable", + "DTR<=5Mbs", "DTR>5Mbs", "DTR>10Mbs", "RotSpdTol>.5%", + "dStbOff", "TrkOff", "FmtGapReq", "nonMagnetic" +}; + +static const char * const BuffType[] = {"Unknown", "1Sect", "DualPort", "DualPortCache"}; + +static void dump_identity(const struct hd_driveid *id) +{ + int i; + const unsigned short int *id_regs= (const void*) id; + + printf("\n Model=%.40s, FwRev=%.8s, SerialNo=%.20s\n Config={", + id->model, id->fw_rev, id->serial_no); + for (i=0; i<=15; i++) { + if (id->config & (1<cyls, id->heads, id->sectors, id->track_bytes, + id->sector_bytes, id->ecc_bytes, + id->buf_type, BuffType[(id->buf_type > 3) ? 0 : id->buf_type], + id->buf_size/2, id->max_multsect); + if (id->max_multsect) + { + printf(", MultSect="); + if (!(id->multsect_valid&1)) + printf("?%u?", id->multsect); + else if (id->multsect) + printf("%u", id->multsect); + else + printf("off"); + } + puts(""); + + if (!(id->field_valid&1)) + printf(" (maybe):"); + + printf(" CurCHS=%u/%u/%u, CurSects=%lu, LBA=%s",id->cur_cyls, id->cur_heads, + id->cur_sectors, + (BB_BIG_ENDIAN) ? + (long unsigned int)(id->cur_capacity0 << 16) | id->cur_capacity1 : + (long unsigned int)(id->cur_capacity1 << 16) | id->cur_capacity0, + ((id->capability&2) == 0) ? "no" : "yes"); + + if (id->capability&2) + printf(", LBAsects=%u", id->lba_capacity); + + printf("\n IORDY=%s", (id->capability&8) ? (id->capability&4) ? "on/off" : "yes" : "no"); + + if (((id->capability&8) || (id->field_valid&2)) && id->field_valid&2) + printf(", tPIO={min:%u,w/IORDY:%u}", id->eide_pio, id->eide_pio_iordy); + + if ((id->capability&1) && (id->field_valid&2)) + printf(", tDMA={min:%u,rec:%u}", id->eide_dma_min, id->eide_dma_time); + + printf("\n PIO modes: "); + if (id->tPIO <= 5) + { + printf("pio0 "); + if (id->tPIO >= 1) printf("pio1 "); + if (id->tPIO >= 2) printf("pio2 "); + } + if (id->field_valid&2) + { + if (id->eide_pio_modes & 1) printf("pio3 "); + if (id->eide_pio_modes & 2) printf("pio4 "); + if (id->eide_pio_modes &~3) printf("pio? "); + } + if (id->capability&1) + { + if (id->dma_1word | id->dma_mword) + { + printf("\n DMA modes: "); + if (id->dma_1word & 0x100) printf("*"); + if (id->dma_1word & 1) printf("sdma0 "); + if (id->dma_1word & 0x200) printf("*"); + if (id->dma_1word & 2) printf("sdma1 "); + if (id->dma_1word & 0x400) printf("*"); + if (id->dma_1word & 4) printf("sdma2 "); + if (id->dma_1word & 0xf800) printf("*"); + if (id->dma_1word & 0xf8) printf("sdma? "); + if (id->dma_mword & 0x100) printf("*"); + if (id->dma_mword & 1) printf("mdma0 "); + if (id->dma_mword & 0x200) printf("*"); + if (id->dma_mword & 2) printf("mdma1 "); + if (id->dma_mword & 0x400) printf("*"); + if (id->dma_mword & 4) printf("mdma2 "); + if (id->dma_mword & 0xf800) printf("*"); + if (id->dma_mword & 0xf8) printf("mdma? "); + } + } + if (((id->capability&8) || (id->field_valid&2)) && id->field_valid&4) + { + printf("\n UDMA modes: "); + if (id->dma_ultra & 0x100) printf("*"); + if (id->dma_ultra & 0x001) printf("udma0 "); + if (id->dma_ultra & 0x200) printf("*"); + if (id->dma_ultra & 0x002) printf("udma1 "); + if (id->dma_ultra & 0x400) printf("*"); + if (id->dma_ultra & 0x004) printf("udma2 "); +#ifdef __NEW_HD_DRIVE_ID + if (id->hw_config & 0x2000) + { +#else /* !__NEW_HD_DRIVE_ID */ + if (id->word93 & 0x2000) + { +#endif /* __NEW_HD_DRIVE_ID */ + if (id->dma_ultra & 0x0800) printf("*"); + if (id->dma_ultra & 0x0008) printf("udma3 "); + if (id->dma_ultra & 0x1000) printf("*"); + if (id->dma_ultra & 0x0010) printf("udma4 "); + if (id->dma_ultra & 0x2000) printf("*"); + if (id->dma_ultra & 0x0020) printf("udma5 "); + if (id->dma_ultra & 0x4000) printf("*"); + if (id->dma_ultra & 0x0040) printf("udma6 "); + if (id->dma_ultra & 0x8000) printf("*"); + if (id->dma_ultra & 0x0080) printf("udma7 "); + } + } + printf("\n AdvancedPM=%s",((id_regs[83]&8)==0)?"no":"yes"); + if (id_regs[83] & 8) + { + if (!(id_regs[86]&8)) + printf(": disabled (255)"); + else if ((id_regs[91]&0xFF00)!=0x4000) + printf(": unknown setting"); + else + printf(": mode=0x%02X (%u)",id_regs[91]&0xFF,id_regs[91]&0xFF); + } + if (id_regs[82]&0x20) + printf(" WriteCache=%s",(id_regs[85]&0x20) ? "enabled" : "disabled"); +#ifdef __NEW_HD_DRIVE_ID + if ((id->minor_rev_num && id->minor_rev_num <= 31) || (id->major_rev_num && id->minor_rev_num <= 31)) + { + printf("\n Drive conforms to: %s: ", (id->minor_rev_num <= 31) ? minor_str[id->minor_rev_num] : "Unknown"); + if (id->major_rev_num != 0x0000 && /* NOVAL_0 */ + id->major_rev_num != 0xFFFF) { /* NOVAL_1 */ + for (i=0; i <= 15; i++) { + if (id->major_rev_num & (1<= e) /* more than 1MB/s */ + printf("%2d MB in %5.2f seconds =%6.2f %cB/sec\n", t, e, t / e, 'M'); + else + printf("%2d MB in %5.2f seconds =%6.2f %cB/sec\n", t, e, t / e * 1024, 'k'); +} + +static int do_blkgetsize (int fd, unsigned long long *blksize64) +{ + int rc; + unsigned int blksize32 = 0; + + if (0 == ioctl(fd, BLKGETSIZE64, blksize64)) { // returns bytes + *blksize64 /= 512; + return 0; + } + rc = ioctl(fd, BLKGETSIZE, &blksize32); // returns sectors + if (rc) + bb_perror_msg("BLKGETSIZE"); + *blksize64 = blksize32; + return rc; +} + +static void do_time(int flag, int fd) +/* + flag = 0 time_cache + flag = 1 time_device +*/ +{ + struct itimerval e1, e2; + double elapsed, elapsed2; + unsigned int max_iterations = 1024, total_MB, iterations; + unsigned long long blksize; + RESERVE_CONFIG_BUFFER(buf, TIMING_BUF_BYTES); + + if (mlock(buf, TIMING_BUF_BYTES)) { + bb_perror_msg("mlock"); + goto quit2; + } + + if (0 == do_blkgetsize(fd, &blksize)) { + max_iterations = blksize / (2 * 1024) / TIMING_BUF_MB; + } + + /* Clear out the device request queues & give them time to complete */ + sync(); + sleep(3); + + setitimer(ITIMER_REAL, &(struct itimerval){{1000,0},{1000,0}}, NULL); + + if (flag == 0) /* Time cache */ + { + if (seek_to_zero (fd)) return; + if (read_big_block (fd, buf)) return; + printf(" Timing cached reads: "); + fflush(stdout); + + /* Now do the timing */ + iterations = 0; + getitimer(ITIMER_REAL, &e1); + do { + ++iterations; + if (seek_to_zero (fd) || read_big_block (fd, buf)) + goto quit; + getitimer(ITIMER_REAL, &e2); + elapsed = (e1.it_value.tv_sec - e2.it_value.tv_sec) + + ((e1.it_value.tv_usec - e2.it_value.tv_usec) / 1000000.0); + } while (elapsed < 2.0); + total_MB = iterations * TIMING_BUF_MB; + + /* Now remove the lseek() and getitimer() overheads from the elapsed time */ + getitimer(ITIMER_REAL, &e1); + do { + if (seek_to_zero (fd)) + goto quit; + getitimer(ITIMER_REAL, &e2); + elapsed2 = (e1.it_value.tv_sec - e2.it_value.tv_sec) + + ((e1.it_value.tv_usec - e2.it_value.tv_usec) / 1000000.0); + } while (--iterations); + + elapsed -= elapsed2; + print_timing(BUFCACHE_FACTOR * total_MB, elapsed); + flush_buffer_cache(fd); + sleep(1); + } + else /* Time device */ + { + printf(" Timing buffered disk reads: "); + fflush(stdout); + /* + * getitimer() is used rather than gettimeofday() because + * it is much more consistent (on my machine, at least). + */ + /* Now do the timings for real */ + iterations = 0; + getitimer(ITIMER_REAL, &e1); + do { + ++iterations; + if (read_big_block (fd, buf)) + goto quit; + getitimer(ITIMER_REAL, &e2); + elapsed = (e1.it_value.tv_sec - e2.it_value.tv_sec) + + ((e1.it_value.tv_usec - e2.it_value.tv_usec) / 1000000.0); + } while (elapsed < 3.0 && iterations < max_iterations); + + total_MB = iterations * TIMING_BUF_MB; + print_timing(total_MB, elapsed); + } +quit: + munlock(buf, TIMING_BUF_BYTES); +quit2: + RELEASE_CONFIG_BUFFER(buf); +} + +static void on_off (unsigned int value) +{ + printf(value ? " (on)\n" : " (off)\n"); +} + +#ifdef CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF +static void bus_state_value(unsigned int value) +{ + if (value == BUSSTATE_ON) + on_off(1); + else if (value == BUSSTATE_OFF) + on_off(0); + else if (value == BUSSTATE_TRISTATE) + printf(" (tristate)\n"); + else + printf(" (unknown: %d)\n", value); +} +#endif + +#ifdef HDIO_DRIVE_CMD +static void interpret_standby(unsigned int standby) +{ + unsigned int t; + + printf(" ("); + if (standby == 0) + printf("off"); + else if (standby == 252) + printf("21 minutes"); + else if (standby == 253) + printf("vendor-specific"); + else if (standby == 254) + printf("Reserved"); + else if (standby == 255) + printf("21 minutes + 15 seconds"); + else { + if (standby <= 240) { + t = standby * 5; + printf("%u minutes + %u seconds", t / 60, t % 60); + } else if (standby <= 251) { + t = (standby - 240) * 30; + printf("%u hours + %u minutes", t / 60, t % 60); + } else + printf("illegal value"); + } + printf(")\n"); +} + +struct xfermode_entry { + int val; + const char *name; +}; + +static const struct xfermode_entry xfermode_table[] = { + { 8, "pio0" }, + { 9, "pio1" }, + { 10, "pio2" }, + { 11, "pio3" }, + { 12, "pio4" }, + { 13, "pio5" }, + { 14, "pio6" }, + { 15, "pio7" }, + { 16, "sdma0" }, + { 17, "sdma1" }, + { 18, "sdma2" }, + { 19, "sdma3" }, + { 20, "sdma4" }, + { 21, "sdma5" }, + { 22, "sdma6" }, + { 23, "sdma7" }, + { 32, "mdma0" }, + { 33, "mdma1" }, + { 34, "mdma2" }, + { 35, "mdma3" }, + { 36, "mdma4" }, + { 37, "mdma5" }, + { 38, "mdma6" }, + { 39, "mdma7" }, + { 64, "udma0" }, + { 65, "udma1" }, + { 66, "udma2" }, + { 67, "udma3" }, + { 68, "udma4" }, + { 69, "udma5" }, + { 70, "udma6" }, + { 71, "udma7" }, + { 0, NULL } +}; + +static int translate_xfermode(char * name) +{ + const struct xfermode_entry *tmp; + char *endptr; + int val = -1; + + + for (tmp = xfermode_table; tmp->name != NULL; ++tmp) + { + if (!strcmp(name, tmp->name)) + return tmp->val; + } + + val = strtol(name, &endptr, 10); + if (*endptr == '\0') + return val; + + return -1; +} + +static void interpret_xfermode(unsigned int xfermode) +{ + printf(" ("); + if (xfermode == 0) + printf("default PIO mode"); + else if (xfermode == 1) + printf("default PIO mode, disable IORDY"); + else if (xfermode >= 8 && xfermode <= 15) + printf("PIO flow control mode%u", xfermode-8); + else if (xfermode >= 16 && xfermode <= 23) + printf("singleword DMA mode%u", xfermode-16); + else if (xfermode >= 32 && xfermode <= 39) + printf("multiword DMA mode%u", xfermode-32); + else if (xfermode >= 64 && xfermode <= 71) + printf("UltraDMA mode%u", xfermode-64); + else + printf("Unknown"); + printf(")\n"); +} +#endif /* HDIO_DRIVE_CMD */ + +static void print_flag(unsigned long flag, char *s, unsigned long value) +{ + if (flag) + printf(" setting %s to %ld\n", s, value); +} + +static void process_dev(char *devname) +{ + int fd; + static long parm, multcount; +#ifndef HDIO_DRIVE_CMD + int force_operation = 0; +#endif + /* Please restore args[n] to these values after each ioctl + except for args[2] */ + unsigned char args[4] = {WIN_SETFEATURES,0,0,0}; + const char *fmt = " %s\t= %2ld"; + + fd = xopen(devname, O_RDONLY|O_NONBLOCK); + printf("\n%s:\n", devname); + + if (set_readahead) + { + print_flag(get_readahead,"fs readahead", Xreadahead); + bb_ioctl(fd, BLKRASET,(int *)Xreadahead,"BLKRASET"); + } +#ifdef CONFIG_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF + if (unregister_hwif) + { + printf(" attempting to unregister hwif#%lu\n", hwif); + bb_ioctl(fd, HDIO_UNREGISTER_HWIF,(int *)(unsigned long)hwif,"HDIO_UNREGISTER_HWIF"); + } +#endif +#ifdef CONFIG_FEATURE_HDPARM_HDIO_SCAN_HWIF + if (scan_hwif) + { + printf(" attempting to scan hwif (0x%lx, 0x%lx, %lu)\n", hwif_data, hwif_ctrl, hwif_irq); + args[0] = hwif_data; + args[1] = hwif_ctrl; + args[2] = hwif_irq; + bb_ioctl(fd, HDIO_SCAN_HWIF, args, "HDIO_SCAN_HWIF"); + args[0] = WIN_SETFEATURES; + args[1] = 0; + } +#endif + if (set_piomode) + { + if (noisy_piomode) + { + printf(" attempting to "); + if (piomode == 255) + printf("auto-tune PIO mode\n"); + else if (piomode < 100) + printf("set PIO mode to %d\n", piomode); + else if (piomode < 200) + printf("set MDMA mode to %d\n", (piomode-100)); + else + printf("set UDMA mode to %d\n", (piomode-200)); + } + bb_ioctl(fd, HDIO_SET_PIO_MODE, (int *)(unsigned long)piomode, "HDIO_SET_PIO_MODE"); + } + if (set_io32bit) + { + print_flag(get_io32bit,"32-bit IO_support flag", io32bit); + bb_ioctl(fd, HDIO_SET_32BIT, (int *)io32bit, "HDIO_SET_32BIT"); + } + if (set_mult) + { + print_flag(get_mult, "multcount", mult); +#ifdef HDIO_DRIVE_CMD + bb_ioctl(fd, HDIO_SET_MULTCOUNT, &mult, "HDIO_SET_MULTCOUNT"); +#else + force_operation |= (!bb_ioctl(fd, HDIO_SET_MULTCOUNT, &mult, "HDIO_SET_MULTCOUNT")); +#endif + } + if (set_readonly) + { + print_flag_on_off(get_readonly,"readonly", readonly); + bb_ioctl(fd, BLKROSET, &readonly, "BLKROSET"); + } + if (set_unmask) + { + print_flag_on_off(get_unmask,"unmaskirq", unmask); + bb_ioctl(fd, HDIO_SET_UNMASKINTR, (int *)unmask, "HDIO_SET_UNMASKINTR"); + } +#ifdef CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA + if (set_dma) + { + print_flag_on_off(get_dma, "using_dma", dma); + bb_ioctl(fd, HDIO_SET_DMA, (int *)dma, "HDIO_SET_DMA"); + } +#endif /* CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA */ + if (set_dma_q) + { + print_flag_on_off(get_dma_q,"DMA queue_depth", dma_q); + bb_ioctl(fd, HDIO_SET_QDMA, (int *)dma_q, "HDIO_SET_QDMA"); + } + if (set_nowerr) + { + print_flag_on_off(get_nowerr,"nowerr", nowerr); + bb_ioctl(fd, HDIO_SET_NOWERR, (int *)nowerr,"HDIO_SET_NOWERR"); + } + if (set_keep) + { + print_flag_on_off(get_keep,"keep_settings", keep); + bb_ioctl(fd, HDIO_SET_KEEPSETTINGS, (int *)keep,"HDIO_SET_KEEPSETTINGS"); + } +#ifdef HDIO_DRIVE_CMD + if (set_doorlock) + { + args[0] = doorlock ? WIN_DOORLOCK : WIN_DOORUNLOCK; + args[2] = 0; + print_flag_on_off(get_doorlock,"drive doorlock", doorlock); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args,"HDIO_DRIVE_CMD(doorlock)"); + args[0] = WIN_SETFEATURES; + } + if (set_dkeep) + { + /* lock/unlock the drive's "feature" settings */ + print_flag_on_off(get_dkeep,"drive keep features", dkeep); + args[2] = dkeep ? 0x66 : 0xcc; + bb_ioctl(fd, HDIO_DRIVE_CMD, &args,"HDIO_DRIVE_CMD(keepsettings)"); + } + if (set_defects) + { + args[2] = defects ? 0x04 : 0x84; + print_flag(get_defects,"drive defect-mgmt", defects); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args,"HDIO_DRIVE_CMD(defectmgmt)"); + } + if (set_prefetch) + { + args[1] = prefetch; + args[2] = 0xab; + print_flag(get_prefetch,"drive prefetch", prefetch); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args, "HDIO_DRIVE_CMD(setprefetch)"); + args[1] = 0; + } + if (set_xfermode) + { + args[1] = xfermode_requested; + args[2] = 3; + if (get_xfermode) + { + print_flag(1,"xfermode", xfermode_requested); + interpret_xfermode(xfermode_requested); + } + bb_ioctl(fd, HDIO_DRIVE_CMD, &args,"HDIO_DRIVE_CMD(setxfermode)"); + args[1] = 0; + } + if (set_lookahead) + { + args[2] = lookahead ? 0xaa : 0x55; + print_flag_on_off(get_lookahead,"drive read-lookahead", lookahead); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args, "HDIO_DRIVE_CMD(setreadahead)"); + } + if (set_apmmode) + { + args[2] = (apmmode == 255) ? 0x85 /* disable */ : 0x05 /* set */; /* feature register */ + args[1] = apmmode; /* sector count register 1-255 */ + if (get_apmmode) + printf(" setting APM level to %s 0x%02lX (%ld)\n", (apmmode == 255) ? "disabled" : "", apmmode, apmmode); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args,"HDIO_DRIVE_CMD"); + args[1] = 0; + } + if (set_wcache) + { +#ifdef DO_FLUSHCACHE +#ifndef WIN_FLUSHCACHE +#define WIN_FLUSHCACHE 0xe7 +#endif + static unsigned char flushcache[4] = {WIN_FLUSHCACHE,0,0,0}; +#endif /* DO_FLUSHCACHE */ + args[2] = wcache ? 0x02 : 0x82; + print_flag_on_off(get_wcache,"drive write-caching", wcache); +#ifdef DO_FLUSHCACHE + if (!wcache) + bb_ioctl(fd, HDIO_DRIVE_CMD, &flushcache, "HDIO_DRIVE_CMD(flushcache)"); +#endif /* DO_FLUSHCACHE */ + bb_ioctl(fd, HDIO_DRIVE_CMD, &args, "HDIO_DRIVE_CMD(setcache)"); +#ifdef DO_FLUSHCACHE + if (!wcache) + bb_ioctl(fd, HDIO_DRIVE_CMD, &flushcache, "HDIO_DRIVE_CMD(flushcache)"); +#endif /* DO_FLUSHCACHE */ + } + + /* In code below, we do not preserve args[0], but the rest + is preserved, including args[2] */ + args[2] = 0; + + if (set_standbynow) + { +#ifndef WIN_STANDBYNOW1 +#define WIN_STANDBYNOW1 0xE0 +#endif +#ifndef WIN_STANDBYNOW2 +#define WIN_STANDBYNOW2 0x94 +#endif + if (get_standbynow) printf(" issuing standby command\n"); + args[0] = WIN_STANDBYNOW1; + bb_ioctl_alt(fd, HDIO_DRIVE_CMD, args, WIN_STANDBYNOW2, "HDIO_DRIVE_CMD(standby)"); + } + if (set_sleepnow) + { +#ifndef WIN_SLEEPNOW1 +#define WIN_SLEEPNOW1 0xE6 +#endif +#ifndef WIN_SLEEPNOW2 +#define WIN_SLEEPNOW2 0x99 +#endif + if (get_sleepnow) printf(" issuing sleep command\n"); + args[0] = WIN_SLEEPNOW1; + bb_ioctl_alt(fd, HDIO_DRIVE_CMD, args, WIN_SLEEPNOW2, "HDIO_DRIVE_CMD(sleep)"); + } + if (set_seagate) + { + args[0] = 0xfb; + if (get_seagate) printf(" disabling Seagate auto powersaving mode\n"); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args, "HDIO_DRIVE_CMD(seagatepwrsave)"); + } + if (set_standby) + { + args[0] = WIN_SETIDLE1; + args[1] = standby_requested; + if (get_standby) + { + print_flag(1,"standby", standby_requested); + interpret_standby(standby_requested); + } + bb_ioctl(fd, HDIO_DRIVE_CMD, &args, "HDIO_DRIVE_CMD(setidle1)"); + args[1] = 0; + } +#else /* HDIO_DRIVE_CMD */ + if (force_operation) + { + char buf[512]; + flush_buffer_cache(fd); + if (-1 == read(fd, buf, sizeof(buf))) + bb_perror_msg("read(%d bytes) failed (rc=%d)", sizeof(buf), -1); + } +#endif /* HDIO_DRIVE_CMD */ + + if (get_mult || get_identity) + { + multcount = -1; + if (ioctl(fd, HDIO_GET_MULTCOUNT, &multcount)) + { + if (get_mult) + bb_perror_msg("HDIO_GET_MULTCOUNT"); + } + else if (get_mult) + { + printf(fmt, "multcount", multcount); + on_off(multcount); + } + } + if (get_io32bit) + { + if (!bb_ioctl(fd, HDIO_GET_32BIT, &parm, "HDIO_GET_32BIT")) + { + printf(" IO_support\t=%3ld (", parm); + if (parm == 0) + printf("default 16-bit)\n"); + else if (parm == 2) + printf("16-bit)\n"); + else if (parm == 1) + printf("32-bit)\n"); + else if (parm == 3) + printf("32-bit w/sync)\n"); + else if (parm == 8) + printf("Request-Queue-Bypass)\n"); + else + printf("\?\?\?)\n"); + } + } + if (get_unmask) + { + bb_ioctl_on_off(fd, HDIO_GET_UNMASKINTR,(unsigned long *)parm, + "HDIO_GET_UNMASKINTR","unmaskirq"); + } + + +#ifdef CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA + if (get_dma) { + if (!bb_ioctl(fd, HDIO_GET_DMA, &parm, "HDIO_GET_DMA")) + { + printf(fmt, "using_dma", parm); + if (parm == 8) + printf(" (DMA-Assisted-PIO)\n"); + else + on_off(parm); + } + } +#endif + if (get_dma_q) + { + bb_ioctl_on_off (fd, HDIO_GET_QDMA,(unsigned long *)parm, + "HDIO_GET_QDMA","queue_depth"); + } + if (get_keep) + { + bb_ioctl_on_off (fd, HDIO_GET_KEEPSETTINGS,(unsigned long *)parm, + "HDIO_GET_KEEPSETTINGS","keepsettings"); + } + + if (get_nowerr) + { + bb_ioctl_on_off (fd, HDIO_GET_NOWERR,(unsigned long *)&parm, + "HDIO_GET_NOWERR","nowerr"); + } + if (get_readonly) + { + bb_ioctl_on_off(fd, BLKROGET,(unsigned long *)parm, + "BLKROGET","readonly"); + } + if (get_readahead) + { + bb_ioctl_on_off (fd, BLKRAGET, (unsigned long *) parm, + "BLKRAGET","readahead"); + } + if (get_geom) + { + if (!bb_ioctl(fd, BLKGETSIZE, &parm, "BLKGETSIZE")) + { + struct hd_geometry g; + + if (!bb_ioctl(fd, HDIO_GETGEO, &g, "HDIO_GETGEO")) + printf(" geometry\t= %u/%u/%u, sectors = %ld, start = %ld\n", + g.cylinders, g.heads, g.sectors, parm, g.start); + } + } +#ifdef HDIO_DRIVE_CMD + if (get_powermode) + { +#ifndef WIN_CHECKPOWERMODE1 +#define WIN_CHECKPOWERMODE1 0xE5 +#endif +#ifndef WIN_CHECKPOWERMODE2 +#define WIN_CHECKPOWERMODE2 0x98 +#endif + const char *state; + + args[0] = WIN_CHECKPOWERMODE1; + if (bb_ioctl_alt(fd, HDIO_DRIVE_CMD, args, WIN_CHECKPOWERMODE2, 0)) + { + if (errno != EIO || args[0] != 0 || args[1] != 0) + state = "Unknown"; + else + state = "sleeping"; + } + else + state = (args[2] == 255) ? "active/idle" : "standby"; + args[1] = args[2] = 0; + + printf(" drive state is: %s\n", state); + } +#endif +#ifdef CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET + if (perform_reset) + { + bb_ioctl(fd, HDIO_DRIVE_RESET, NULL, "HDIO_DRIVE_RESET"); + } +#endif /* CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET */ +#ifdef CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF + if (perform_tristate) + { + args[0] = 0; + args[1] = tristate; + bb_ioctl(fd, HDIO_TRISTATE_HWIF, &args, "HDIO_TRISTATE_HWIF"); + } +#endif /* CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF */ +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY + if (get_identity) + { + static struct hd_driveid id; + + if (!ioctl(fd, HDIO_GET_IDENTITY, &id)) + { + if (multcount != -1) + { + id.multsect = multcount; + id.multsect_valid |= 1; + } + else + id.multsect_valid &= ~1; + dump_identity(&id); + } + else if (errno == -ENOMSG) + printf(" no identification info available\n"); + else + bb_perror_msg("HDIO_GET_IDENTITY"); + } + + if (get_IDentity) + { + unsigned char args1[4+512]; /* = { ... } will eat 0.5k of rodata! */ + + memset(args1, 0, sizeof(args1)); + args1[0] = WIN_IDENTIFY; + args1[3] = 1; + if (!bb_ioctl_alt(fd, HDIO_DRIVE_CMD, args1, WIN_PIDENTIFY, "HDIO_DRIVE_CMD(identify)")) + identify((void *)(args1 + 4)); + } +#endif +#ifdef CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF + if (set_busstate) + { + if (get_busstate) + { + print_flag(1, "bus state", busstate); + bus_state_value(busstate); + } + bb_ioctl(fd, HDIO_SET_BUSSTATE, (int *)(unsigned long)busstate, "HDIO_SET_BUSSTATE"); + } + if (get_busstate) + { + if (!bb_ioctl(fd, HDIO_GET_BUSSTATE, &parm, "HDIO_GET_BUSSTATE")) + { + printf(fmt, "bus state", parm); + bus_state_value(parm); + } + } +#endif + if (reread_partn) + bb_ioctl(fd, BLKRRPART, NULL, "BLKRRPART"); + + + if (do_ctimings) + do_time(0,fd); /*time cache */ + if (do_timings) + do_time(1,fd); /*time device */ + if (do_flush) + flush_buffer_cache(fd); + close(fd); +} + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static int fromhex(unsigned char c) +{ + if (c >= 'a' && c <= 'f') + return 10 + (c - 'a'); + if (c >= '0' && c <= '9') + return (c - '0'); + bb_error_msg_and_die("bad char: '%c' 0x%02x", c, c); +} + +static void identify_from_stdin(void) +{ + uint16_t sbuf[256]; + unsigned char buf[1280], *b = (unsigned char *)buf; + int i, count = read(0, buf, 1280); + + if (count != 1280) + bb_error_msg_and_die("read(%d bytes) failed (rc=%d)", 1280, count); + + // Convert the newline-separated hex data into an identify block. + + for (i = 0; i<256; i++) + { + int j; + for(j=0;j<4;j++) sbuf[i] = (sbuf[i] <<4) + fromhex(*(b++)); + } + + // Parse the data. + + identify(sbuf); +} +#endif + +/* busybox specific stuff */ +static void parse_opts(unsigned long *get, unsigned long *set, unsigned long *value, int min, int max) +{ + if (get) { + *get = 1; + } + if (optarg) { + *set = 1; + *value = xatol_range(optarg, min, max); + } +} + +static void parse_xfermode(int flag, unsigned long *get, unsigned long *set, int *value) +{ + if (flag) { + *get = 1; + if (optarg) { + *set = ((*value = translate_xfermode(optarg)) > -1); + } + } +} + +/*------- getopt short options --------*/ +static const char hdparm_options[] = "gfu::n::p:r::m::c::k::a::B:tTh" + USE_FEATURE_HDPARM_GET_IDENTITY("iI") + USE_FEATURE_HDPARM_HDIO_GETSET_DMA("d::") +#ifdef HDIO_DRIVE_CMD + "S:D:P:X:K:A:L:W:CyYzZ" +#endif + USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF("U:") +#ifdef HDIO_GET_QDMA +#ifdef HDIO_SET_QDMA + "Q:" +#else + "Q" +#endif +#endif + USE_FEATURE_HDPARM_HDIO_DRIVE_RESET("w") + USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF("x::b:") + USE_FEATURE_HDPARM_HDIO_SCAN_HWIF("R:"); +/*-------------------------------------*/ + +/* our main() routine: */ +int hdparm_main(int argc, char **argv) ATTRIBUTE_NORETURN; +int hdparm_main(int argc, char **argv) +{ + int c; + int flagcount = 0; + + while ((c = getopt(argc, argv, hdparm_options)) >= 0) { + flagcount++; + if (c == 'h') bb_show_usage(); /* EXIT */ + USE_FEATURE_HDPARM_GET_IDENTITY(get_IDentity |= (c == 'I')); + USE_FEATURE_HDPARM_GET_IDENTITY(get_identity |= (c == 'i')); + get_geom |= (c == 'g'); + do_flush |= (c == 'f'); + if (c == 'u') parse_opts(&get_unmask, &set_unmask, &unmask, 0, 1); + USE_FEATURE_HDPARM_HDIO_GETSET_DMA(if (c == 'd') parse_opts(&get_dma, &set_dma, &dma, 0, 9)); + if (c == 'n') parse_opts(&get_nowerr, &set_nowerr, &nowerr, 0, 1); + parse_xfermode((c == 'p'),&noisy_piomode, &set_piomode, &piomode); + if (c == 'r') parse_opts(&get_readonly, &set_readonly, &readonly, 0, 1); + if (c == 'm') parse_opts(&get_mult, &set_mult, &mult, 0, INT_MAX /*32*/); + if (c == 'c') parse_opts(&get_io32bit, &set_io32bit, &io32bit, 0, INT_MAX /*8*/); + if (c == 'k') parse_opts(&get_keep, &set_keep, &keep, 0, 1); + if (c == 'a') parse_opts(&get_readahead, &set_readahead, &Xreadahead, 0, INT_MAX); + if (c == 'B') parse_opts(&get_apmmode, &set_apmmode, &apmmode, 1, 255); + do_flush |= do_timings |= (c == 't'); + do_flush |= do_ctimings |= (c == 'T'); +#ifdef HDIO_DRIVE_CMD + if (c == 'S') parse_opts(&get_standby, &set_standby, &standby_requested, 0, INT_MAX); + if (c == 'D') parse_opts(&get_defects, &set_defects, &defects, 0, INT_MAX); + if (c == 'P') parse_opts(&get_prefetch, &set_prefetch, &prefetch, 0, INT_MAX); + parse_xfermode((c == 'X'), &get_xfermode, &set_xfermode, &xfermode_requested); + if (c == 'K') parse_opts(&get_dkeep, &set_dkeep, &prefetch, 0, 1); + if (c == 'A') parse_opts(&get_lookahead, &set_lookahead, &lookahead, 0, 1); + if (c == 'L') parse_opts(&get_doorlock, &set_doorlock, &doorlock, 0, 1); + if (c == 'W') parse_opts(&get_wcache, &set_wcache, &wcache, 0, 1); + get_powermode |= (c == 'C'); + get_standbynow = set_standbynow |= (c == 'y'); + get_sleepnow = set_sleepnow |= (c == 'Y'); + reread_partn |= (c == 'z'); + get_seagate = set_seagate |= (c == 'Z'); +#endif + USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF(if (c == 'U') parse_opts(NULL, &unregister_hwif, &hwif, 0, INT_MAX)); +#ifdef HDIO_GET_QDMA + if (c == 'Q') { +#ifdef HDIO_SET_QDMA + parse_opts(&get_dma_q, &set_dma_q, &dma_q, 0, INT_MAX); +#else + parse_opts(&get_dma_q, NULL, NULL, 0, 0); +#endif + } +#endif + USE_FEATURE_HDPARM_HDIO_DRIVE_RESET(perform_reset = (c == 'r')); + USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(if (c == 'x') parse_opts(NULL, &perform_tristate, &tristate, 0, 1)); + USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(if (c == 'b') parse_opts(&get_busstate, &set_busstate, &busstate, 0, 2)); +#if ENABLE_FEATURE_HDPARM_HDIO_SCAN_HWIF + if (c == 'R') { + parse_opts(NULL, &scan_hwif, &hwif_data, 0, INT_MAX); + hwif_ctrl = xatoi_u((argv[optind]) ? argv[optind] : ""); + hwif_irq = xatoi_u((argv[optind+1]) ? argv[optind+1] : ""); + /* Move past the 2 additional arguments */ + argv += 2; + argc -= 2; + } +#endif + } + /* When no flags are given (flagcount = 0), -acdgkmnru is assumed. */ + if (!flagcount) { + get_mult = get_io32bit = get_unmask = get_keep = get_readonly = get_readahead = get_geom = 1; + USE_FEATURE_HDPARM_HDIO_GETSET_DMA(get_dma = 1); + } + argc -= optind; + argv += optind; + + if (argc < 1) { + if (ENABLE_FEATURE_HDPARM_GET_IDENTITY && !isatty(STDIN_FILENO)) + identify_from_stdin(); /* EXIT */ + else bb_show_usage(); + } + + while (argc--) { + process_dev(*argv); + argv++; + } + exit(EXIT_SUCCESS); +} diff --git a/miscutils/last.c b/miscutils/last.c new file mode 100644 index 000000000..668f0c17d --- /dev/null +++ b/miscutils/last.c @@ -0,0 +1,91 @@ +/* vi: set sw=4 ts=4: */ +/* + * last implementation for busybox + * + * Copyright (C) 2003-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include + +#ifndef SHUTDOWN_TIME +# define SHUTDOWN_TIME 254 +#endif + +/* Grr... utmp char[] members do not have to be nul-terminated. + * Do what we can while still keeping this reasonably small. + * Note: We are assuming the ut_id[] size is fixed at 4. */ + +#if defined UT_LINESIZE \ + && ((UT_LINESIZE != 32) || (UT_NAMESIZE != 32) || (UT_HOSTSIZE != 256)) +#error struct utmp member char[] size(s) have changed! +#elif defined __UT_LINESIZE \ + && ((__UT_LINESIZE != 32) || (__UT_NAMESIZE != 64) || (__UT_HOSTSIZE != 256)) +#error struct utmp member char[] size(s) have changed! +#endif + +int last_main(int argc, char **argv) +{ + struct utmp ut; + int n, file = STDIN_FILENO; + time_t t_tmp; + + if (argc > 1) { + bb_show_usage(); + } + file = xopen(bb_path_wtmp_file, O_RDONLY); + + printf("%-10s %-14s %-18s %-12.12s %s\n", "USER", "TTY", "HOST", "LOGIN", "TIME"); + while ((n = safe_read(file, (void*)&ut, sizeof(struct utmp))) != 0) { + + if (n != sizeof(struct utmp)) { + bb_perror_msg_and_die("short read"); + } + + if (strncmp(ut.ut_line, "~", 1) == 0) { + if (strncmp(ut.ut_user, "shutdown", 8) == 0) + ut.ut_type = SHUTDOWN_TIME; + else if (strncmp(ut.ut_user, "reboot", 6) == 0) + ut.ut_type = BOOT_TIME; + else if (strncmp(ut.ut_user, "runlevel", 7) == 0) + ut.ut_type = RUN_LVL; + } else { + if (!ut.ut_name[0] || strcmp(ut.ut_name, "LOGIN") == 0 || + ut.ut_name[0] == 0) + { + /* Don't bother. This means we can't find how long + * someone was logged in for. Oh well. */ + continue; + } + if (ut.ut_type != DEAD_PROCESS && + ut.ut_name[0] && ut.ut_line[0]) + { + ut.ut_type = USER_PROCESS; + } + if (strcmp(ut.ut_name, "date") == 0) { + if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME; + if (ut.ut_line[0] == '{') ut.ut_type = NEW_TIME; + } + } + + if (ut.ut_type!=USER_PROCESS) { + switch (ut.ut_type) { + case OLD_TIME: + case NEW_TIME: + case RUN_LVL: + case SHUTDOWN_TIME: + continue; + case BOOT_TIME: + strcpy(ut.ut_line, "system boot"); + break; + } + } + t_tmp = (time_t)ut.ut_tv.tv_sec; + printf("%-10s %-14s %-18s %-12.12s\n", ut.ut_user, ut.ut_line, ut.ut_host, + ctime(&t_tmp) + 4); + } + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/miscutils/less.c b/miscutils/less.c new file mode 100644 index 000000000..03ffd78ed --- /dev/null +++ b/miscutils/less.c @@ -0,0 +1,1144 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini less implementation for busybox + * + * Copyright (C) 2005 by Rob Sullivan + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* + * This program needs a lot of development, so consider it in a beta stage + * at best. + * + * TODO: + * - Add more regular expression support - search modifiers, certain matches, etc. + * - Add more complex bracket searching - currently, nested brackets are + * not considered. + * - Add support for "F" as an input. This causes less to act in + * a similar way to tail -f. + * - Check for binary files, and prompt the user if a binary file + * is detected. + * - Allow horizontal scrolling. Currently, lines simply continue onto + * the next line, per the terminal's discretion + * + * Notes: + * - filename is an array and not a pointer because that avoids all sorts + * of complications involving the fact that something that is pointed to + * will be changed if the pointer is changed. + * - the inp file pointer is used so that keyboard input works after + * redirected input has been read from stdin +*/ + +#include "busybox.h" + +#ifdef CONFIG_FEATURE_LESS_REGEXP +#include "xregex.h" +#endif + + +/* These are the escape sequences corresponding to special keys */ +#define REAL_KEY_UP 'A' +#define REAL_KEY_DOWN 'B' +#define REAL_KEY_RIGHT 'C' +#define REAL_KEY_LEFT 'D' +#define REAL_PAGE_UP '5' +#define REAL_PAGE_DOWN '6' +#define REAL_KEY_HOME '7' +#define REAL_KEY_END '8' + +/* These are the special codes assigned by this program to the special keys */ +#define KEY_UP 20 +#define KEY_DOWN 21 +#define KEY_RIGHT 22 +#define KEY_LEFT 23 +#define PAGE_UP 24 +#define PAGE_DOWN 25 +#define KEY_HOME 26 +#define KEY_END 27 + +/* The escape codes for highlighted and normal text */ +#define HIGHLIGHT "\033[7m" +#define NORMAL "\033[0m" + +/* The escape code to clear the screen */ +#define CLEAR "\033[H\033[J" + +/* Maximum number of lines in a file */ +#define MAXLINES 10000 + +static int height; +static int width; +static char **files; +static char filename[256]; +static char **buffer; +static char **flines; +static int current_file = 1; +static int line_pos; +static int num_flines; +static int num_files = 1; + +/* Command line options */ +static unsigned flags; +#define FLAG_E 1 +#define FLAG_M (1<<1) +#define FLAG_m (1<<2) +#define FLAG_N (1<<3) +#define FLAG_TILDE (1<<4) +/* hijack command line options variable for internal state vars */ +#define LESS_STATE_INP_STDIN (1<<5) +#define LESS_STATE_PAST_EOF (1<<6) +#define LESS_STATE_MATCH_BACKWARDS (1<<7) +/* INP_STDIN is used to change behaviour when input comes from stdin */ + +#ifdef CONFIG_FEATURE_LESS_MARKS +static int mark_lines[15][2]; +static int num_marks; +#endif + +#ifdef CONFIG_FEATURE_LESS_REGEXP +static int match_found; +static int *match_lines; +static int match_pos; +static int num_matches; +static regex_t old_pattern; +#endif + +/* Needed termios structures */ +static struct termios term_orig, term_vi; + +/* File pointer to get input from */ +static FILE *inp; + +/* Reset terminal input to normal */ +static void set_tty_cooked(void) +{ + fflush(stdout); + tcsetattr(fileno(inp), TCSANOW, &term_orig); +} + +/* Exit the program gracefully */ +static void tless_exit(int code) +{ + /* TODO: We really should save the terminal state when we start, + and restore it when we exit. Less does this with the + "ti" and "te" termcap commands; can this be done with + only termios.h? */ + + putchar('\n'); + fflush_stdout_and_exit(code); +} + +/* Grab a character from input without requiring the return key. If the + character is ASCII \033, get more characters and assign certain sequences + special return codes. Note that this function works best with raw input. */ +static int tless_getch(void) +{ + int input; + /* Set terminal input to raw mode (taken from vi.c) */ + tcsetattr(fileno(inp), TCSANOW, &term_vi); + + input = getc(inp); + /* Detect escape sequences (i.e. arrow keys) and handle + them accordingly */ + + if (input == '\033' && getc(inp) == '[') { + unsigned int i; + input = getc(inp); + set_tty_cooked(); + + i = input - REAL_KEY_UP; + if (i < 4) + return 20 + i; + else if ((i = input - REAL_PAGE_UP) < 4) + return 24 + i; + } + /* The input is a normal ASCII value */ + else { + set_tty_cooked(); + return input; + } + return 0; +} + +/* Move the cursor to a position (x,y), where (0,0) is the + top-left corner of the console */ +static void move_cursor(int x, int y) +{ + printf("\033[%i;%iH", x, y); +} + +static void clear_line(void) +{ + move_cursor(height, 0); + printf("\033[K"); +} + +/* This adds line numbers to every line, as the -N flag necessitates */ +static void add_linenumbers(void) +{ + int i; + + for (i = 0; i <= num_flines; i++) { + char *new = xasprintf("%5d %s", i + 1, flines[i]); + free(flines[i]); + flines[i] = new; + } +} + +static void data_readlines(void) +{ + int i; + char current_line[256]; + FILE *fp; + + fp = (flags & LESS_STATE_INP_STDIN) ? stdin : xfopen(filename, "r"); + flines = NULL; + for (i = 0; (feof(fp)==0) && (i <= MAXLINES); i++) { + strcpy(current_line, ""); + fgets(current_line, 256, fp); + if (fp != stdin) + die_if_ferror(fp, filename); + flines = xrealloc(flines, (i+1) * sizeof(char *)); + flines[i] = xstrdup(current_line); + } + num_flines = i - 2; + + /* Reset variables for a new file */ + + line_pos = 0; + flags &= ~LESS_STATE_PAST_EOF; + + fclose(fp); + + if (inp == NULL) + inp = (flags & LESS_STATE_INP_STDIN) ? xfopen(CURRENT_TTY, "r") : stdin; + + if (flags & FLAG_N) + add_linenumbers(); +} + +#ifdef CONFIG_FEATURE_LESS_FLAGS + +/* Interestingly, writing calc_percent as a function and not a prototype saves around 32 bytes + * on my build. */ +static int calc_percent(void) +{ + return ((100 * (line_pos + height - 2) / num_flines) + 1); +} + +/* Print a status line if -M was specified */ +static void m_status_print(void) +{ + int percentage; + + if (!(flags & LESS_STATE_PAST_EOF)) { + if (!line_pos) { + if (num_files > 1) + printf("%s%s %s%i%s%i%s%i-%i/%i ", HIGHLIGHT, + filename, "(file ", current_file, " of ", num_files, ") lines ", + line_pos + 1, line_pos + height - 1, num_flines + 1); + else { + printf("%s%s lines %i-%i/%i ", HIGHLIGHT, + filename, line_pos + 1, line_pos + height - 1, + num_flines + 1); + } + } + else { + printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename, + line_pos + 1, line_pos + height - 1, num_flines + 1); + } + + if (line_pos == num_flines - height + 2) { + printf("(END) %s", NORMAL); + if ((num_files > 1) && (current_file != num_files)) + printf("%s- Next: %s%s", HIGHLIGHT, files[current_file], NORMAL); + } + else { + percentage = calc_percent(); + printf("%i%% %s", percentage, NORMAL); + } + } + else { + printf("%s%s lines %i-%i/%i (END) ", HIGHLIGHT, filename, + line_pos + 1, num_flines + 1, num_flines + 1); + if ((num_files > 1) && (current_file != num_files)) + printf("- Next: %s", files[current_file]); + printf("%s", NORMAL); + } +} + +/* Print a status line if -m was specified */ +static void medium_status_print(void) +{ + int percentage; + percentage = calc_percent(); + + if (!line_pos) + printf("%s%s %i%%%s", HIGHLIGHT, filename, percentage, NORMAL); + else if (line_pos == num_flines - height + 2) + printf("%s(END)%s", HIGHLIGHT, NORMAL); + else + printf("%s%i%%%s", HIGHLIGHT, percentage, NORMAL); +} +#endif + +/* Print the status line */ +static void status_print(void) +{ + /* Change the status if flags have been set */ +#ifdef CONFIG_FEATURE_LESS_FLAGS + if (flags & FLAG_M) + m_status_print(); + else if (flags & FLAG_m) + medium_status_print(); + /* No flags set */ + else { +#endif + if (!line_pos) { + printf("%s%s %s", HIGHLIGHT, filename, NORMAL); + if (num_files > 1) + printf("%s%s%i%s%i%s%s", HIGHLIGHT, "(file ", + current_file, " of ", num_files, ")", NORMAL); + } + else if (line_pos == num_flines - height + 2) { + printf("%s%s %s", HIGHLIGHT, "(END)", NORMAL); + if ((num_files > 1) && (current_file != num_files)) + printf("%s%s%s%s", HIGHLIGHT, "- Next: ", files[current_file], NORMAL); + } + else { + putchar(':'); + } +#ifdef CONFIG_FEATURE_LESS_FLAGS + } +#endif +} + +/* Print the buffer */ +static void buffer_print(void) +{ + int i; + + printf("%s", CLEAR); + if (num_flines >= height - 2) { + for (i = 0; i < height - 1; i++) + printf("%s", buffer[i]); + } + else { + for (i = 1; i < (height - 1 - num_flines); i++) + putchar('\n'); + for (i = 0; i < height - 1; i++) + printf("%s", buffer[i]); + } + + status_print(); +} + +/* Initialise the buffer */ +static void buffer_init(void) +{ + int i; + + if (buffer == NULL) { + /* malloc the number of lines needed for the buffer */ + buffer = xrealloc(buffer, height * sizeof(char *)); + } else { + for (i = 0; i < (height - 1); i++) + free(buffer[i]); + } + + /* Fill the buffer until the end of the file or the + end of the buffer is reached */ + for (i = 0; (i < (height - 1)) && (i <= num_flines); i++) { + buffer[i] = xstrdup(flines[i]); + } + + /* If the buffer still isn't full, fill it with blank lines */ + for (; i < (height - 1); i++) { + buffer[i] = xstrdup(""); + } +} + +/* Move the buffer up and down in the file in order to scroll */ +static void buffer_down(int nlines) +{ + int i; + + if (!(flags & LESS_STATE_PAST_EOF)) { + if (line_pos + (height - 3) + nlines < num_flines) { + line_pos += nlines; + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = xstrdup(flines[line_pos + i]); + } + } + else { + /* As the number of lines requested was too large, we just move + to the end of the file */ + while (line_pos + (height - 3) + 1 < num_flines) { + line_pos += 1; + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = xstrdup(flines[line_pos + i]); + } + } + } + + /* We exit if the -E flag has been set */ + if ((flags & FLAG_E) && (line_pos + (height - 2) == num_flines)) + tless_exit(0); + } +} + +static void buffer_up(int nlines) +{ + int i; + int tilde_line; + + if (!(flags & LESS_STATE_PAST_EOF)) { + if (line_pos - nlines >= 0) { + line_pos -= nlines; + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = xstrdup(flines[line_pos + i]); + } + } + else { + /* As the requested number of lines to move was too large, we + move one line up at a time until we can't. */ + while (line_pos != 0) { + line_pos -= 1; + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = xstrdup(flines[line_pos + i]); + } + } + } + } + else { + /* Work out where the tildes start */ + tilde_line = num_flines - line_pos + 3; + + line_pos -= nlines; + /* Going backwards nlines lines has taken us to a point where + nothing is past the EOF, so we revert to normal. */ + if (line_pos < num_flines - height + 3) { + flags &= ~LESS_STATE_PAST_EOF; + buffer_up(nlines); + } + else { + /* We only move part of the buffer, as the rest + is past the EOF */ + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + if (i < tilde_line - nlines + 1) + buffer[i] = xstrdup(flines[line_pos + i]); + else { + if (line_pos >= num_flines - height + 2) + buffer[i] = xstrdup("~\n"); + } + } + } + } +} + +static void buffer_line(int linenum) +{ + int i; + flags &= ~LESS_STATE_PAST_EOF; + + if (linenum < 0 || linenum > num_flines) { + clear_line(); + printf("%s%s%i%s", HIGHLIGHT, "Cannot seek to line number ", linenum + 1, NORMAL); + } + else if (linenum < (num_flines - height - 2)) { + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = xstrdup(flines[linenum + i]); + } + line_pos = linenum; + buffer_print(); + } + else { + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + if (linenum + i < num_flines + 2) + buffer[i] = xstrdup(flines[linenum + i]); + else + buffer[i] = xstrdup((flags & FLAG_TILDE) ? "\n" : "~\n"); + } + line_pos = linenum; + /* Set past_eof so buffer_down and buffer_up act differently */ + flags |= LESS_STATE_PAST_EOF; + buffer_print(); + } +} + +/* Reinitialise everything for a new file - free the memory and start over */ +static void reinitialise(void) +{ + int i; + + for (i = 0; i <= num_flines; i++) + free(flines[i]); + free(flines); + + data_readlines(); + buffer_init(); + buffer_print(); +} + +static void examine_file(void) +{ + int newline_offset; + + clear_line(); + printf("Examine: "); + fgets(filename, 256, inp); + + /* As fgets adds a newline to the end of an input string, we + need to remove it */ + newline_offset = strlen(filename) - 1; + filename[newline_offset] = '\0'; + + files[num_files] = xstrdup(filename); + current_file = num_files + 1; + num_files++; + + flags &= ~LESS_STATE_INP_STDIN; + reinitialise(); +} + +/* This function changes the file currently being paged. direction can be one of the following: + * -1: go back one file + * 0: go to the first file + * 1: go forward one file +*/ +static void change_file(int direction) +{ + if (current_file != ((direction > 0) ? num_files : 1)) { + current_file = direction ? current_file + direction : 1; + strcpy(filename, files[current_file - 1]); + reinitialise(); + } + else { + clear_line(); + printf("%s%s%s", HIGHLIGHT, (direction > 0) ? "No next file" : "No previous file", NORMAL); + } +} + +static void remove_current_file(void) +{ + int i; + + if (current_file != 1) { + change_file(-1); + for (i = 3; i <= num_files; i++) + files[i - 2] = files[i - 1]; + num_files--; + buffer_print(); + } + else { + change_file(1); + for (i = 2; i <= num_files; i++) + files[i - 2] = files[i - 1]; + num_files--; + current_file--; + buffer_print(); + } +} + +static void colon_process(void) +{ + int keypress; + + /* Clear the current line and print a prompt */ + clear_line(); + printf(" :"); + + keypress = tless_getch(); + switch (keypress) { + case 'd': + remove_current_file(); + break; + case 'e': + examine_file(); + break; +#ifdef CONFIG_FEATURE_LESS_FLAGS + case 'f': + clear_line(); + m_status_print(); + break; +#endif + case 'n': + change_file(1); + break; + case 'p': + change_file(-1); + break; + case 'q': + tless_exit(0); + break; + case 'x': + change_file(0); + break; + default: + break; + } +} + +#ifdef CONFIG_FEATURE_LESS_REGEXP +/* The below two regular expression handler functions NEED development. */ + +/* Get a regular expression from the user, and then go through the current + file line by line, running a processing regex function on each one. */ + +static char *process_regex_on_line(char *line, regex_t *pattern, int action) +{ + /* This function takes the regex and applies it to the line. + Each part of the line that matches has the HIGHLIGHT + and NORMAL escape sequences placed around it by + insert_highlights if action = 1, or has the escape sequences + removed if action = 0, and then the line is returned. */ + int match_status; + char *line2 = xmalloc((sizeof(char) * (strlen(line) + 1)) + 64); + char *growline = ""; + regmatch_t match_structs; + + line2 = xstrdup(line); + + match_found = 0; + match_status = regexec(pattern, line2, 1, &match_structs, 0); + + while (match_status == 0) { + if (match_found == 0) + match_found = 1; + + if (action) { + growline = xasprintf("%s%.*s%s%.*s%s", growline, + match_structs.rm_so, line2, HIGHLIGHT, + match_structs.rm_eo - match_structs.rm_so, + line2 + match_structs.rm_so, NORMAL); + } + else { + growline = xasprintf("%s%.*s%.*s", growline, + match_structs.rm_so - 4, line2, + match_structs.rm_eo - match_structs.rm_so, + line2 + match_structs.rm_so); + } + + line2 += match_structs.rm_eo; + match_status = regexec(pattern, line2, 1, &match_structs, REG_NOTBOL); + } + + growline = xasprintf("%s%s", growline, line2); + + return (match_found ? growline : line); + + free(growline); + free(line2); +} + +static void goto_match(int match) +{ + /* This goes to a specific match - all line positions of matches are + stored within the match_lines[] array. */ + if ((match < num_matches) && (match >= 0)) { + buffer_line(match_lines[match]); + match_pos = match; + } +} + +static void regex_process(void) +{ + char uncomp_regex[100]; + char *current_line; + int i; + int j = 0; + regex_t pattern; + /* Get the uncompiled regular expression from the user */ + clear_line(); + putchar((flags & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/'); + uncomp_regex[0] = 0; + fgets(uncomp_regex, sizeof(uncomp_regex), inp); + + if (strlen(uncomp_regex) == 1) { + if (num_matches) + goto_match((flags & LESS_STATE_MATCH_BACKWARDS) + ? match_pos - 1 : match_pos + 1); + else + buffer_print(); + return; + } + uncomp_regex[strlen(uncomp_regex) - 1] = '\0'; + + /* Compile the regex and check for errors */ + xregcomp(&pattern, uncomp_regex, 0); + + if (num_matches) { + /* Get rid of all the highlights we added previously */ + for (i = 0; i <= num_flines; i++) { + current_line = process_regex_on_line(flines[i], &old_pattern, 0); + flines[i] = xstrdup(current_line); + } + } + old_pattern = pattern; + + /* Reset variables */ + match_lines = xrealloc(match_lines, sizeof(int)); + match_lines[0] = -1; + match_pos = 0; + num_matches = 0; + match_found = 0; + /* Run the regex on each line of the current file here */ + for (i = 0; i <= num_flines; i++) { + current_line = process_regex_on_line(flines[i], &pattern, 1); + flines[i] = xstrdup(current_line); + if (match_found) { + match_lines = xrealloc(match_lines, (j + 1) * sizeof(int)); + match_lines[j] = i; + j++; + } + } + + num_matches = j; + if ((match_lines[0] != -1) && (num_flines > height - 2)) { + if (flags & LESS_STATE_MATCH_BACKWARDS) { + for (i = 0; i < num_matches; i++) { + if (match_lines[i] > line_pos) { + match_pos = i - 1; + buffer_line(match_lines[match_pos]); + break; + } + } + } + else + buffer_line(match_lines[0]); + } + else + buffer_init(); +} +#endif + +static void number_process(int first_digit) +{ + int i = 1; + int num; + char num_input[80]; + char keypress; + char *endptr; + + num_input[0] = first_digit; + + /* Clear the current line, print a prompt, and then print the digit */ + clear_line(); + printf(":%c", first_digit); + + /* Receive input until a letter is given (max 80 chars)*/ + while((i < 80) && (num_input[i] = tless_getch()) && isdigit(num_input[i])) { + putchar(num_input[i]); + i++; + } + + /* Take the final letter out of the digits string */ + keypress = num_input[i]; + num_input[i] = '\0'; + num = strtol(num_input, &endptr, 10); + if (endptr==num_input || *endptr!='\0' || num < 1 || num > MAXLINES) { + buffer_print(); + return; + } + + /* We now know the number and the letter entered, so we process them */ + switch (keypress) { + case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015': + buffer_down(num); + break; + case KEY_UP: case 'b': case 'w': case 'y': case 'u': + buffer_up(num); + break; + case 'g': case '<': case 'G': case '>': + if (num_flines >= height - 2) + buffer_line(num - 1); + break; + case 'p': case '%': + buffer_line(((num / 100) * num_flines) - 1); + break; +#ifdef CONFIG_FEATURE_LESS_REGEXP + case 'n': + goto_match(match_pos + num); + break; + case '/': + flags &= ~LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; + case '?': + flags |= LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; +#endif + default: + break; + } +} + +#ifdef CONFIG_FEATURE_LESS_FLAGCS +static void flag_change(void) +{ + int keypress; + + clear_line(); + putchar('-'); + keypress = tless_getch(); + + switch (keypress) { + case 'M': + flags ^= FLAG_M; + break; + case 'm': + flags ^= FLAG_m; + break; + case 'E': + flags ^= FLAG_E; + break; + case '~': + flags ^= FLAG_TILDE; + break; + default: + break; + } +} + +static void show_flag_status(void) +{ + int keypress; + int flag_val; + + clear_line(); + putchar('_'); + keypress = tless_getch(); + + switch (keypress) { + case 'M': + flag_val = flags & FLAG_M; + break; + case 'm': + flag_val = flags & FLAG_m; + break; + case '~': + flag_val = flags & FLAG_TILDE; + break; + case 'N': + flag_val = flags & FLAG_N; + break; + case 'E': + flag_val = flags & FLAG_E; + break; + default: + flag_val = 0; + break; + } + + clear_line(); + printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL); +} +#endif + +static void full_repaint(void) +{ + int temp_line_pos = line_pos; + data_readlines(); + buffer_init(); + buffer_line(temp_line_pos); +} + + +static void save_input_to_file(void) +{ + char current_line[256]; + int i; + FILE *fp; + + clear_line(); + printf("Log file: "); + fgets(current_line, 256, inp); + current_line[strlen(current_line) - 1] = '\0'; + if (strlen(current_line) > 1) { + fp = xfopen(current_line, "w"); + for (i = 0; i < num_flines; i++) + fprintf(fp, "%s", flines[i]); + fclose(fp); + buffer_print(); + } + else + printf("%s%s%s", HIGHLIGHT, "No log file", NORMAL); +} + +#ifdef CONFIG_FEATURE_LESS_MARKS +static void add_mark(void) +{ + int letter; + + clear_line(); + printf("Mark: "); + letter = tless_getch(); + + if (isalpha(letter)) { + + /* If we exceed 15 marks, start overwriting previous ones */ + if (num_marks == 14) + num_marks = 0; + + mark_lines[num_marks][0] = letter; + mark_lines[num_marks][1] = line_pos; + num_marks++; + } + else { + clear_line(); + printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); + } +} + +static void goto_mark(void) +{ + int letter; + int i; + + clear_line(); + printf("Go to mark: "); + letter = tless_getch(); + clear_line(); + + if (isalpha(letter)) { + for (i = 0; i <= num_marks; i++) + if (letter == mark_lines[i][0]) { + buffer_line(mark_lines[i][1]); + break; + } + if ((num_marks == 14) && (letter != mark_lines[14][0])) + printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL); + } + else + printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); +} +#endif + + +#ifdef CONFIG_FEATURE_LESS_BRACKETS + +static char opp_bracket(char bracket) +{ + switch (bracket) { + case '{': case '[': + return bracket + 2; + case '(': + return ')'; + case '}': case ']': + return bracket - 2; + case ')': + return '('; + default: + return 0; + } +} + +static void match_right_bracket(char bracket) +{ + int bracket_line = -1; + int i; + + clear_line(); + + if (strchr(flines[line_pos], bracket) == NULL) + printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL); + else { + for (i = line_pos + 1; i < num_flines; i++) { + if (strchr(flines[i], opp_bracket(bracket)) != NULL) { + bracket_line = i; + break; + } + } + + if (bracket_line == -1) + printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL); + + buffer_line(bracket_line - height + 2); + } +} + +static void match_left_bracket(char bracket) +{ + int bracket_line = -1; + int i; + + clear_line(); + + if (strchr(flines[line_pos + height - 2], bracket) == NULL) { + printf("%s%s%s", HIGHLIGHT, "No bracket in bottom line", NORMAL); + printf("%s", flines[line_pos + height]); + sleep(4); + } + else { + for (i = line_pos + height - 2; i >= 0; i--) { + if (strchr(flines[i], opp_bracket(bracket)) != NULL) { + bracket_line = i; + break; + } + } + + if (bracket_line == -1) + printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL); + + buffer_line(bracket_line); + } +} + +#endif /* CONFIG_FEATURE_LESS_BRACKETS */ + +static void keypress_process(int keypress) +{ + switch (keypress) { + case KEY_DOWN: case 'e': case 'j': case '\015': + buffer_down(1); + buffer_print(); + break; + case KEY_UP: case 'y': case 'k': + buffer_up(1); + buffer_print(); + break; + case PAGE_DOWN: case ' ': case 'z': + buffer_down(height - 1); + buffer_print(); + break; + case PAGE_UP: case 'w': case 'b': + buffer_up(height - 1); + buffer_print(); + break; + case 'd': + buffer_down((height - 1) / 2); + buffer_print(); + break; + case 'u': + buffer_up((height - 1) / 2); + buffer_print(); + break; + case KEY_HOME: case 'g': case 'p': case '<': case '%': + buffer_line(0); + break; + case KEY_END: case 'G': case '>': + buffer_line(num_flines - height + 2); + break; + case 'q': case 'Q': + tless_exit(0); + break; +#ifdef CONFIG_FEATURE_LESS_MARKS + case 'm': + add_mark(); + buffer_print(); + break; + case '\'': + goto_mark(); + buffer_print(); + break; +#endif + case 'r': + buffer_print(); + break; + case 'R': + full_repaint(); + break; + case 's': + if (flags & LESS_STATE_INP_STDIN) + save_input_to_file(); + break; + case 'E': + examine_file(); + break; +#ifdef CONFIG_FEATURE_LESS_FLAGS + case '=': + clear_line(); + m_status_print(); + break; +#endif +#ifdef CONFIG_FEATURE_LESS_REGEXP + case '/': + flags &= ~LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; + case 'n': + goto_match(match_pos + 1); + break; + case 'N': + goto_match(match_pos - 1); + break; + case '?': + flags |= LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; +#endif +#ifdef CONFIG_FEATURE_LESS_FLAGCS + case '-': + flag_change(); + buffer_print(); + break; + case '_': + show_flag_status(); + break; +#endif +#ifdef CONFIG_FEATURE_LESS_BRACKETS + case '{': case '(': case '[': + match_right_bracket(keypress); + break; + case '}': case ')': case ']': + match_left_bracket(keypress); + break; +#endif + case ':': + colon_process(); + break; + default: + break; + } + + if (isdigit(keypress)) + number_process(keypress); +} + +int less_main(int argc, char **argv) { + + int keypress; + + flags = getopt32(argc, argv, "EMmN~"); + + argc -= optind; + argv += optind; + files = argv; + num_files = argc; + + if (!num_files) { + if (ttyname(STDIN_FILENO) == NULL) + flags |= LESS_STATE_INP_STDIN; + else { + bb_error_msg("missing filename"); + bb_show_usage(); + } + } + + strcpy(filename, (flags & LESS_STATE_INP_STDIN) ? bb_msg_standard_input : files[0]); + get_terminal_width_height(0, &width, &height); + data_readlines(); + tcgetattr(fileno(inp), &term_orig); + term_vi = term_orig; + term_vi.c_lflag &= (~ICANON & ~ECHO); + term_vi.c_iflag &= (~IXON & ~ICRNL); + term_vi.c_oflag &= (~ONLCR); + term_vi.c_cc[VMIN] = 1; + term_vi.c_cc[VTIME] = 0; + buffer_init(); + buffer_print(); + + while (1) { + keypress = tless_getch(); + keypress_process(keypress); + } +} diff --git a/miscutils/makedevs.c b/miscutils/makedevs.c new file mode 100644 index 000000000..3bc1559c7 --- /dev/null +++ b/miscutils/makedevs.c @@ -0,0 +1,230 @@ +/* vi: set sw=4 ts=4: */ +/* + * public domain -- Dave 'Kill a Cop' Cinege + * + * makedevs + * Make ranges of device files quickly. + * known bugs: can't deal with alpha ranges + */ + +#include "busybox.h" + +#ifdef CONFIG_FEATURE_MAKEDEVS_LEAF +int makedevs_main(int argc, char **argv) +{ + mode_t mode; + char *basedev, *type, *nodname, buf[255]; + int Smajor, Sminor, S, E; + + if (argc < 7 || *argv[1]=='-') + bb_show_usage(); + + basedev = argv[1]; + type = argv[2]; + Smajor = xatoi_u(argv[3]); + Sminor = xatoi_u(argv[4]); + S = xatoi_u(argv[5]); + E = xatoi_u(argv[6]); + nodname = argc == 8 ? basedev : buf; + + mode = 0660; + + switch (type[0]) { + case 'c': + mode |= S_IFCHR; + break; + case 'b': + mode |= S_IFBLK; + break; + case 'f': + mode |= S_IFIFO; + break; + default: + bb_show_usage(); + } + + while (S <= E) { + int sz; + + sz = snprintf(buf, sizeof(buf), "%s%d", basedev, S); + if(sz<0 || sz>=sizeof(buf)) /* libc different */ + bb_error_msg_and_die("%s too large", basedev); + + /* if mode != S_IFCHR and != S_IFBLK third param in mknod() ignored */ + + if (mknod(nodname, mode, makedev(Smajor, Sminor))) + bb_error_msg("failed to create: %s", nodname); + + if (nodname == basedev) /* ex. /dev/hda - to /dev/hda1 ... */ + nodname = buf; + S++; + Sminor++; + } + + return 0; +} + +#elif defined CONFIG_FEATURE_MAKEDEVS_TABLE + +/* Licensed under the GPL v2 or later, see the file LICENSE in this tarball. */ + +int makedevs_main(int argc, char **argv) +{ + FILE *table = stdin; + char *rootdir = NULL; + char *line = NULL; + int linenum = 0; + int ret = EXIT_SUCCESS; + + getopt32(argc, argv, "d:", &line); + if (line) + table = xfopen(line, "r"); + + if (optind >= argc || (rootdir=argv[optind])==NULL) { + bb_error_msg_and_die("root directory not specified"); + } + + xchdir(rootdir); + + umask(0); + + printf("rootdir=%s\n", rootdir); + if (line) { + printf("table='%s'\n", line); + } else { + printf("table=\n"); + } + + while ((line = xmalloc_getline(table))) { + char type; + unsigned int mode = 0755; + unsigned int major = 0; + unsigned int minor = 0; + unsigned int count = 0; + unsigned int increment = 0; + unsigned int start = 0; + char name[41]; + char user[41]; + char group[41]; + char *full_name; + uid_t uid; + gid_t gid; + + linenum++; + + if ((2 > sscanf(line, "%40s %c %o %40s %40s %u %u %u %u %u", name, + &type, &mode, user, group, &major, + &minor, &start, &increment, &count)) || + ((major | minor | start | count | increment) > 255)) + { + if (*line=='\0' || *line=='#' || isspace(*line)) + continue; + bb_error_msg("line %d invalid: '%s'", linenum, line); + ret = EXIT_FAILURE; + continue; + } + if (name[0] == '#') { + continue; + } + + gid = (*group) ? get_ug_id(group, bb_xgetgrnam) : getgid(); + uid = (*user) ? get_ug_id(user, bb_xgetpwnam) : getuid(); + full_name = concat_path_file(rootdir, name); + + if (type == 'd') { + bb_make_directory(full_name, mode | S_IFDIR, FILEUTILS_RECUR); + if (chown(full_name, uid, gid) == -1) { + bb_perror_msg("line %d: chown failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + goto loop; + } + if ((mode != -1) && (chmod(full_name, mode) < 0)){ + bb_perror_msg("line %d: chmod failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + goto loop; + } + } else if (type == 'f') { + struct stat st; + if ((stat(full_name, &st) < 0 || !S_ISREG(st.st_mode))) { + bb_perror_msg("line %d: regular file '%s' does not exist", linenum, full_name); + ret = EXIT_FAILURE; + goto loop; + } + if (chown(full_name, uid, gid) == -1) { + bb_perror_msg("line %d: chown failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + goto loop; + } + if ((mode != -1) && (chmod(full_name, mode) < 0)){ + bb_perror_msg("line %d: chmod failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + goto loop; + } + } else + { + dev_t rdev; + + if (type == 'p') { + mode |= S_IFIFO; + } + else if (type == 'c') { + mode |= S_IFCHR; + } + else if (type == 'b') { + mode |= S_IFBLK; + } else { + bb_error_msg("line %d: unsupported file type %c", linenum, type); + ret = EXIT_FAILURE; + goto loop; + } + + if (count > 0) { + int i; + char *full_name_inc; + + full_name_inc = xmalloc(strlen(full_name) + 4); + for (i = start; i < count; i++) { + sprintf(full_name_inc, "%s%d", full_name, i); + rdev = (major << 8) + minor + (i * increment - start); + if (mknod(full_name_inc, mode, rdev) == -1) { + bb_perror_msg("line %d: cannot create node %s", linenum, full_name_inc); + ret = EXIT_FAILURE; + } + else if (chown(full_name_inc, uid, gid) == -1) { + bb_perror_msg("line %d: chown failed for %s", linenum, full_name_inc); + ret = EXIT_FAILURE; + } + if ((mode != -1) && (chmod(full_name_inc, mode) < 0)){ + bb_perror_msg("line %d: chmod failed for %s", linenum, full_name_inc); + ret = EXIT_FAILURE; + } + } + free(full_name_inc); + } else { + rdev = (major << 8) + minor; + if (mknod(full_name, mode, rdev) == -1) { + bb_perror_msg("line %d: cannot create node %s", linenum, full_name); + ret = EXIT_FAILURE; + } + else if (chown(full_name, uid, gid) == -1) { + bb_perror_msg("line %d: chown failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + } + if ((mode != -1) && (chmod(full_name, mode) < 0)){ + bb_perror_msg("line %d: chmod failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + } + } + } +loop: + free(line); + free(full_name); + } + fclose(table); + + return ret; +} + +#else +# error makedevs configuration error, either leaf or table must be selected +#endif diff --git a/miscutils/mountpoint.c b/miscutils/mountpoint.c new file mode 100644 index 000000000..b1bcab698 --- /dev/null +++ b/miscutils/mountpoint.c @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * mountpoint implementation for busybox + * + * Copyright (C) 2005 Bernhard Fischer + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Based on sysvinit's mountpoint + */ + +#include "busybox.h" + +int mountpoint_main(int argc, char **argv) +{ + struct stat st; + char *arg; + int opt = getopt32(argc, argv, "qdx"); +#define OPT_q (1) +#define OPT_d (2) +#define OPT_x (4) + + if (optind != argc - 1) + bb_show_usage(); + + arg = argv[optind]; + + if ( (opt & OPT_x && stat(arg, &st) == 0) || (lstat(arg, &st) == 0) ) { + if (opt & OPT_x) { + if (S_ISBLK(st.st_mode)) { + printf("%u:%u\n", major(st.st_rdev), + minor(st.st_rdev)); + return EXIT_SUCCESS; + } else { + if (opt & OPT_q) + putchar('\n'); + else + bb_error_msg("%s: not a block device", arg); + } + return EXIT_FAILURE; + } else + if (S_ISDIR(st.st_mode)) { + dev_t st_dev = st.st_dev; + ino_t st_ino = st.st_ino; + char *p = xasprintf("%s/..", arg); + + if (stat(p, &st) == 0) { + int ret = (st_dev != st.st_dev) || + (st_dev == st.st_dev && st_ino == st.st_ino); + if (opt & OPT_d) + printf("%u:%u\n", major(st_dev), minor(st_dev)); + else if (!(opt & OPT_q)) + printf("%s is %sa mountpoint\n", arg, ret?"":"not "); + return !ret; + } + } else { + if (!(opt & OPT_q)) + bb_error_msg("%s: not a directory", arg); + return EXIT_FAILURE; + } + } + if (!(opt & OPT_q)) + bb_perror_msg("%s", arg); + return EXIT_FAILURE; +} diff --git a/miscutils/mt.c b/miscutils/mt.c new file mode 100644 index 000000000..9ecec82a1 --- /dev/null +++ b/miscutils/mt.c @@ -0,0 +1,120 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + +struct mt_opcodes { + char *name; + short value; +}; + +/* missing: eod/seod, stoptions, stwrthreshold, densities */ +static const struct mt_opcodes opcodes[] = { + {"bsf", MTBSF}, + {"bsfm", MTBSFM}, + {"bsr", MTBSR}, + {"bss", MTBSS}, + {"datacompression", MTCOMPRESSION}, + {"eom", MTEOM}, + {"erase", MTERASE}, + {"fsf", MTFSF}, + {"fsfm", MTFSFM}, + {"fsr", MTFSR}, + {"fss", MTFSS}, + {"load", MTLOAD}, + {"lock", MTLOCK}, + {"mkpart", MTMKPART}, + {"nop", MTNOP}, + {"offline", MTOFFL}, + {"rewoffline", MTOFFL}, + {"ras1", MTRAS1}, + {"ras2", MTRAS2}, + {"ras3", MTRAS3}, + {"reset", MTRESET}, + {"retension", MTRETEN}, + {"rewind", MTREW}, + {"seek", MTSEEK}, + {"setblk", MTSETBLK}, + {"setdensity", MTSETDENSITY}, + {"drvbuffer", MTSETDRVBUFFER}, + {"setpart", MTSETPART}, + {"tell", MTTELL}, + {"wset", MTWSM}, + {"unload", MTUNLOAD}, + {"unlock", MTUNLOCK}, + {"eof", MTWEOF}, + {"weof", MTWEOF}, + {0, 0} +}; + +int mt_main(int argc, char **argv) +{ + const char *file = "/dev/tape"; + const struct mt_opcodes *code = opcodes; + struct mtop op; + struct mtpos position; + int fd, mode; + + if (argc < 2) { + bb_show_usage(); + } + + if (strcmp(argv[1], "-f") == 0) { + if (argc < 4) { + bb_show_usage(); + } + file = argv[2]; + argv += 2; + argc -= 2; + } + + while (code->name != 0) { + if (strcmp(code->name, argv[1]) == 0) + break; + code++; + } + + if (code->name == 0) { + bb_error_msg("unrecognized opcode %s", argv[1]); + return EXIT_FAILURE; + } + + op.mt_op = code->value; + if (argc >= 3) + op.mt_count = xatoi_u(argv[2]); + else + op.mt_count = 1; /* One, not zero, right? */ + + switch (code->value) { + case MTWEOF: + case MTERASE: + case MTWSM: + case MTSETDRVBUFFER: + mode = O_WRONLY; + break; + + default: + mode = O_RDONLY; + break; + } + + fd = xopen(file, mode); + + switch (code->value) { + case MTTELL: + if (ioctl(fd, MTIOCPOS, &position) < 0) + bb_perror_msg_and_die("%s", file); + printf("At block %d.\n", (int) position.mt_blkno); + break; + + default: + if (ioctl(fd, MTIOCTOP, &op) != 0) + bb_perror_msg_and_die("%s", file); + break; + } + + return EXIT_SUCCESS; +} diff --git a/miscutils/nmeter.c b/miscutils/nmeter.c new file mode 100644 index 000000000..b09877137 --- /dev/null +++ b/miscutils/nmeter.c @@ -0,0 +1,848 @@ +/* +** Licensed under the GPL v2, see the file LICENSE in this tarball +** +** Based on nanotop.c from floppyfw project +** +** Contact me: vda.linux@googlemail.com */ + +//TODO: +// simplify code +// /proc/locks +// /proc/stat: +// disk_io: (3,0):(22272,17897,410702,4375,54750) +// btime 1059401962 + +#include "busybox.h" +#include + +typedef unsigned long long ullong; + +enum { proc_file_size = 4096 }; + +typedef struct proc_file { + char *name; + int gen; + char *file; +} proc_file; + +static proc_file proc_stat = { "/proc/stat", -1 }; +static proc_file proc_loadavg = { "/proc/loadavg", -1 }; +static proc_file proc_net_dev = { "/proc/net/dev", -1 }; +static proc_file proc_meminfo = { "/proc/meminfo", -1 }; +static proc_file proc_diskstats = { "/proc/diskstats", -1 }; +// Sample # +static int gen = -1; +// Linux 2.6? (otherwise assumes 2.4) +static int is26 = 0; +static struct timeval tv; +static int delta = 1000000; +static int deltanz = 1000000; +static int need_seconds = 0; +static char *final_str = "\n"; + +// We depend on this being a char[], not char* - we take sizeof() of it +#define outbuf bb_common_bufsiz1 +static char *cur_outbuf = outbuf; + + +static inline void reset_outbuf(void) +{ + cur_outbuf = outbuf; +} + +static inline int outbuf_count(void) +{ + return cur_outbuf - outbuf; +} + +static void print_outbuf(void) +{ + int sz = cur_outbuf - outbuf; + if (sz > 0) { + write(1, outbuf, sz); + cur_outbuf = outbuf; + } +} + +static void put(const char *s) +{ + int sz = strlen(s); + if (sz > outbuf + sizeof(outbuf) - cur_outbuf) + sz = outbuf + sizeof(outbuf) - cur_outbuf; + memcpy(cur_outbuf, s, sz); + cur_outbuf += sz; +} + +static void put_c(char c) +{ + if (cur_outbuf < outbuf + sizeof(outbuf)) + *cur_outbuf++ = c; +} + +static void put_question_marks(int count) +{ + while (count--) + put_c('?'); +} + +static int readfile_z(char *buf, int sz, const char* fname) +{ + sz = open_read_close(fname, buf, sz-1); + if (sz < 0) { + buf[0] = '\0'; + return 1; + } + buf[sz] = '\0'; + return 0; +} + +static const char* get_file(proc_file *pf) +{ + if (pf->gen != gen) { + pf->gen = gen; + // We allocate proc_file_size bytes. This wastes memory, + // but allows us to allocate only once (at first sample) + // per proc file, and reuse buffer for each sample + if (!pf->file) + pf->file = (char*)xmalloc(proc_file_size); + readfile_z(pf->file, proc_file_size, pf->name); + } + return pf->file; +} + +static inline ullong read_after_slash(const char *p) +{ + p = strchr(p, '/'); + if (!p) return 0; + return strtoull(p+1, NULL, 10); +} + +enum conv_type { conv_decimal, conv_slash }; + +// Reads decimal values from line. Values start after key, for example: +// "cpu 649369 0 341297 4336769..." - key is "cpu" here. +// Values are stored in vec[]. arg_ptr has list of positions +// we are interested in: for example: 1,2,5 - we want 1st, 2nd and 5th value. +static int vrdval(const char* p, const char* key, + enum conv_type conv, ullong *vec, va_list arg_ptr) +{ + int indexline; + int indexnext; + + p = strstr(p, key); + if (!p) return 1; + + p += strlen(key); + indexline = 1; + indexnext = va_arg(arg_ptr, int); + while (1) { + while (*p == ' ' || *p == '\t') p++; + if (*p == '\n' || *p == '\0') break; + + if (indexline == indexnext) { // read this value + *vec++ = conv==conv_decimal ? + strtoull(p, NULL, 10) : + read_after_slash(p); + indexnext = va_arg(arg_ptr, int); + } + while (*p > ' ') p++; // skip over value + indexline++; + } + return 0; +} + +// Parses files with lines like "cpu0 21727 0 15718 1813856 9461 10485 0 0": +// rdval(file_contents, "string_to_find", result_vector, value#, value#...) +// value# start with 1 +static int rdval(const char* p, const char* key, ullong *vec, ...) +{ + va_list arg_ptr; + int result; + + va_start(arg_ptr, vec); + result = vrdval(p, key, conv_decimal, vec, arg_ptr); + va_end(arg_ptr); + + return result; +} + +// Parses files with lines like "... ... ... 3/148 ...." +static int rdval_loadavg(const char* p, ullong *vec, ...) +{ + va_list arg_ptr; + int result; + + va_start(arg_ptr, vec); + result = vrdval(p, "", conv_slash, vec, arg_ptr); + va_end(arg_ptr); + + return result; +} + +// Parses /proc/diskstats +// 1 2 3 4 5 6(rd) 7 8 9 10(wr) 11 12 13 14 +// 3 0 hda 51292 14441 841783 926052 25717 79650 843256 3029804 0 148459 3956933 +// 3 1 hda1 0 0 0 0 <- ignore if only 4 fields +static int rdval_diskstats(const char* p, ullong *vec) +{ + ullong rd = 0; // to avoid "warning: 'rd' might be used uninitialized" + int indexline = 0; + vec[0] = 0; + vec[1] = 0; + while (1) { + indexline++; + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') break; + if (*p == '\n') { + indexline = 0; + p++; + continue; + } + if (indexline == 6) { + rd = strtoull(p, NULL, 10); + } else if (indexline == 10) { + vec[0] += rd; // TODO: *sectorsize (don't know how to find out sectorsize) + vec[1] += strtoull(p, NULL, 10); + while (*p != '\n' && *p != '\0') p++; + continue; + } + while (*p > ' ') p++; // skip over value + } + return 0; +} + +static void scale(ullong ul) +{ + char buf[5]; + smart_ulltoa5(ul, buf); + put(buf); +} + + +#define S_STAT(a) \ +typedef struct a { \ + struct s_stat *next; \ + void (*collect)(struct a *s); \ + const char *label; +#define S_STAT_END(a) } a; + +S_STAT(s_stat) +S_STAT_END(s_stat) + +static void collect_literal(s_stat *s) +{ +} + +static s_stat* init_literal(void) +{ + s_stat *s = xmalloc(sizeof(s_stat)); + s->collect = collect_literal; + return (s_stat*)s; +} + +static s_stat* init_delay(const char *param) +{ + delta = strtol(param, NULL, 0)*1000; + deltanz = delta > 0 ? delta : 1; + need_seconds = (1000000%deltanz) != 0; + return (s_stat*)0; +} + +static s_stat* init_cr(const char *param) +{ + final_str = "\r"; + return (s_stat*)0; +} + + +// user nice system idle iowait irq softirq (last 3 only in 2.6) +//cpu 649369 0 341297 4336769 11640 7122 1183 +//cpuN 649369 0 341297 4336769 11640 7122 1183 +enum { CPU_FIELDCNT = 7 }; +S_STAT(cpu_stat) + ullong old[CPU_FIELDCNT]; + int bar_sz; + char *bar; +S_STAT_END(cpu_stat) + + +static void collect_cpu(cpu_stat *s) +{ + ullong data[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 }; + unsigned frac[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 }; + ullong all = 0; + int norm_all = 0; + int bar_sz = s->bar_sz; + char *bar = s->bar; + int i; + + if (rdval(get_file(&proc_stat), "cpu ", data, 1, 2, 3, 4, 5, 6, 7)) { + put_question_marks(bar_sz); + return; + } + + for (i=0; iold[i]; + if (data[i] < old) old = data[i]; //sanitize + s->old[i] = data[i]; + all += (data[i] -= old); + } + + if (all) { + for (i=0; i max) max = frac[i], pos = i; + } + frac[pos] = 0; //avoid bumping up same value twice + data[pos]++; + norm_all++; + } + + memset(bar, '.', bar_sz); + memset(bar, 'S', data[2]); bar += data[2]; //sys + memset(bar, 'U', data[0]); bar += data[0]; //usr + memset(bar, 'N', data[1]); bar += data[1]; //nice + memset(bar, 'D', data[4]); bar += data[4]; //iowait + memset(bar, 'I', data[5]); bar += data[5]; //irq + memset(bar, 'i', data[6]); bar += data[6]; //softirq + } else { + memset(bar, '?', bar_sz); + } + put(s->bar); +} + + +static s_stat* init_cpu(const char *param) +{ + int sz; + cpu_stat *s = xmalloc(sizeof(cpu_stat)); + s->collect = collect_cpu; + sz = strtol(param, NULL, 0); + if (sz < 10) sz = 10; + if (sz > 1000) sz = 1000; + s->bar = xmalloc(sz+1); + s->bar[sz] = '\0'; + s->bar_sz = sz; + return (s_stat*)s; +} + + +S_STAT(int_stat) + ullong old; + int no; +S_STAT_END(int_stat) + +static void collect_int(int_stat *s) +{ + ullong data[1]; + ullong old; + + if (rdval(get_file(&proc_stat), "intr", data, s->no)) { + put_question_marks(4); + return; + } + + old = s->old; + if (data[0] < old) old = data[0]; //sanitize + s->old = data[0]; + scale(data[0] - old); +} + +static s_stat* init_int(const char *param) +{ + int_stat *s = xmalloc(sizeof(int_stat)); + s->collect = collect_int; + if (param[0]=='\0') { + s->no = 1; + } else { + int n = strtoul(param, NULL, 0); + s->no = n+2; + } + return (s_stat*)s; +} + + +S_STAT(ctx_stat) + ullong old; +S_STAT_END(ctx_stat) + +static void collect_ctx(ctx_stat *s) +{ + ullong data[1]; + ullong old; + + if (rdval(get_file(&proc_stat), "ctxt", data, 1)) { + put_question_marks(4); + return; + } + + old = s->old; + if (data[0] < old) old = data[0]; //sanitize + s->old = data[0]; + scale(data[0] - old); +} + +static s_stat* init_ctx(const char *param) +{ + ctx_stat *s = xmalloc(sizeof(ctx_stat)); + s->collect = collect_ctx; + return (s_stat*)s; +} + + +S_STAT(blk_stat) + const char* lookfor; + ullong old[2]; +S_STAT_END(blk_stat) + +static void collect_blk(blk_stat *s) +{ + ullong data[2]; + int i; + + if (is26) { + i = rdval_diskstats(get_file(&proc_diskstats), data); + } else { + i = rdval(get_file(&proc_stat), s->lookfor, data, 1, 2); + // Linux 2.4 reports bio in Kbytes, convert to sectors: + data[0] *= 2; + data[1] *= 2; + } + if (i) { + put_question_marks(9); + return; + } + + for (i=0; i<2; i++) { + ullong old = s->old[i]; + if (data[i] < old) old = data[i]; //sanitize + s->old[i] = data[i]; + data[i] -= old; + } + scale(data[0]*512); // TODO: *sectorsize + put_c(' '); + scale(data[1]*512); +} + +static s_stat* init_blk(const char *param) +{ + blk_stat *s = xmalloc(sizeof(blk_stat)); + s->collect = collect_blk; + s->lookfor = "page"; + return (s_stat*)s; +} + + +S_STAT(fork_stat) + ullong old; +S_STAT_END(fork_stat) + +static void collect_thread_nr(fork_stat *s) +{ + ullong data[1]; + + if (rdval_loadavg(get_file(&proc_loadavg), data, 4)) { + put_question_marks(4); + return; + } + scale(data[0]); +} + +static void collect_fork(fork_stat *s) +{ + ullong data[1]; + ullong old; + + if (rdval(get_file(&proc_stat), "processes", data, 1)) { + put_question_marks(4); + return; + } + + old = s->old; + if (data[0] < old) old = data[0]; //sanitize + s->old = data[0]; + scale(data[0] - old); +} + +static s_stat* init_fork(const char *param) +{ + fork_stat *s = xmalloc(sizeof(fork_stat)); + if (*param == 'n') { + s->collect = collect_thread_nr; + } else { + s->collect = collect_fork; + } + return (s_stat*)s; +} + + +S_STAT(if_stat) + ullong old[4]; + const char *device; + char *device_colon; +S_STAT_END(if_stat) + +static void collect_if(if_stat *s) +{ + ullong data[4]; + int i; + + if (rdval(get_file(&proc_net_dev), s->device_colon, data, 1, 3, 9, 11)) { + put_question_marks(10); + return; + } + + for (i=0; i<4; i++) { + ullong old = s->old[i]; + if (data[i] < old) old = data[i]; //sanitize + s->old[i] = data[i]; + data[i] -= old; + } + put_c(data[1] ? '*' : ' '); + scale(data[0]); + put_c(data[3] ? '*' : ' '); + scale(data[2]); +} + +static s_stat* init_if(const char *device) +{ + if_stat *s = xmalloc(sizeof(if_stat)); + + if (!device || !device[0]) + bb_show_usage(); + s->collect = collect_if; + + s->device = device; + s->device_colon = xmalloc(strlen(device)+2); + strcpy(s->device_colon, device); + strcat(s->device_colon, ":"); + return (s_stat*)s; +} + + +S_STAT(mem_stat) + char opt; +S_STAT_END(mem_stat) + +// "Memory" value should not include any caches. +// IOW: neither "ls -laR /" nor heavy read/write activity +// should affect it. We'd like to also include any +// long-term allocated kernel-side mem, but it is hard +// to figure out. For now, bufs, cached & slab are +// counted as "free" memory +//2.6.16: +//MemTotal: 773280 kB +//MemFree: 25912 kB - genuinely free +//Buffers: 320672 kB - cache +//Cached: 146396 kB - cache +//SwapCached: 0 kB +//Active: 183064 kB +//Inactive: 356892 kB +//HighTotal: 0 kB +//HighFree: 0 kB +//LowTotal: 773280 kB +//LowFree: 25912 kB +//SwapTotal: 131064 kB +//SwapFree: 131064 kB +//Dirty: 48 kB +//Writeback: 0 kB +//Mapped: 96620 kB +//Slab: 200668 kB - takes 7 Mb on my box fresh after boot, +// but includes dentries and inodes +// (== can take arbitrary amount of mem) +//CommitLimit: 517704 kB +//Committed_AS: 236776 kB +//PageTables: 1248 kB +//VmallocTotal: 516052 kB +//VmallocUsed: 3852 kB +//VmallocChunk: 512096 kB +//HugePages_Total: 0 +//HugePages_Free: 0 +//Hugepagesize: 4096 kB +static void collect_mem(mem_stat *s) +{ + ullong m_total = 0; + ullong m_free = 0; + ullong m_bufs = 0; + ullong m_cached = 0; + ullong m_slab = 0; + + if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1)) { + put_question_marks(4); + return; + } + if (s->opt == 'f') { + scale(m_total << 10); + return; + } + + if (rdval(proc_meminfo.file, "MemFree:", &m_free , 1) + || rdval(proc_meminfo.file, "Buffers:", &m_bufs , 1) + || rdval(proc_meminfo.file, "Cached:", &m_cached, 1) + || rdval(proc_meminfo.file, "Slab:", &m_slab , 1) + ) { + put_question_marks(4); + return; + } + + m_free += m_bufs + m_cached + m_slab; + switch(s->opt) { + case 'f': + scale(m_free << 10); break; + default: + scale((m_total - m_free) << 10); break; + } +} + +static s_stat* init_mem(const char *param) +{ + mem_stat *s = xmalloc(sizeof(mem_stat)); + s->collect = collect_mem; + s->opt = param[0]; + return (s_stat*)s; +} + + +S_STAT(swp_stat) +S_STAT_END(swp_stat) + +static void collect_swp(swp_stat *s) +{ + ullong s_total[1]; + ullong s_free[1]; + if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1) + || rdval(proc_meminfo.file, "SwapFree:" , s_free, 1) + ) { + put_question_marks(4); + return; + } + scale((s_total[0]-s_free[0]) << 10); +} + +static s_stat* init_swp(const char *param) +{ + swp_stat *s = xmalloc(sizeof(swp_stat)); + s->collect = collect_swp; + return (s_stat*)s; +} + + +S_STAT(fd_stat) +S_STAT_END(fd_stat) + +static void collect_fd(fd_stat *s) +{ + char file[4096]; + ullong data[2]; + + readfile_z(file, sizeof(file), "/proc/sys/fs/file-nr"); + if (rdval(file, "", data, 1, 2)) { + put_question_marks(4); + return; + } + + scale(data[0] - data[1]); +} + +static s_stat* init_fd(const char *param) +{ + fd_stat *s = xmalloc(sizeof(fd_stat)); + s->collect = collect_fd; + return (s_stat*)s; +} + + +S_STAT(time_stat) + int prec; + int scale; +S_STAT_END(time_stat) + +static void collect_time(time_stat *s) +{ + char buf[sizeof("12:34:56.123456")]; + struct tm* tm; + int us = tv.tv_usec + s->scale/2; + time_t t = tv.tv_sec; + + if (us >= 1000000) { + t++; + us -= 1000000; + } + tm = localtime(&t); + + sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); + if (s->prec) + sprintf(buf+8, ".%0*d", s->prec, us / s->scale); + put(buf); +} + +static s_stat* init_time(const char *param) +{ + int prec; + time_stat *s = xmalloc(sizeof(time_stat)); + + s->collect = collect_time; + prec = param[0]-'0'; + if (prec < 0) prec = 0; + else if (prec > 6) prec = 6; + s->prec = prec; + s->scale = 1; + while (prec++ < 6) + s->scale *= 10; + return (s_stat*)s; +} + +static void collect_info(s_stat *s) +{ + gen++; + while (s) { + put(s->label); + s->collect(s); + s = s->next; + } +} + + +typedef s_stat* init_func(const char *param); + +static const char options[] = "ncmsfixptbdr"; +static init_func* init_functions[] = { + init_if, + init_cpu, + init_mem, + init_swp, + init_fd, + init_int, + init_ctx, + init_fork, + init_time, + init_blk, + init_delay, + init_cr, +}; + +int nmeter_main(int argc, char* argv[]) +{ + char buf[32]; + s_stat *first = NULL; + s_stat *last = NULL; + s_stat *s; + char *cur, *prev; + + if (argc != 2) + bb_show_usage(); + + if (open_read_close("/proc/version", buf, sizeof(buf)) > 0) + is26 = (strstr(buf, " 2.4.")==NULL); + + // Can use argv[1] directly, but this will mess up + // parameters as seen by e.g. ps. Making a copy... + cur = xstrdup(argv[1]); + while (1) { + char *param, *p; + prev = cur; +again: + cur = strchr(cur, '%'); + if (!cur) + break; + if (cur[1]=='%') { // %% + strcpy(cur, cur+1); + cur++; + goto again; + } + *cur++ = '\0'; // overwrite % + if (cur[0] == '[') { + // format: %[foptstring] + cur++; + p = strchr(options, cur[0]); + param = cur+1; + while (cur[0] != ']') { + if (!cur[0]) + bb_show_usage(); + cur++; + } + *cur++ = '\0'; // overwrite [ + } else { + // format: %NNNNNNf + param = cur; + while (cur[0] >= '0' && cur[0] <= '9') + cur++; + if (!cur[0]) + bb_show_usage(); + p = strchr(options, cur[0]); + *cur++ = '\0'; // overwrite format char + } + if (!p) + bb_show_usage(); + s = init_functions[p-options](param); + if (s) { + s->label = prev; + s->next = 0; + if (!first) + first = s; + else + last->next = s; + last = s; + } else { + // %NNNNd or %r option. remove it from string + strcpy(prev + strlen(prev), cur); + cur = prev; + } + } + if (prev[0]) { + s = init_literal(); + s->label = prev; + s->next = 0; + if (!first) + first = s; + else + last->next = s; + last = s; + } + + // Generate first samples but do not print them, they're bogus + collect_info(first); + reset_outbuf(); + if (delta >= 0) { + gettimeofday(&tv, 0); + usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz); + } + + while (1) { + gettimeofday(&tv, 0); + collect_info(first); + put(final_str); + print_outbuf(); + + // Negative delta -> no usleep at all + // This will hog the CPU but you can have REALLY GOOD + // time resolution ;) + // TODO: detect and avoid useless updates + // (like: nothing happens except time) + if (delta >= 0) { + int rem; + // can be commented out, will sacrifice sleep time precision a bit + gettimeofday(&tv, 0); + if (need_seconds) + rem = delta - ((ullong)tv.tv_sec*1000000+tv.tv_usec)%deltanz; + else + rem = delta - tv.tv_usec%deltanz; + // Sometimes kernel wakes us up just a tiny bit earlier than asked + // Do not go to very short sleep in this case + if (rem < delta/128) { + rem += delta; + } + usleep(rem); + } + } + + return 0; +} diff --git a/miscutils/raidautorun.c b/miscutils/raidautorun.c new file mode 100644 index 000000000..e5bce9a92 --- /dev/null +++ b/miscutils/raidautorun.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * raidautorun implementation for busybox + * + * Copyright (C) 2006 Bernhard Fischer + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + */ + +#include "busybox.h" + +#include +#include + +int raidautorun_main(int argc, char **argv) +{ + if (argc != 2) + bb_show_usage(); + + if (ioctl(xopen(argv[1], O_RDONLY), RAID_AUTORUN, NULL) != 0) { + bb_perror_msg_and_die("ioctl"); + } + + return EXIT_SUCCESS; +} diff --git a/miscutils/readahead.c b/miscutils/readahead.c new file mode 100644 index 000000000..356c40451 --- /dev/null +++ b/miscutils/readahead.c @@ -0,0 +1,34 @@ +/* vi: set sw=4 ts=4: */ +/* + * readahead implementation for busybox + * + * Preloads the given files in RAM, to reduce access time. + * Does this by calling the readahead(2) system call. + * + * Copyright (C) 2006 Michael Opdenacker + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ + +#include "busybox.h" + +int readahead_main(int argc, char **argv) +{ + FILE *f; + int retval = EXIT_SUCCESS; + + if (argc == 1) bb_show_usage(); + + while (*++argv) { + if ((f = fopen_or_warn(*argv, "r")) != NULL) { + int r, fd=fileno(f); + + r = readahead(fd, 0, fdlength(fd)); + fclose(f); + if (r >= 0) continue; + } + retval = EXIT_FAILURE; + } + + return retval; +} diff --git a/miscutils/runlevel.c b/miscutils/runlevel.c new file mode 100644 index 000000000..91d49fa55 --- /dev/null +++ b/miscutils/runlevel.c @@ -0,0 +1,42 @@ +/* vi: set sw=4 ts=4: */ +/* + * runlevel Prints out the previous and the current runlevel. + * + * Version: @(#)runlevel 1.20 16-Apr-1997 MvS + * + * This file is part of the sysvinit suite, + * Copyright 1991-1997 Miquel van Smoorenburg. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * initially busyboxified by Bernhard Fischer + */ + +#include "busybox.h" +#include +#include +#include +#include + +int runlevel_main(int argc, char *argv[]) +{ + struct utmp *ut; + char prev; + + if (argc > 1) utmpname(argv[1]); + + setutent(); + while ((ut = getutent()) != NULL) { + if (ut->ut_type == RUN_LVL) { + prev = ut->ut_pid / 256; + if (prev == 0) prev = 'N'; + printf("%c %c\n", prev, ut->ut_pid % 256); + endutent(); + return 0; + } + } + + puts("unknown"); + endutent(); + return 1; +} diff --git a/miscutils/rx.c b/miscutils/rx.c new file mode 100644 index 000000000..9b9f6afd4 --- /dev/null +++ b/miscutils/rx.c @@ -0,0 +1,290 @@ +/* vi: set sw=4 ts=4: */ +/*------------------------------------------------------------------------- + * Filename: xmodem.c + * Version: $Id: rx.c,v 1.2 2004/03/15 08:28:46 andersen Exp $ + * Copyright: Copyright (C) 2001, Hewlett-Packard Company + * Author: Christopher Hoover + * Description: xmodem functionality for uploading of kernels + * and the like + * Created at: Thu Dec 20 01:58:08 PST 2001 + *-----------------------------------------------------------------------*/ +/* + * xmodem.c: xmodem functionality for uploading of kernels and + * the like + * + * Copyright (C) 2001 Hewlett-Packard Laboratories + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * This was originally written for blob and then adapted for busybox. + * + */ + +#include "busybox.h" + +#define SOH 0x01 +#define STX 0x02 +#define EOT 0x04 +#define ACK 0x06 +#define NAK 0x15 +#define BS 0x08 + +/* + +Cf: + + http://www.textfiles.com/apple/xmodem + http://www.phys.washington.edu/~belonis/xmodem/docxmodem.txt + http://www.phys.washington.edu/~belonis/xmodem/docymodem.txt + http://www.phys.washington.edu/~belonis/xmodem/modmprot.col + +*/ + +#define TIMEOUT 1 +#define TIMEOUT_LONG 10 +#define MAXERRORS 10 + +static int read_byte(int fd, unsigned int timeout) { + char buf[1]; + int n; + + alarm(timeout); + + n = read(fd, &buf, 1); + + alarm(0); + + if (n == 1) + return buf[0] & 0xff; + else + return -1; +} + +static int receive(char *error_buf, size_t error_buf_size, + int ttyfd, int filefd) +{ + char blockBuf[1024]; + unsigned int errors = 0; + unsigned int wantBlockNo = 1; + unsigned int length = 0; + int docrc = 1; + char nak = 'C'; + unsigned int timeout = TIMEOUT_LONG; + +#define note_error(fmt,args...) \ + snprintf(error_buf, error_buf_size, fmt,##args) + + /* Flush pending input */ + tcflush(ttyfd, TCIFLUSH); + + /* Ask for CRC; if we get errors, we will go with checksum */ + write(ttyfd, &nak, 1); + + for (;;) { + int blockBegin; + int blockNo, blockNoOnesCompl; + int blockLength; + int cksum = 0; + int crcHi = 0; + int crcLo = 0; + + blockBegin = read_byte(ttyfd, timeout); + if (blockBegin < 0) + goto timeout; + + timeout = TIMEOUT; + nak = NAK; + + switch (blockBegin) { + case SOH: + case STX: + break; + + case EOT: + nak = ACK; + write(ttyfd, &nak, 1); + goto done; + + default: + goto error; + } + + /* block no */ + blockNo = read_byte(ttyfd, TIMEOUT); + if (blockNo < 0) + goto timeout; + + /* block no one's compliment */ + blockNoOnesCompl = read_byte(ttyfd, TIMEOUT); + if (blockNoOnesCompl < 0) + goto timeout; + + if (blockNo != (255 - blockNoOnesCompl)) { + note_error("bad block ones compl"); + goto error; + } + + blockLength = (blockBegin == SOH) ? 128 : 1024; + + { + int i; + + for (i = 0; i < blockLength; i++) { + int cc = read_byte(ttyfd, TIMEOUT); + if (cc < 0) + goto timeout; + blockBuf[i] = cc; + } + } + + if (docrc) { + crcHi = read_byte(ttyfd, TIMEOUT); + if (crcHi < 0) + goto timeout; + + crcLo = read_byte(ttyfd, TIMEOUT); + if (crcLo < 0) + goto timeout; + } else { + cksum = read_byte(ttyfd, TIMEOUT); + if (cksum < 0) + goto timeout; + } + + if (blockNo == ((wantBlockNo - 1) & 0xff)) { + /* a repeat of the last block is ok, just ignore it. */ + /* this also ignores the initial block 0 which is */ + /* meta data. */ + goto next; + } else if (blockNo != (wantBlockNo & 0xff)) { + note_error("unexpected block no, 0x%08x, expecting 0x%08x", blockNo, wantBlockNo); + goto error; + } + + if (docrc) { + int crc = 0; + int i, j; + int expectedCrcHi; + int expectedCrcLo; + + for (i = 0; i < blockLength; i++) { + crc = crc ^ (int) blockBuf[i] << 8; + for (j = 0; j < 8; j++) + if (crc & 0x8000) + crc = crc << 1 ^ 0x1021; + else + crc = crc << 1; + } + + expectedCrcHi = (crc >> 8) & 0xff; + expectedCrcLo = crc & 0xff; + + if ((crcHi != expectedCrcHi) || + (crcLo != expectedCrcLo)) { + note_error("crc error, expected 0x%02x 0x%02x, got 0x%02x 0x%02x", expectedCrcHi, expectedCrcLo, crcHi, crcLo); + goto error; + } + } else { + unsigned char expectedCksum = 0; + int i; + + for (i = 0; i < blockLength; i++) + expectedCksum += blockBuf[i]; + + if (cksum != expectedCksum) { + note_error("checksum error, expected 0x%02x, got 0x%02x", expectedCksum, cksum); + goto error; + } + } + + wantBlockNo++; + length += blockLength; + + if (full_write(filefd, blockBuf, blockLength) < 0) { + note_error("write to file failed: %m"); + goto fatal; + } + + next: + errors = 0; + nak = ACK; + write(ttyfd, &nak, 1); + continue; + + error: + timeout: + errors++; + if (errors == MAXERRORS) { + /* Abort */ + + // if using crc, try again w/o crc + if (nak == 'C') { + nak = NAK; + errors = 0; + docrc = 0; + goto timeout; + } + + note_error("too many errors; giving up"); + + fatal: + /* 5 CAN followed by 5 BS */ + write(ttyfd, "\030\030\030\030\030\010\010\010\010\010", 10); + return -1; + } + + /* Flush pending input */ + tcflush(ttyfd, TCIFLUSH); + + write(ttyfd, &nak, 1); + } + + done: + return length; + +#undef note_error +} + +static void sigalrm_handler(int ATTRIBUTE_UNUSED signum) +{ +} + +int rx_main(int argc, char **argv) +{ + char *fn; + int ttyfd, filefd; + struct termios tty, orig_tty; + struct sigaction act; + int n; + char error_buf[256]; + + if (argc != 2) + bb_show_usage(); + + fn = argv[1]; + ttyfd = xopen(CURRENT_TTY, O_RDWR); + filefd = xopen(fn, O_RDWR|O_CREAT|O_TRUNC); + + if (tcgetattr(ttyfd, &tty) < 0) + bb_perror_msg_and_die("tcgetattr"); + + orig_tty = tty; + + cfmakeraw(&tty); + tcsetattr(ttyfd, TCSAFLUSH, &tty); + + memset(&act, 0, sizeof(act)); + act.sa_handler = sigalrm_handler; + sigaction(SIGALRM, &act, 0); + + n = receive(error_buf, sizeof(error_buf), ttyfd, filefd); + + close(filefd); + + tcsetattr(ttyfd, TCSAFLUSH, &orig_tty); + + if (n < 0) + bb_error_msg_and_die("\nreceive failed:\n %s", error_buf); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/miscutils/setsid.c b/miscutils/setsid.c new file mode 100644 index 000000000..347b2babd --- /dev/null +++ b/miscutils/setsid.c @@ -0,0 +1,44 @@ +/* vi: set sw=4 ts=4: */ +/* + * setsid.c -- execute a command in a new session + * Rick Sladkey + * In the public domain. + * + * 1999-02-22 Arkadiusz Mi¶kiewicz + * - added Native Language Support + * + * 2001-01-18 John Fremlin + * - fork in case we are process group leader + * + * 2004-11-12 Paul Fox + * - busyboxed + */ + +#include "busybox.h" +#include +#include +#include + +int setsid_main(int argc, char *argv[]) +{ + if (argc < 2) + bb_show_usage(); + + if (getpgrp() == getpid()) { + switch (fork()){ + case -1: + bb_perror_msg_and_die("fork"); + case 0: + break; + default: /* parent */ + exit(0); + } + /* child falls through */ + } + + setsid(); /* no error possible */ + + execvp(argv[1], argv + 1); + + bb_perror_msg_and_die("%s", argv[1]); +} diff --git a/miscutils/strings.c b/miscutils/strings.c new file mode 100644 index 000000000..0d5576e9b --- /dev/null +++ b/miscutils/strings.c @@ -0,0 +1,88 @@ +/* vi: set sw=4 ts=4: */ +/* + * strings implementation for busybox + * + * Copyright Tito Ragusa + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include +#include +#include +#include + +#define WHOLE_FILE 1 +#define PRINT_NAME 2 +#define PRINT_OFFSET 4 +#define SIZE 8 + +int strings_main(int argc, char **argv) +{ + int n, c, i = 0, status = EXIT_SUCCESS; + unsigned opt; + unsigned long count; + FILE *file = stdin; + char *string; + const char *fmt = "%s: "; + char *n_arg = "4"; + + opt = getopt32(argc, argv, "afon:", &n_arg); + /* -a is our default behaviour */ + + argc -= optind; + argv += optind; + + n = xatoul_range(n_arg, 1, INT_MAX); + string = xzalloc(n + 1); + n--; + + if (argc == 0) { + fmt = "{%s}: "; + *argv = (char *)bb_msg_standard_input; + goto PIPE; + } + + do { + file = fopen_or_warn(*argv, "r"); + if (file) { +PIPE: + count = 0; + do { + c = fgetc(file); + if (isprint(c) || c == '\t') { + if (i <= n) { + string[i] = c; + } else { + putchar(c); + } + if (i == n) { + if (opt & PRINT_NAME) { + printf(fmt, *argv); + } + if (opt & PRINT_OFFSET) { + printf("%7lo ", count - n); + } + printf("%s", string); + } + i++; + } else { + if (i > n) { + putchar('\n'); + } + i = 0; + } + count++; + } while (c != EOF); + fclose_if_not_stdin(file); + } else { + status = EXIT_FAILURE; + } + } while (--argc > 0); + + if (ENABLE_FEATURE_CLEAN_UP) + free(string); + + fflush_stdout_and_exit(status); +} diff --git a/miscutils/taskset.c b/miscutils/taskset.c new file mode 100644 index 000000000..4496aa5b4 --- /dev/null +++ b/miscutils/taskset.c @@ -0,0 +1,96 @@ +/* vi: set sw=4 ts=4: */ +/* + * taskset - retrieve or set a processes' CPU affinity + * Copyright (c) 2006 Bernhard Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include /* optind */ + +#if ENABLE_FEATURE_TASKSET_FANCY +#define TASKSET_PRINTF_MASK "%s" +#define from_cpuset(x) __from_cpuset(&x) +/* craft a string from the mask */ +static char *__from_cpuset(cpu_set_t *mask) { + int i; + char *ret = 0, *str = xzalloc(9); + + for (i = CPU_SETSIZE - 4; i >= 0; i -= 4) { + char val = 0; + int off; + for (off = 0; off <= 3; ++off) + if (CPU_ISSET(i+off, mask)) + val |= 1< */ + aff = p_opt; + p_opt = argv[optind]; + } + argv += optind; /* me -p */ + pid = xatoul_range(p_opt, 1, ULONG_MAX); /* -p */ + } else + aff = *++argv; /* */ + if (aff) { + unsigned i = 0; + unsigned long l = xstrtol_range(aff, 16, 1, ULONG_MAX); + + CPU_ZERO(&new_mask); + while (i < CPU_SETSIZE && l >= (1<. + Heavily modified by David MacKenzie . + Heavily modified for busybox by Erik Andersen +*/ + +#include "busybox.h" + +#define TV_MSEC tv_usec / 1000 + +/* Information on the resources used by a child process. */ +typedef struct { + int waitstatus; + struct rusage ru; + struct timeval start, elapsed; /* Wallclock time of process. */ +} resource_t; + +/* msec = milliseconds = 1/1,000 (1*10e-3) second. + usec = microseconds = 1/1,000,000 (1*10e-6) second. */ + +#ifndef TICKS_PER_SEC +#define TICKS_PER_SEC 100 +#endif + +/* The number of milliseconds in one `tick' used by the `rusage' structure. */ +#define MSEC_PER_TICK (1000 / TICKS_PER_SEC) + +/* Return the number of clock ticks that occur in M milliseconds. */ +#define MSEC_TO_TICKS(m) ((m) / MSEC_PER_TICK) + +#define UL unsigned long + +static const char *const default_format = "real\t%E\nuser\t%u\nsys\t%T"; + +/* The output format for the -p option .*/ +static const char *const posix_format = "real %e\nuser %U\nsys %S"; + + +/* Format string for printing all statistics verbosely. + Keep this output to 24 lines so users on terminals can see it all.*/ +static const char *const long_format = + "\tCommand being timed: \"%C\"\n" + "\tUser time (seconds): %U\n" + "\tSystem time (seconds): %S\n" + "\tPercent of CPU this job got: %P\n" + "\tElapsed (wall clock) time (h:mm:ss or m:ss): %E\n" + "\tAverage shared text size (kbytes): %X\n" + "\tAverage unshared data size (kbytes): %D\n" + "\tAverage stack size (kbytes): %p\n" + "\tAverage total size (kbytes): %K\n" + "\tMaximum resident set size (kbytes): %M\n" + "\tAverage resident set size (kbytes): %t\n" + "\tMajor (requiring I/O) page faults: %F\n" + "\tMinor (reclaiming a frame) page faults: %R\n" + "\tVoluntary context switches: %w\n" + "\tInvoluntary context switches: %c\n" + "\tSwaps: %W\n" + "\tFile system inputs: %I\n" + "\tFile system outputs: %O\n" + "\tSocket messages sent: %s\n" + "\tSocket messages received: %r\n" + "\tSignals delivered: %k\n" + "\tPage size (bytes): %Z\n" "\tExit status: %x"; + + + /* Wait for and fill in data on child process PID. + Return 0 on error, 1 if ok. */ + +/* pid_t is short on BSDI, so don't try to promote it. */ +static int resuse_end(pid_t pid, resource_t * resp) +{ + int status; + + pid_t caught; + + /* Ignore signals, but don't ignore the children. When wait3 + returns the child process, set the time the command finished. */ + while ((caught = wait3(&status, 0, &resp->ru)) != pid) { + if (caught == -1) + return 0; + } + + gettimeofday(&resp->elapsed, (struct timezone *) 0); + resp->elapsed.tv_sec -= resp->start.tv_sec; + if (resp->elapsed.tv_usec < resp->start.tv_usec) { + /* Manually carry a one from the seconds field. */ + resp->elapsed.tv_usec += 1000000; + --resp->elapsed.tv_sec; + } + resp->elapsed.tv_usec -= resp->start.tv_usec; + + resp->waitstatus = status; + + return 1; +} + +/* Print ARGV to FP, with each entry in ARGV separated by FILLER. */ +static void fprintargv(FILE * fp, char *const *argv, const char *filler) +{ + char *const *av; + + av = argv; + fputs(*av, fp); + while (*++av) { + fputs(filler, fp); + fputs(*av, fp); + } + if (ferror(fp)) + bb_error_msg_and_die(bb_msg_write_error); +} + +/* Return the number of kilobytes corresponding to a number of pages PAGES. + (Actually, we use it to convert pages*ticks into kilobytes*ticks.) + + Try to do arithmetic so that the risk of overflow errors is minimized. + This is funky since the pagesize could be less than 1K. + Note: Some machines express getrusage statistics in terms of K, + others in terms of pages. */ + +static unsigned long ptok(unsigned long pages) +{ + static unsigned long ps = 0; + unsigned long tmp; + static long size = LONG_MAX; + + /* Initialization. */ + if (ps == 0) + ps = (long) getpagesize(); + + /* Conversion. */ + if (pages > (LONG_MAX / ps)) { /* Could overflow. */ + tmp = pages / 1024; /* Smaller first, */ + size = tmp * ps; /* then larger. */ + } else { /* Could underflow. */ + tmp = pages * ps; /* Larger first, */ + size = tmp / 1024; /* then smaller. */ + } + return size; +} + +/* summarize: Report on the system use of a command. + + Copy the FMT argument to FP except that `%' sequences + have special meaning, and `\n' and `\t' are translated into + newline and tab, respectively, and `\\' is translated into `\'. + + The character following a `%' can be: + (* means the tcsh time builtin also recognizes it) + % == a literal `%' + C == command name and arguments +* D == average unshared data size in K (ru_idrss+ru_isrss) +* E == elapsed real (wall clock) time in [hour:]min:sec +* F == major page faults (required physical I/O) (ru_majflt) +* I == file system inputs (ru_inblock) +* K == average total mem usage (ru_idrss+ru_isrss+ru_ixrss) +* M == maximum resident set size in K (ru_maxrss) +* O == file system outputs (ru_oublock) +* P == percent of CPU this job got (total cpu time / elapsed time) +* R == minor page faults (reclaims; no physical I/O involved) (ru_minflt) +* S == system (kernel) time (seconds) (ru_stime) +* T == system time in [hour:]min:sec +* U == user time (seconds) (ru_utime) +* u == user time in [hour:]min:sec +* W == times swapped out (ru_nswap) +* X == average amount of shared text in K (ru_ixrss) + Z == page size +* c == involuntary context switches (ru_nivcsw) + e == elapsed real time in seconds +* k == signals delivered (ru_nsignals) + p == average unshared stack size in K (ru_isrss) +* r == socket messages received (ru_msgrcv) +* s == socket messages sent (ru_msgsnd) + t == average resident set size in K (ru_idrss) +* w == voluntary context switches (ru_nvcsw) + x == exit status of command + + Various memory usages are found by converting from page-seconds + to kbytes by multiplying by the page size, dividing by 1024, + and dividing by elapsed real time. + + FP is the stream to print to. + FMT is the format string, interpreted as described above. + COMMAND is the command and args that are being summarized. + RESP is resource information on the command. */ + +static void summarize(FILE * fp, const char *fmt, char **command, + resource_t * resp) +{ + unsigned long r; /* Elapsed real milliseconds. */ + unsigned long v; /* Elapsed virtual (CPU) milliseconds. */ + + if (WIFSTOPPED(resp->waitstatus)) + fprintf(fp, "Command stopped by signal %d\n", + WSTOPSIG(resp->waitstatus)); + else if (WIFSIGNALED(resp->waitstatus)) + fprintf(fp, "Command terminated by signal %d\n", + WTERMSIG(resp->waitstatus)); + else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus)) + fprintf(fp, "Command exited with non-zero status %d\n", + WEXITSTATUS(resp->waitstatus)); + + /* Convert all times to milliseconds. Occasionally, one of these values + comes out as zero. Dividing by zero causes problems, so we first + check the time value. If it is zero, then we take `evasive action' + instead of calculating a value. */ + + r = resp->elapsed.tv_sec * 1000 + resp->elapsed.tv_usec / 1000; + + v = resp->ru.ru_utime.tv_sec * 1000 + resp->ru.ru_utime.TV_MSEC + + resp->ru.ru_stime.tv_sec * 1000 + resp->ru.ru_stime.TV_MSEC; + + while (*fmt) { + switch (*fmt) { + case '%': + switch (*++fmt) { + case '%': /* Literal '%'. */ + putc('%', fp); + break; + case 'C': /* The command that got timed. */ + fprintargv(fp, command, " "); + break; + case 'D': /* Average unshared data size. */ + fprintf(fp, "%lu", + MSEC_TO_TICKS(v) == 0 ? 0 : + ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) + + ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v)); + break; + case 'E': /* Elapsed real (wall clock) time. */ + if (resp->elapsed.tv_sec >= 3600) /* One hour -> h:m:s. */ + fprintf(fp, "%ldh %ldm %02lds", + resp->elapsed.tv_sec / 3600, + (resp->elapsed.tv_sec % 3600) / 60, + resp->elapsed.tv_sec % 60); + else + fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */ + resp->elapsed.tv_sec / 60, + resp->elapsed.tv_sec % 60, + resp->elapsed.tv_usec / 10000); + break; + case 'F': /* Major page faults. */ + fprintf(fp, "%ld", resp->ru.ru_majflt); + break; + case 'I': /* Inputs. */ + fprintf(fp, "%ld", resp->ru.ru_inblock); + break; + case 'K': /* Average mem usage == data+stack+text. */ + fprintf(fp, "%lu", + MSEC_TO_TICKS(v) == 0 ? 0 : + ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) + + ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v) + + ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v)); + break; + case 'M': /* Maximum resident set size. */ + fprintf(fp, "%lu", ptok((UL) resp->ru.ru_maxrss)); + break; + case 'O': /* Outputs. */ + fprintf(fp, "%ld", resp->ru.ru_oublock); + break; + case 'P': /* Percent of CPU this job got. */ + /* % cpu is (total cpu time)/(elapsed time). */ + if (r > 0) + fprintf(fp, "%lu%%", (v * 100 / r)); + else + fprintf(fp, "?%%"); + break; + case 'R': /* Minor page faults (reclaims). */ + fprintf(fp, "%ld", resp->ru.ru_minflt); + break; + case 'S': /* System time. */ + fprintf(fp, "%ld.%02ld", + resp->ru.ru_stime.tv_sec, + resp->ru.ru_stime.TV_MSEC / 10); + break; + case 'T': /* System time. */ + if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */ + fprintf(fp, "%ldh %ldm %02lds", + resp->ru.ru_stime.tv_sec / 3600, + (resp->ru.ru_stime.tv_sec % 3600) / 60, + resp->ru.ru_stime.tv_sec % 60); + else + fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */ + resp->ru.ru_stime.tv_sec / 60, + resp->ru.ru_stime.tv_sec % 60, + resp->ru.ru_stime.tv_usec / 10000); + break; + case 'U': /* User time. */ + fprintf(fp, "%ld.%02ld", + resp->ru.ru_utime.tv_sec, + resp->ru.ru_utime.TV_MSEC / 10); + break; + case 'u': /* User time. */ + if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */ + fprintf(fp, "%ldh %ldm %02lds", + resp->ru.ru_utime.tv_sec / 3600, + (resp->ru.ru_utime.tv_sec % 3600) / 60, + resp->ru.ru_utime.tv_sec % 60); + else + fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */ + resp->ru.ru_utime.tv_sec / 60, + resp->ru.ru_utime.tv_sec % 60, + resp->ru.ru_utime.tv_usec / 10000); + break; + case 'W': /* Times swapped out. */ + fprintf(fp, "%ld", resp->ru.ru_nswap); + break; + case 'X': /* Average shared text size. */ + fprintf(fp, "%lu", + MSEC_TO_TICKS(v) == 0 ? 0 : + ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v)); + break; + case 'Z': /* Page size. */ + fprintf(fp, "%d", getpagesize()); + break; + case 'c': /* Involuntary context switches. */ + fprintf(fp, "%ld", resp->ru.ru_nivcsw); + break; + case 'e': /* Elapsed real time in seconds. */ + fprintf(fp, "%ld.%02ld", + resp->elapsed.tv_sec, resp->elapsed.tv_usec / 10000); + break; + case 'k': /* Signals delivered. */ + fprintf(fp, "%ld", resp->ru.ru_nsignals); + break; + case 'p': /* Average stack segment. */ + fprintf(fp, "%lu", + MSEC_TO_TICKS(v) == 0 ? 0 : + ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v)); + break; + case 'r': /* Incoming socket messages received. */ + fprintf(fp, "%ld", resp->ru.ru_msgrcv); + break; + case 's': /* Outgoing socket messages sent. */ + fprintf(fp, "%ld", resp->ru.ru_msgsnd); + break; + case 't': /* Average resident set size. */ + fprintf(fp, "%lu", + MSEC_TO_TICKS(v) == 0 ? 0 : + ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v)); + break; + case 'w': /* Voluntary context switches. */ + fprintf(fp, "%ld", resp->ru.ru_nvcsw); + break; + case 'x': /* Exit status. */ + fprintf(fp, "%d", WEXITSTATUS(resp->waitstatus)); + break; + case '\0': + putc('?', fp); + return; + default: + putc('?', fp); + putc(*fmt, fp); + } + ++fmt; + break; + + case '\\': /* Format escape. */ + switch (*++fmt) { + case 't': + putc('\t', fp); + break; + case 'n': + putc('\n', fp); + break; + case '\\': + putc('\\', fp); + break; + default: + putc('?', fp); + putc('\\', fp); + putc(*fmt, fp); + } + ++fmt; + break; + + default: + putc(*fmt++, fp); + } + + if (ferror(fp)) + bb_error_msg_and_die(bb_msg_write_error); + } + putc('\n', fp); + + if (ferror(fp)) + bb_error_msg_and_die(bb_msg_write_error); +} + +/* Run command CMD and return statistics on it. + Put the statistics in *RESP. */ +static void run_command(char *const *cmd, resource_t * resp) +{ + pid_t pid; /* Pid of child. */ + __sighandler_t interrupt_signal, quit_signal; + + gettimeofday(&resp->start, (struct timezone *) 0); + pid = vfork(); /* Run CMD as child process. */ + if (pid < 0) + bb_error_msg_and_die("cannot fork"); + else if (pid == 0) { /* If child. */ + /* Don't cast execvp arguments; that causes errors on some systems, + versus merely warnings if the cast is left off. */ + execvp(cmd[0], cmd); + bb_error_msg("cannot run %s", cmd[0]); + _exit(errno == ENOENT ? 127 : 126); + } + + /* Have signals kill the child but not self (if possible). */ + interrupt_signal = signal(SIGINT, SIG_IGN); + quit_signal = signal(SIGQUIT, SIG_IGN); + + if (resuse_end(pid, resp) == 0) + bb_error_msg("error waiting for child process"); + + /* Re-enable signals. */ + signal(SIGINT, interrupt_signal); + signal(SIGQUIT, quit_signal); +} + +int time_main(int argc, char **argv) +{ + int gotone; + resource_t res; + const char *output_format = default_format; + + argc--; + argv++; + /* Parse any options -- don't use getopt() here so we don't + * consume the args of our client application... */ + while (argc > 0 && **argv == '-') { + gotone = 0; + while (gotone == 0 && *++(*argv)) { + switch (**argv) { + case 'v': + output_format = long_format; + break; + case 'p': + output_format = posix_format; + break; + default: + bb_show_usage(); + } + argc--; + argv++; + gotone = 1; + } + } + + if (argv == NULL || *argv == NULL) + bb_show_usage(); + + run_command(argv, &res); + summarize(stderr, output_format, argv, &res); + fflush(stderr); + + if (WIFSTOPPED(res.waitstatus)) + exit(WSTOPSIG(res.waitstatus)); + else if (WIFSIGNALED(res.waitstatus)) + exit(WTERMSIG(res.waitstatus)); + else if (WIFEXITED(res.waitstatus)) + exit(WEXITSTATUS(res.waitstatus)); + return 0; +} diff --git a/miscutils/watchdog.c b/miscutils/watchdog.c new file mode 100644 index 000000000..e342c13f3 --- /dev/null +++ b/miscutils/watchdog.c @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini watchdog implementation for busybox + * + * Copyright (C) 2003 Paul Mundt + * Copyright (C) 2006 Bernhard Fischer + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +#define OPT_FOREGROUND 0x01 +#define OPT_TIMER 0x02 + +/* Watchdog file descriptor */ +static int fd; + +static void watchdog_shutdown(int ATTRIBUTE_UNUSED unused) +{ + write(fd, "V", 1); /* Magic, see watchdog-api.txt in kernel */ + close(fd); + exit(0); +} + +int watchdog_main(int argc, char **argv) +{ + unsigned opts; + unsigned timer_duration = 30; /* Userspace timer duration, in seconds */ + char *t_arg; + + opts = getopt32(argc, argv, "Ft:", &t_arg); + + if (opts & OPT_TIMER) + timer_duration = xatou(t_arg); + + /* We're only interested in the watchdog device .. */ + if (optind < argc - 1 || argc == 1) + bb_show_usage(); + +#ifdef BB_NOMMU + if (!(opts & OPT_FOREGROUND)) + vfork_daemon_rexec(0, 1, argc, argv, "-F"); +#else + xdaemon(0, 1); +#endif + + signal(SIGHUP, watchdog_shutdown); + signal(SIGINT, watchdog_shutdown); + + fd = xopen(argv[argc - 1], O_WRONLY); + + while (1) { + /* + * Make sure we clear the counter before sleeping, as the counter value + * is undefined at this point -- PFM + */ + write(fd, "\0", 1); + sleep(timer_duration); + } + + watchdog_shutdown(0); + + return EXIT_SUCCESS; +} diff --git a/modutils/Config.in b/modutils/Config.in new file mode 100644 index 000000000..6a6956755 --- /dev/null +++ b/modutils/Config.in @@ -0,0 +1,158 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Linux Module Utilities" + +config INSMOD + bool "insmod" + default n + help + insmod is used to load specified modules in the running kernel. + +config FEATURE_INSMOD_VERSION_CHECKING + bool "Module version checking" + default n + depends on INSMOD && FEATURE_2_4_MODULES + help + Support checking of versions for modules. This is used to + ensure that the kernel and module are made for each other. + +config FEATURE_INSMOD_KSYMOOPS_SYMBOLS + bool "Add module symbols to kernel symbol table" + default n + depends on INSMOD && FEATURE_2_4_MODULES + help + By adding module symbols to the kernel symbol table, Oops messages + occuring within kernel modules can be properly debugged. By enabling + this feature, module symbols will always be added to the kernel symbol + table for properly debugging support. If you are not interested in + Oops messages from kernel modules, say N. + +config FEATURE_INSMOD_LOADINKMEM + bool "In kernel memory optimization (uClinux only)" + default n + depends on INSMOD && FEATURE_2_4_MODULES + help + This is a special uClinux only memory optimization that lets insmod + load the specified kernel module directly into kernel space, reducing + memory usage by preventing the need for two copies of the module + being loaded into memory. + +config FEATURE_INSMOD_LOAD_MAP + bool "Enable load map (-m) option" + default n + depends on INSMOD && ( FEATURE_2_4_MODULES || FEATURE_2_6_MODULES ) + help + Enabling this, one would be able to get a load map + output on stdout. This makes kernel module debugging + easier. + If you don't plan to debug kernel modules, you + don't need this option. + +config FEATURE_INSMOD_LOAD_MAP_FULL + bool "Symbols in load map" + default y + depends on FEATURE_INSMOD_LOAD_MAP + help + Without this option, -m will only output section + load map. With this option, -m will also output + symbols load map. + +config RMMOD + bool "rmmod" + default n + help + rmmod is used to unload specified modules from the kernel. + +config LSMOD + bool "lsmod" + default n + help + lsmod is used to display a list of loaded modules. + +config FEATURE_LSMOD_PRETTY_2_6_OUTPUT + bool "lsmod pretty output for 2.6.x Linux kernels " + default n + depends on LSMOD + help + This option makes output format of lsmod adjusted to + the format of module-init-tools for Linux kernel 2.6. + +config MODPROBE + bool "modprobe" + default n + help + Handle the loading of modules, and their dependencies on a high + level. + + Note that in the state, modprobe does not understand multiple + module options from the configuration file. See option below. + +config FEATURE_MODPROBE_MULTIPLE_OPTIONS + bool + prompt "Multiple options parsing" if NITPICK + default y + depends on MODPROBE + help + Allow modprobe to understand more than one option to pass to + modules. + + This is a WIP, while waiting for a common argument parsing + common amongst all BB applets (shell, modprobe, etc...) and + adds around 600 bytes on x86, 700 bytes on ARM. The code is + biggish and uggly, but just works. + + Saying Y here is not a bad idea if you're not that short + on storage capacity. + +config FEATURE_MODPROBE_FANCY_ALIAS + bool + prompt "Fancy alias parsing" if NITPICK + default y + depends on MODPROBE && FEATURE_2_6_MODULES + help + Say 'y' here to enable parsing of aliases with underscore/dash + mismatch between module name and file name, along with bus-specific + aliases (such as pci:... or usb:... aliases). + +comment "Options common to multiple modutils" + depends on INSMOD || RMMOD || MODPROBE || LSMOD + +config FEATURE_CHECK_TAINTED_MODULE + # Simulate indentation + bool "Support tainted module checking with new kernels" + default y + depends on INSMOD || LSMOD + help + Support checking for tainted modules. These are usually binary + only modules that will make the linux-kernel list ignore your + support request. + This option is required to support GPLONLY modules. + +config FEATURE_2_4_MODULES + # Simulate indentation + bool "Support version 2.2.x to 2.4.x Linux kernels" + default y + depends on INSMOD || RMMOD || MODPROBE + help + Support module loading for 2.2.x and 2.4.x Linux kernels. + +config FEATURE_2_6_MODULES + # Simulate indentation + bool "Support version 2.6.x Linux kernels" + default y + depends on INSMOD || RMMOD || MODPROBE + help + Support module loading for newer 2.6.x Linux kernels. + + +config FEATURE_QUERY_MODULE_INTERFACE + bool + default y + depends on FEATURE_2_4_MODULES && !FEATURE_2_6_MODULES + + +endmenu + diff --git a/modutils/Kbuild b/modutils/Kbuild new file mode 100644 index 000000000..cff02b4f2 --- /dev/null +++ b/modutils/Kbuild @@ -0,0 +1,11 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_INSMOD) += insmod.o +lib-$(CONFIG_LSMOD) += lsmod.o +lib-$(CONFIG_MODPROBE) += modprobe.o +lib-$(CONFIG_RMMOD) += rmmod.o diff --git a/modutils/insmod.c b/modutils/insmod.c new file mode 100644 index 000000000..7b715b9c3 --- /dev/null +++ b/modutils/insmod.c @@ -0,0 +1,4316 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini insmod implementation for busybox + * + * This version of insmod supports ARM, CRIS, H8/300, x86, ia64, x86_64, + * m68k, MIPS, PowerPC, S390, SH3/4/5, Sparc, v850e, and x86_64. + * + * Copyright (C) 1999-2004 by Erik Andersen + * and Ron Alder + * + * Rodney Radford 17-Aug-2004. + * Added x86_64 support. + * + * Miles Bader added NEC V850E support. + * + * Modified by Bryan Rittmeyer to support SH4 + * and (theoretically) SH3. I have only tested SH4 in little endian mode. + * + * Modified by Alcove, Julien Gaulmin and + * Nicolas Ferre to support ARM7TDMI. Only + * very minor changes required to also work with StrongArm and presumably + * all ARM based systems. + * + * Yoshinori Sato 19-May-2004. + * added Renesas H8/300 support. + * + * Paul Mundt 08-Aug-2003. + * Integrated support for sh64 (SH-5), from preliminary modutils + * patches from Benedict Gaster . + * Currently limited to support for 32bit ABI. + * + * Magnus Damm 22-May-2002. + * The plt and got code are now using the same structs. + * Added generic linked list code to fully support PowerPC. + * Replaced the mess in arch_apply_relocation() with architecture blocks. + * The arch_create_got() function got cleaned up with architecture blocks. + * These blocks should be easy maintain and sync with obj_xxx.c in modutils. + * + * Magnus Damm added PowerPC support 20-Feb-2001. + * PowerPC specific code stolen from modutils-2.3.16, + * written by Paul Mackerras, Copyright 1996, 1997 Linux International. + * I've only tested the code on mpc8xx platforms in big-endian mode. + * Did some cleanup and added USE_xxx_ENTRIES... + * + * Quinn Jensen added MIPS support 23-Feb-2001. + * based on modutils-2.4.2 + * MIPS specific support for Elf loading and relocation. + * Copyright 1996, 1997 Linux International. + * Contributed by Ralf Baechle + * + * Based almost entirely on the Linux modutils-2.3.11 implementation. + * Copyright 1996, 1997 Linux International. + * New implementation contributed by Richard Henderson + * Based on original work by Bjorn Ekwall + * Restructured (and partly rewritten) by: + * Björn Ekwall February 1999 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include + +#if !ENABLE_FEATURE_2_4_MODULES && !ENABLE_FEATURE_2_6_MODULES +#define ENABLE_FEATURE_2_4_MODULES 1 +#endif + +#if !ENABLE_FEATURE_2_4_MODULES +#define insmod_ng_main insmod_main +#endif + +#if ENABLE_FEATURE_2_6_MODULES +extern int insmod_ng_main( int argc, char **argv); +#endif + + +#if ENABLE_FEATURE_2_4_MODULES + + +#if ENABLE_FEATURE_INSMOD_LOADINKMEM +#define LOADBITS 0 +#else +#define LOADBITS 1 +#endif + + +/* Alpha */ +#if defined(__alpha__) +#define MATCH_MACHINE(x) (x == EM_ALPHA) +#define SHT_RELM SHT_RELA +#define Elf64_RelM Elf64_Rela +#define ELFCLASSM ELFCLASS64 +#endif + +/* ARM support */ +#if defined(__arm__) +#define MATCH_MACHINE(x) (x == EM_ARM) +#define SHT_RELM SHT_REL +#define Elf32_RelM Elf32_Rel +#define ELFCLASSM ELFCLASS32 +#define USE_PLT_ENTRIES +#define PLT_ENTRY_SIZE 8 +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 8 +#define USE_SINGLE +#endif + +/* blackfin */ +#if defined(BFIN) +#define MATCH_MACHINE(x) (x == EM_BLACKFIN) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#endif + +/* CRIS */ +#if defined(__cris__) +#define MATCH_MACHINE(x) (x == EM_CRIS) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#ifndef EM_CRIS +#define EM_CRIS 76 +#define R_CRIS_NONE 0 +#define R_CRIS_32 3 +#endif +#endif + +/* H8/300 */ +#if defined(__H8300H__) || defined(__H8300S__) +#define MATCH_MACHINE(x) (x == EM_H8_300) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_SINGLE +#define SYMBOL_PREFIX "_" +#endif + +/* PA-RISC / HP-PA */ +#if defined(__hppa__) +#define MATCH_MACHINE(x) (x == EM_PARISC) +#define SHT_RELM SHT_RELA +#if defined(__LP64__) +#define Elf64_RelM Elf64_Rela +#define ELFCLASSM ELFCLASS64 +#else +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#endif +#endif + +/* x86 */ +#if defined(__i386__) +#ifndef EM_486 +#define MATCH_MACHINE(x) (x == EM_386) +#else +#define MATCH_MACHINE(x) (x == EM_386 || x == EM_486) +#endif +#define SHT_RELM SHT_REL +#define Elf32_RelM Elf32_Rel +#define ELFCLASSM ELFCLASS32 +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 4 +#define USE_SINGLE +#endif + +/* IA64, aka Itanium */ +#if defined(__ia64__) +#define MATCH_MACHINE(x) (x == EM_IA_64) +#define SHT_RELM SHT_RELA +#define Elf64_RelM Elf64_Rela +#define ELFCLASSM ELFCLASS64 +#endif + +/* m68k */ +#if defined(__mc68000__) +#define MATCH_MACHINE(x) (x == EM_68K) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 4 +#define USE_SINGLE +#endif + +/* Microblaze */ +#if defined(__microblaze__) +#define USE_SINGLE +#define MATCH_MACHINE(x) (x == EM_XILINX_MICROBLAZE) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#endif + +/* MIPS */ +#if defined(__mips__) +#define MATCH_MACHINE(x) (x == EM_MIPS || x == EM_MIPS_RS3_LE) +#define SHT_RELM SHT_REL +#define Elf32_RelM Elf32_Rel +#define ELFCLASSM ELFCLASS32 +/* Account for ELF spec changes. */ +#ifndef EM_MIPS_RS3_LE +#ifdef EM_MIPS_RS4_BE +#define EM_MIPS_RS3_LE EM_MIPS_RS4_BE +#else +#define EM_MIPS_RS3_LE 10 +#endif +#endif /* !EM_MIPS_RS3_LE */ +#define ARCHDATAM "__dbe_table" +#endif + +/* Nios II */ +#if defined(__nios2__) +#define MATCH_MACHINE(x) (x == EM_ALTERA_NIOS2) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#endif + +/* PowerPC */ +#if defined(__powerpc64__) +#define MATCH_MACHINE(x) (x == EM_PPC64) +#define SHT_RELM SHT_RELA +#define Elf64_RelM Elf64_Rela +#define ELFCLASSM ELFCLASS64 +#elif defined(__powerpc__) +#define MATCH_MACHINE(x) (x == EM_PPC) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_PLT_ENTRIES +#define PLT_ENTRY_SIZE 16 +#define USE_PLT_LIST +#define LIST_ARCHTYPE ElfW(Addr) +#define USE_LIST +#define ARCHDATAM "__ftr_fixup" +#endif + +/* S390 */ +#if defined(__s390__) +#define MATCH_MACHINE(x) (x == EM_S390) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_PLT_ENTRIES +#define PLT_ENTRY_SIZE 8 +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 8 +#define USE_SINGLE +#endif + +/* SuperH */ +#if defined(__sh__) +#define MATCH_MACHINE(x) (x == EM_SH) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 4 +#define USE_SINGLE +/* the SH changes have only been tested in =little endian= mode */ +/* I'm not sure about big endian, so let's warn: */ +#if defined(__sh__) && BB_BIG_ENDIAN +# error insmod.c may require changes for use on big endian SH +#endif +/* it may or may not work on the SH1/SH2... Error on those also */ +#if ((!(defined(__SH3__) || defined(__SH4__) || defined(__SH5__)))) && (defined(__sh__)) +#error insmod.c may require changes for SH1 or SH2 use +#endif +#endif + +/* Sparc */ +#if defined(__sparc__) +#define MATCH_MACHINE(x) (x == EM_SPARC) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#endif + +/* v850e */ +#if defined (__v850e__) +#define MATCH_MACHINE(x) ((x) == EM_V850 || (x) == EM_CYGNUS_V850) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_PLT_ENTRIES +#define PLT_ENTRY_SIZE 8 +#define USE_SINGLE +#ifndef EM_CYGNUS_V850 /* grumble */ +#define EM_CYGNUS_V850 0x9080 +#endif +#define SYMBOL_PREFIX "_" +#endif + +/* X86_64 */ +#if defined(__x86_64__) +#define MATCH_MACHINE(x) (x == EM_X86_64) +#define SHT_RELM SHT_RELA +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 8 +#define USE_SINGLE +#define Elf64_RelM Elf64_Rela +#define ELFCLASSM ELFCLASS64 +#endif + +#ifndef SHT_RELM +#error Sorry, but insmod.c does not yet support this architecture... +#endif + + +//---------------------------------------------------------------------------- +//--------modutils module.h, lines 45-242 +//---------------------------------------------------------------------------- + +/* Definitions for the Linux module syscall interface. + Copyright 1996, 1997 Linux International. + + Contributed by Richard Henderson + + This file is part of the Linux modutils. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +#ifndef MODUTILS_MODULE_H +/* Why? static const int MODUTILS_MODULE_H = 1;*/ + +/*======================================================================*/ +/* For sizeof() which are related to the module platform and not to the + environment isnmod is running in, use sizeof_xx instead of sizeof(xx). */ + +#define tgt_sizeof_char sizeof(char) +#define tgt_sizeof_short sizeof(short) +#define tgt_sizeof_int sizeof(int) +#define tgt_sizeof_long sizeof(long) +#define tgt_sizeof_char_p sizeof(char *) +#define tgt_sizeof_void_p sizeof(void *) +#define tgt_long long + +#if defined(__sparc__) && !defined(__sparc_v9__) && defined(ARCH_sparc64) +#undef tgt_sizeof_long +#undef tgt_sizeof_char_p +#undef tgt_sizeof_void_p +#undef tgt_long +enum { + tgt_sizeof_long = 8, + tgt_sizeof_char_p = 8, + tgt_sizeof_void_p = 8 +}; +#define tgt_long long long +#endif + +/*======================================================================*/ +/* The structures used in Linux 2.1. */ + +/* Note: new_module_symbol does not use tgt_long intentionally */ +struct new_module_symbol { + unsigned long value; + unsigned long name; +}; + +struct new_module_persist; + +struct new_module_ref { + unsigned tgt_long dep; /* kernel addresses */ + unsigned tgt_long ref; + unsigned tgt_long next_ref; +}; + +struct new_module { + unsigned tgt_long size_of_struct; /* == sizeof(module) */ + unsigned tgt_long next; + unsigned tgt_long name; + unsigned tgt_long size; + + tgt_long usecount; + unsigned tgt_long flags; /* AUTOCLEAN et al */ + + unsigned nsyms; + unsigned ndeps; + + unsigned tgt_long syms; + unsigned tgt_long deps; + unsigned tgt_long refs; + unsigned tgt_long init; + unsigned tgt_long cleanup; + unsigned tgt_long ex_table_start; + unsigned tgt_long ex_table_end; +#ifdef __alpha__ + unsigned tgt_long gp; +#endif + /* Everything after here is extension. */ + unsigned tgt_long persist_start; + unsigned tgt_long persist_end; + unsigned tgt_long can_unload; + unsigned tgt_long runsize; + const char *kallsyms_start; /* All symbols for kernel debugging */ + const char *kallsyms_end; + const char *archdata_start; /* arch specific data for module */ + const char *archdata_end; + const char *kernel_data; /* Reserved for kernel internal use */ +}; + +#ifdef ARCHDATAM +#define ARCHDATA_SEC_NAME ARCHDATAM +#else +#define ARCHDATA_SEC_NAME "__archdata" +#endif +#define KALLSYMS_SEC_NAME "__kallsyms" + + +struct new_module_info { + unsigned long addr; + unsigned long size; + unsigned long flags; + long usecount; +}; + +/* Bits of module.flags. */ +enum { + NEW_MOD_RUNNING = 1, + NEW_MOD_DELETED = 2, + NEW_MOD_AUTOCLEAN = 4, + NEW_MOD_VISITED = 8, + NEW_MOD_USED_ONCE = 16 +}; + +int init_module(const char *name, const struct new_module *); +int query_module(const char *name, int which, void *buf, + size_t bufsize, size_t *ret); + +/* Values for query_module's which. */ +enum { + QM_MODULES = 1, + QM_DEPS = 2, + QM_REFS = 3, + QM_SYMBOLS = 4, + QM_INFO = 5 +}; + +/*======================================================================*/ +/* The system calls unchanged between 2.0 and 2.1. */ + +unsigned long create_module(const char *, size_t); +int delete_module(const char *); + + +#endif /* module.h */ + +//---------------------------------------------------------------------------- +//--------end of modutils module.h +//---------------------------------------------------------------------------- + + + +//---------------------------------------------------------------------------- +//--------modutils obj.h, lines 253-462 +//---------------------------------------------------------------------------- + +/* Elf object file loading and relocation routines. + Copyright 1996, 1997 Linux International. + + Contributed by Richard Henderson + + This file is part of the Linux modutils. + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by the + Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +#ifndef MODUTILS_OBJ_H +/* Why? static const int MODUTILS_OBJ_H = 1; */ + +/* The relocatable object is manipulated using elfin types. */ + +#include +#include +#include + +#ifndef ElfW +# if ELFCLASSM == ELFCLASS32 +# define ElfW(x) Elf32_ ## x +# define ELFW(x) ELF32_ ## x +# else +# define ElfW(x) Elf64_ ## x +# define ELFW(x) ELF64_ ## x +# endif +#endif + +/* For some reason this is missing from some ancient C libraries.... */ +#ifndef ELF32_ST_INFO +# define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf)) +#endif + +#ifndef ELF64_ST_INFO +# define ELF64_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf)) +#endif + +#define ELF_ST_BIND(info) ELFW(ST_BIND)(info) +#define ELF_ST_TYPE(info) ELFW(ST_TYPE)(info) +#define ELF_ST_INFO(bind, type) ELFW(ST_INFO)(bind, type) +#define ELF_R_TYPE(val) ELFW(R_TYPE)(val) +#define ELF_R_SYM(val) ELFW(R_SYM)(val) + +struct obj_string_patch; +struct obj_symbol_patch; + +struct obj_section +{ + ElfW(Shdr) header; + const char *name; + char *contents; + struct obj_section *load_next; + int idx; +}; + +struct obj_symbol +{ + struct obj_symbol *next; /* hash table link */ + const char *name; + unsigned long value; + unsigned long size; + int secidx; /* the defining section index/module */ + int info; + int ksymidx; /* for export to the kernel symtab */ + int referenced; /* actually used in the link */ +}; + +/* Hardcode the hash table size. We shouldn't be needing so many + symbols that we begin to degrade performance, and we get a big win + by giving the compiler a constant divisor. */ + +#define HASH_BUCKETS 521 + +struct obj_file { + ElfW(Ehdr) header; + ElfW(Addr) baseaddr; + struct obj_section **sections; + struct obj_section *load_order; + struct obj_section **load_order_search_start; + struct obj_string_patch *string_patches; + struct obj_symbol_patch *symbol_patches; + int (*symbol_cmp)(const char *, const char *); + unsigned long (*symbol_hash)(const char *); + unsigned long local_symtab_size; + struct obj_symbol **local_symtab; + struct obj_symbol *symtab[HASH_BUCKETS]; +}; + +enum obj_reloc { + obj_reloc_ok, + obj_reloc_overflow, + obj_reloc_dangerous, + obj_reloc_unhandled +}; + +struct obj_string_patch { + struct obj_string_patch *next; + int reloc_secidx; + ElfW(Addr) reloc_offset; + ElfW(Addr) string_offset; +}; + +struct obj_symbol_patch { + struct obj_symbol_patch *next; + int reloc_secidx; + ElfW(Addr) reloc_offset; + struct obj_symbol *sym; +}; + + +/* Generic object manipulation routines. */ + +static unsigned long obj_elf_hash(const char *); + +static unsigned long obj_elf_hash_n(const char *, unsigned long len); + +static struct obj_symbol *obj_find_symbol (struct obj_file *f, + const char *name); + +static ElfW(Addr) obj_symbol_final_value(struct obj_file *f, + struct obj_symbol *sym); + +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING +static void obj_set_symbol_compare(struct obj_file *f, + int (*cmp)(const char *, const char *), + unsigned long (*hash)(const char *)); +#endif + +static struct obj_section *obj_find_section (struct obj_file *f, + const char *name); + +static void obj_insert_section_load_order (struct obj_file *f, + struct obj_section *sec); + +static struct obj_section *obj_create_alloced_section (struct obj_file *f, + const char *name, + unsigned long align, + unsigned long size); + +static struct obj_section *obj_create_alloced_section_first (struct obj_file *f, + const char *name, + unsigned long align, + unsigned long size); + +static void *obj_extend_section (struct obj_section *sec, unsigned long more); + +static int obj_string_patch(struct obj_file *f, int secidx, ElfW(Addr) offset, + const char *string); + +static int obj_symbol_patch(struct obj_file *f, int secidx, ElfW(Addr) offset, + struct obj_symbol *sym); + +static int obj_check_undefineds(struct obj_file *f); + +static void obj_allocate_commons(struct obj_file *f); + +static unsigned long obj_load_size (struct obj_file *f); + +static int obj_relocate (struct obj_file *f, ElfW(Addr) base); + +static struct obj_file *obj_load(FILE *f, int loadprogbits); + +static int obj_create_image (struct obj_file *f, char *image); + +/* Architecture specific manipulation routines. */ + +static struct obj_file *arch_new_file (void); + +static struct obj_section *arch_new_section (void); + +static struct obj_symbol *arch_new_symbol (void); + +static enum obj_reloc arch_apply_relocation (struct obj_file *f, + struct obj_section *targsec, + struct obj_section *symsec, + struct obj_symbol *sym, + ElfW(RelM) *rel, ElfW(Addr) value); + +static void arch_create_got (struct obj_file *f); +#if ENABLE_FEATURE_CHECK_TAINTED_MODULE +static int obj_gpl_license(struct obj_file *f, const char **license); +#endif /* FEATURE_CHECK_TAINTED_MODULE */ +#endif /* obj.h */ +//---------------------------------------------------------------------------- +//--------end of modutils obj.h +//---------------------------------------------------------------------------- + + +/* SPFX is always a string, so it can be concatenated to string constants. */ +#ifdef SYMBOL_PREFIX +#define SPFX SYMBOL_PREFIX +#else +#define SPFX "" +#endif + + +#define _PATH_MODULES "/lib/modules" +enum { STRVERSIONLEN = 64 }; + +/*======================================================================*/ + +#define OPTION_STR "sLo:fkvqx" USE_FEATURE_INSMOD_LOAD_MAP("m") +enum { + OPT_s = 0x1, // -s /* log to syslog */ + /* Not supported but kernel needs this for request_module(), + as this calls: modprobe -k -s -- + so silently ignore this flag */ + OPT_L = 0x2, // -L /* Stub warning */ + /* Compatibility with modprobe. + In theory, this does locking, but we don't do + that. So be careful and plan your life around not + loading the same module 50 times concurrently. */ + OPT_o = 0x4, // -o /* name the output module */ + OPT_f = 0x8, // -f /* force loading */ + OPT_k = 0x10, // -k /* module loaded by kerneld, auto-cleanable */ + OPT_v = 0x20, // -v /* verbose output */ + OPT_q = 0x40, // -q /* silent */ + OPT_x = 0x80, // -x /* do not export externs */ + OPT_m = 0x100, // -m /* print module load map */ +}; +#define flag_force_load (option_mask32 & OPT_f) +#define flag_autoclean (option_mask32 & OPT_k) +#define flag_verbose (option_mask32 & OPT_v) +#define flag_quiet (option_mask32 & OPT_q) +#define flag_noexport (option_mask32 & OPT_x) +#if ENABLE_FEATURE_INSMOD_LOAD_MAP +#define flag_print_load_map (option_mask32 & OPT_m) +#else +#define flag_print_load_map 0 +#endif + +/*======================================================================*/ + +#if defined(USE_LIST) + +struct arch_list_entry +{ + struct arch_list_entry *next; + LIST_ARCHTYPE addend; + int offset; + int inited : 1; +}; + +#endif + +#if defined(USE_SINGLE) + +struct arch_single_entry +{ + int offset; + int inited : 1; + int allocated : 1; +}; + +#endif + +#if defined(__mips__) +struct mips_hi16 +{ + struct mips_hi16 *next; + ElfW(Addr) *addr; + ElfW(Addr) value; +}; +#endif + +struct arch_file { + struct obj_file root; +#if defined(USE_PLT_ENTRIES) + struct obj_section *plt; +#endif +#if defined(USE_GOT_ENTRIES) + struct obj_section *got; +#endif +#if defined(__mips__) + struct mips_hi16 *mips_hi16_list; +#endif +}; + +struct arch_symbol { + struct obj_symbol root; +#if defined(USE_PLT_ENTRIES) +#if defined(USE_PLT_LIST) + struct arch_list_entry *pltent; +#else + struct arch_single_entry pltent; +#endif +#endif +#if defined(USE_GOT_ENTRIES) + struct arch_single_entry gotent; +#endif +}; + + +struct external_module { + const char *name; + ElfW(Addr) addr; + int used; + size_t nsyms; + struct new_module_symbol *syms; +}; + +static struct new_module_symbol *ksyms; +static size_t nksyms; + +static struct external_module *ext_modules; +static int n_ext_modules; +static int n_ext_modules_used; +extern int delete_module(const char *); + +static char *m_filename; +static char *m_fullName; + + +/*======================================================================*/ + + +static int check_module_name_match(const char *filename, struct stat *statbuf, + void *userdata, int depth) +{ + char *fullname = (char *) userdata; + + if (fullname[0] == '\0') + return FALSE; + else { + char *tmp, *tmp1 = xstrdup(filename); + tmp = bb_get_last_path_component(tmp1); + if (strcmp(tmp, fullname) == 0) { + free(tmp1); + /* Stop searching if we find a match */ + m_filename = xstrdup(filename); + return FALSE; + } + free(tmp1); + } + return TRUE; +} + + +/*======================================================================*/ + +static struct obj_file *arch_new_file(void) +{ + struct arch_file *f; + f = xmalloc(sizeof(*f)); + + memset(f, 0, sizeof(*f)); + + return &f->root; +} + +static struct obj_section *arch_new_section(void) +{ + return xmalloc(sizeof(struct obj_section)); +} + +static struct obj_symbol *arch_new_symbol(void) +{ + struct arch_symbol *sym; + sym = xmalloc(sizeof(*sym)); + + memset(sym, 0, sizeof(*sym)); + + return &sym->root; +} + +static enum obj_reloc +arch_apply_relocation(struct obj_file *f, + struct obj_section *targsec, + struct obj_section *symsec, + struct obj_symbol *sym, + ElfW(RelM) *rel, ElfW(Addr) v) +{ + struct arch_file *ifile = (struct arch_file *) f; + enum obj_reloc ret = obj_reloc_ok; + ElfW(Addr) *loc = (ElfW(Addr) *) (targsec->contents + rel->r_offset); + ElfW(Addr) dot = targsec->header.sh_addr + rel->r_offset; +#if defined(USE_GOT_ENTRIES) || defined(USE_PLT_ENTRIES) + struct arch_symbol *isym = (struct arch_symbol *) sym; +#endif +#if defined(__arm__) || defined(__i386__) || defined(__mc68000__) || defined(__sh__) || defined(__s390__) +#if defined(USE_GOT_ENTRIES) + ElfW(Addr) got = ifile->got ? ifile->got->header.sh_addr : 0; +#endif +#endif +#if defined(USE_PLT_ENTRIES) + ElfW(Addr) plt = ifile->plt ? ifile->plt->header.sh_addr : 0; + unsigned long *ip; +# if defined(USE_PLT_LIST) + struct arch_list_entry *pe; +# else + struct arch_single_entry *pe; +# endif +#endif + + switch (ELF_R_TYPE(rel->r_info)) { + +#if defined(__arm__) + + case R_ARM_NONE: + break; + + case R_ARM_ABS32: + *loc += v; + break; + + case R_ARM_GOT32: + goto bb_use_got; + + case R_ARM_GOTPC: + /* relative reloc, always to _GLOBAL_OFFSET_TABLE_ + * (which is .got) similar to branch, + * but is full 32 bits relative */ + + *loc += got - dot; + break; + + case R_ARM_PC24: + case R_ARM_PLT32: + goto bb_use_plt; + + case R_ARM_GOTOFF: /* address relative to the got */ + *loc += v - got; + break; + +#elif defined(__cris__) + + case R_CRIS_NONE: + break; + + case R_CRIS_32: + /* CRIS keeps the relocation value in the r_addend field and + * should not use whats in *loc at all + */ + *loc = v; + break; + +#elif defined(__H8300H__) || defined(__H8300S__) + + case R_H8_DIR24R8: + loc = (ElfW(Addr) *)((ElfW(Addr))loc - 1); + *loc = (*loc & 0xff000000) | ((*loc & 0xffffff) + v); + break; + case R_H8_DIR24A8: + *loc += v; + break; + case R_H8_DIR32: + case R_H8_DIR32A16: + *loc += v; + break; + case R_H8_PCREL16: + v -= dot + 2; + if ((ElfW(Sword))v > 0x7fff || + (ElfW(Sword))v < -(ElfW(Sword))0x8000) + ret = obj_reloc_overflow; + else + *(unsigned short *)loc = v; + break; + case R_H8_PCREL8: + v -= dot + 1; + if ((ElfW(Sword))v > 0x7f || + (ElfW(Sword))v < -(ElfW(Sword))0x80) + ret = obj_reloc_overflow; + else + *(unsigned char *)loc = v; + break; + +#elif defined(__i386__) + + case R_386_NONE: + break; + + case R_386_32: + *loc += v; + break; + + case R_386_PLT32: + case R_386_PC32: + *loc += v - dot; + break; + + case R_386_GLOB_DAT: + case R_386_JMP_SLOT: + *loc = v; + break; + + case R_386_RELATIVE: + *loc += f->baseaddr; + break; + + case R_386_GOTPC: + *loc += got - dot; + break; + + case R_386_GOT32: + goto bb_use_got; + + case R_386_GOTOFF: + *loc += v - got; + break; + +#elif defined (__microblaze__) + case R_MICROBLAZE_NONE: + case R_MICROBLAZE_64_NONE: + case R_MICROBLAZE_32_SYM_OP_SYM: + case R_MICROBLAZE_32_PCREL: + break; + + case R_MICROBLAZE_64_PCREL: { + /* dot is the address of the current instruction. + * v is the target symbol address. + * So we need to extract the offset in the code, + * adding v, then subtrating the current address + * of this instruction. + * Ex: "IMM 0xFFFE bralid 0x0000" = "bralid 0xFFFE0000" + */ + + /* Get split offset stored in code */ + unsigned int temp = (loc[0] & 0xFFFF) << 16 | + (loc[1] & 0xFFFF); + + /* Adjust relative offset. -4 adjustment required + * because dot points to the IMM insn, but branch + * is computed relative to the branch instruction itself. + */ + temp += v - dot - 4; + + /* Store back into code */ + loc[0] = (loc[0] & 0xFFFF0000) | temp >> 16; + loc[1] = (loc[1] & 0xFFFF0000) | (temp & 0xFFFF); + + break; + } + + case R_MICROBLAZE_32: + *loc += v; + break; + + case R_MICROBLAZE_64: { + /* Get split pointer stored in code */ + unsigned int temp1 = (loc[0] & 0xFFFF) << 16 | + (loc[1] & 0xFFFF); + + /* Add reloc offset */ + temp1+=v; + + /* Store back into code */ + loc[0] = (loc[0] & 0xFFFF0000) | temp1 >> 16; + loc[1] = (loc[1] & 0xFFFF0000) | (temp1 & 0xFFFF); + + break; + } + + case R_MICROBLAZE_32_PCREL_LO: + case R_MICROBLAZE_32_LO: + case R_MICROBLAZE_SRO32: + case R_MICROBLAZE_SRW32: + ret = obj_reloc_unhandled; + break; + +#elif defined(__mc68000__) + + case R_68K_NONE: + break; + + case R_68K_32: + *loc += v; + break; + + case R_68K_8: + if (v > 0xff) { + ret = obj_reloc_overflow; + } + *(char *)loc = v; + break; + + case R_68K_16: + if (v > 0xffff) { + ret = obj_reloc_overflow; + } + *(short *)loc = v; + break; + + case R_68K_PC8: + v -= dot; + if ((ElfW(Sword))v > 0x7f || + (ElfW(Sword))v < -(ElfW(Sword))0x80) { + ret = obj_reloc_overflow; + } + *(char *)loc = v; + break; + + case R_68K_PC16: + v -= dot; + if ((ElfW(Sword))v > 0x7fff || + (ElfW(Sword))v < -(ElfW(Sword))0x8000) { + ret = obj_reloc_overflow; + } + *(short *)loc = v; + break; + + case R_68K_PC32: + *(int *)loc = v - dot; + break; + + case R_68K_GLOB_DAT: + case R_68K_JMP_SLOT: + *loc = v; + break; + + case R_68K_RELATIVE: + *(int *)loc += f->baseaddr; + break; + + case R_68K_GOT32: + goto bb_use_got; + +# ifdef R_68K_GOTOFF + case R_68K_GOTOFF: + *loc += v - got; + break; +# endif + +#elif defined(__mips__) + + case R_MIPS_NONE: + break; + + case R_MIPS_32: + *loc += v; + break; + + case R_MIPS_26: + if (v % 4) + ret = obj_reloc_dangerous; + if ((v & 0xf0000000) != ((dot + 4) & 0xf0000000)) + ret = obj_reloc_overflow; + *loc = + (*loc & ~0x03ffffff) | ((*loc + (v >> 2)) & + 0x03ffffff); + break; + + case R_MIPS_HI16: + { + struct mips_hi16 *n; + + /* We cannot relocate this one now because we don't know the value + of the carry we need to add. Save the information, and let LO16 + do the actual relocation. */ + n = (struct mips_hi16 *) xmalloc(sizeof *n); + n->addr = loc; + n->value = v; + n->next = ifile->mips_hi16_list; + ifile->mips_hi16_list = n; + break; + } + + case R_MIPS_LO16: + { + unsigned long insnlo = *loc; + ElfW(Addr) val, vallo; + + /* Sign extend the addend we extract from the lo insn. */ + vallo = ((insnlo & 0xffff) ^ 0x8000) - 0x8000; + + if (ifile->mips_hi16_list != NULL) { + struct mips_hi16 *l; + + l = ifile->mips_hi16_list; + while (l != NULL) { + struct mips_hi16 *next; + unsigned long insn; + + /* Do the HI16 relocation. Note that we actually don't + need to know anything about the LO16 itself, except where + to find the low 16 bits of the addend needed by the LO16. */ + insn = *l->addr; + val = + ((insn & 0xffff) << 16) + + vallo; + val += v; + + /* Account for the sign extension that will happen in the + low bits. */ + val = + ((val >> 16) + + ((val & 0x8000) != + 0)) & 0xffff; + + insn = (insn & ~0xffff) | val; + *l->addr = insn; + + next = l->next; + free(l); + l = next; + } + + ifile->mips_hi16_list = NULL; + } + + /* Ok, we're done with the HI16 relocs. Now deal with the LO16. */ + val = v + vallo; + insnlo = (insnlo & ~0xffff) | (val & 0xffff); + *loc = insnlo; + break; + } + +#elif defined(__nios2__) + + case R_NIOS2_NONE: + break; + + case R_NIOS2_BFD_RELOC_32: + *loc += v; + break; + + case R_NIOS2_BFD_RELOC_16: + if (v > 0xffff) { + ret = obj_reloc_overflow; + } + *(short *)loc = v; + break; + + case R_NIOS2_BFD_RELOC_8: + if (v > 0xff) { + ret = obj_reloc_overflow; + } + *(char *)loc = v; + break; + + case R_NIOS2_S16: + { + Elf32_Addr word; + + if ((Elf32_Sword)v > 0x7fff || + (Elf32_Sword)v < -(Elf32_Sword)0x8000) { + ret = obj_reloc_overflow; + } + + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | + (word & 0x3f); + } + break; + + case R_NIOS2_U16: + { + Elf32_Addr word; + + if (v > 0xffff) { + ret = obj_reloc_overflow; + } + + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | + (word & 0x3f); + } + break; + + case R_NIOS2_PCREL16: + { + Elf32_Addr word; + + v -= dot + 4; + if ((Elf32_Sword)v > 0x7fff || + (Elf32_Sword)v < -(Elf32_Sword)0x8000) { + ret = obj_reloc_overflow; + } + + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | (word & 0x3f); + } + break; + + case R_NIOS2_GPREL: + { + Elf32_Addr word, gp; + /* get _gp */ + gp = obj_symbol_final_value(f, obj_find_symbol(f, SPFX "_gp")); + v-=gp; + if ((Elf32_Sword)v > 0x7fff || + (Elf32_Sword)v < -(Elf32_Sword)0x8000) { + ret = obj_reloc_overflow; + } + + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | (word & 0x3f); + } + break; + + case R_NIOS2_CALL26: + if (v & 3) + ret = obj_reloc_dangerous; + if ((v >> 28) != (dot >> 28)) + ret = obj_reloc_overflow; + *loc = (*loc & 0x3f) | ((v >> 2) << 6); + break; + + case R_NIOS2_IMM5: + { + Elf32_Addr word; + + if (v > 0x1f) { + ret = obj_reloc_overflow; + } + + word = *loc & ~0x7c0; + *loc = word | ((v & 0x1f) << 6); + } + break; + + case R_NIOS2_IMM6: + { + Elf32_Addr word; + + if (v > 0x3f) { + ret = obj_reloc_overflow; + } + + word = *loc & ~0xfc0; + *loc = word | ((v & 0x3f) << 6); + } + break; + + case R_NIOS2_IMM8: + { + Elf32_Addr word; + + if (v > 0xff) { + ret = obj_reloc_overflow; + } + + word = *loc & ~0x3fc0; + *loc = word | ((v & 0xff) << 6); + } + break; + + case R_NIOS2_HI16: + { + Elf32_Addr word; + + word = *loc; + *loc = ((((word >> 22) << 16) | ((v >>16) & 0xffff)) << 6) | + (word & 0x3f); + } + break; + + case R_NIOS2_LO16: + { + Elf32_Addr word; + + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | + (word & 0x3f); + } + break; + + case R_NIOS2_HIADJ16: + { + Elf32_Addr word1, word2; + + word1 = *loc; + word2 = ((v >> 16) + ((v >> 15) & 1)) & 0xffff; + *loc = ((((word1 >> 22) << 16) | word2) << 6) | + (word1 & 0x3f); + } + break; + +#elif defined(__powerpc64__) + /* PPC64 needs a 2.6 kernel, 2.4 module relocation irrelevant */ + +#elif defined(__powerpc__) + + case R_PPC_ADDR16_HA: + *(unsigned short *)loc = (v + 0x8000) >> 16; + break; + + case R_PPC_ADDR16_HI: + *(unsigned short *)loc = v >> 16; + break; + + case R_PPC_ADDR16_LO: + *(unsigned short *)loc = v; + break; + + case R_PPC_REL24: + goto bb_use_plt; + + case R_PPC_REL32: + *loc = v - dot; + break; + + case R_PPC_ADDR32: + *loc = v; + break; + +#elif defined(__s390__) + + case R_390_32: + *(unsigned int *) loc += v; + break; + case R_390_16: + *(unsigned short *) loc += v; + break; + case R_390_8: + *(unsigned char *) loc += v; + break; + + case R_390_PC32: + *(unsigned int *) loc += v - dot; + break; + case R_390_PC16DBL: + *(unsigned short *) loc += (v - dot) >> 1; + break; + case R_390_PC16: + *(unsigned short *) loc += v - dot; + break; + + case R_390_PLT32: + case R_390_PLT16DBL: + /* find the plt entry and initialize it. */ + pe = (struct arch_single_entry *) &isym->pltent; + if (pe->inited == 0) { + ip = (unsigned long *)(ifile->plt->contents + pe->offset); + ip[0] = 0x0d105810; /* basr 1,0; lg 1,10(1); br 1 */ + ip[1] = 0x100607f1; + if (ELF_R_TYPE(rel->r_info) == R_390_PLT16DBL) + ip[2] = v - 2; + else + ip[2] = v; + pe->inited = 1; + } + + /* Insert relative distance to target. */ + v = plt + pe->offset - dot; + if (ELF_R_TYPE(rel->r_info) == R_390_PLT32) + *(unsigned int *) loc = (unsigned int) v; + else if (ELF_R_TYPE(rel->r_info) == R_390_PLT16DBL) + *(unsigned short *) loc = (unsigned short) ((v + 2) >> 1); + break; + + case R_390_GLOB_DAT: + case R_390_JMP_SLOT: + *loc = v; + break; + + case R_390_RELATIVE: + *loc += f->baseaddr; + break; + + case R_390_GOTPC: + *(unsigned long *) loc += got - dot; + break; + + case R_390_GOT12: + case R_390_GOT16: + case R_390_GOT32: + if (!isym->gotent.inited) + { + isym->gotent.inited = 1; + *(ElfW(Addr) *)(ifile->got->contents + isym->gotent.offset) = v; + } + if (ELF_R_TYPE(rel->r_info) == R_390_GOT12) + *(unsigned short *) loc |= (*(unsigned short *) loc + isym->gotent.offset) & 0xfff; + else if (ELF_R_TYPE(rel->r_info) == R_390_GOT16) + *(unsigned short *) loc += isym->gotent.offset; + else if (ELF_R_TYPE(rel->r_info) == R_390_GOT32) + *(unsigned int *) loc += isym->gotent.offset; + break; + +# ifndef R_390_GOTOFF32 +# define R_390_GOTOFF32 R_390_GOTOFF +# endif + case R_390_GOTOFF32: + *loc += v - got; + break; + +#elif defined(__sh__) + + case R_SH_NONE: + break; + + case R_SH_DIR32: + *loc += v; + break; + + case R_SH_REL32: + *loc += v - dot; + break; + + case R_SH_PLT32: + *loc = v - dot; + break; + + case R_SH_GLOB_DAT: + case R_SH_JMP_SLOT: + *loc = v; + break; + + case R_SH_RELATIVE: + *loc = f->baseaddr + rel->r_addend; + break; + + case R_SH_GOTPC: + *loc = got - dot + rel->r_addend; + break; + + case R_SH_GOT32: + goto bb_use_got; + + case R_SH_GOTOFF: + *loc = v - got; + break; + +# if defined(__SH5__) + case R_SH_IMM_MEDLOW16: + case R_SH_IMM_LOW16: + { + ElfW(Addr) word; + + if (ELF_R_TYPE(rel->r_info) == R_SH_IMM_MEDLOW16) + v >>= 16; + + /* + * movi and shori have the format: + * + * | op | imm | reg | reserved | + * 31..26 25..10 9.. 4 3 .. 0 + * + * so we simply mask and or in imm. + */ + word = *loc & ~0x3fffc00; + word |= (v & 0xffff) << 10; + + *loc = word; + + break; + } + + case R_SH_IMM_MEDLOW16_PCREL: + case R_SH_IMM_LOW16_PCREL: + { + ElfW(Addr) word; + + word = *loc & ~0x3fffc00; + + v -= dot; + + if (ELF_R_TYPE(rel->r_info) == R_SH_IMM_MEDLOW16_PCREL) + v >>= 16; + + word |= (v & 0xffff) << 10; + + *loc = word; + + break; + } +# endif /* __SH5__ */ + +#elif defined (__v850e__) + + case R_V850_NONE: + break; + + case R_V850_32: + /* We write two shorts instead of a long because even + 32-bit insns only need half-word alignment, but + 32-bit data needs to be long-word aligned. */ + v += ((unsigned short *)loc)[0]; + v += ((unsigned short *)loc)[1] << 16; + ((unsigned short *)loc)[0] = v & 0xffff; + ((unsigned short *)loc)[1] = (v >> 16) & 0xffff; + break; + + case R_V850_22_PCREL: + goto bb_use_plt; + +#elif defined(__x86_64__) + + case R_X86_64_NONE: + break; + + case R_X86_64_64: + *loc += v; + break; + + case R_X86_64_32: + *(unsigned int *) loc += v; + if (v > 0xffffffff) + { + ret = obj_reloc_overflow; /* Kernel module compiled without -mcmodel=kernel. */ + /* error("Possibly is module compiled without -mcmodel=kernel!"); */ + } + break; + + case R_X86_64_32S: + *(signed int *) loc += v; + break; + + case R_X86_64_16: + *(unsigned short *) loc += v; + break; + + case R_X86_64_8: + *(unsigned char *) loc += v; + break; + + case R_X86_64_PC32: + *(unsigned int *) loc += v - dot; + break; + + case R_X86_64_PC16: + *(unsigned short *) loc += v - dot; + break; + + case R_X86_64_PC8: + *(unsigned char *) loc += v - dot; + break; + + case R_X86_64_GLOB_DAT: + case R_X86_64_JUMP_SLOT: + *loc = v; + break; + + case R_X86_64_RELATIVE: + *loc += f->baseaddr; + break; + + case R_X86_64_GOT32: + case R_X86_64_GOTPCREL: + goto bb_use_got; +# if 0 + if (!isym->gotent.reloc_done) + { + isym->gotent.reloc_done = 1; + *(Elf64_Addr *)(ifile->got->contents + isym->gotent.offset) = v; + } + /* XXX are these really correct? */ + if (ELF64_R_TYPE(rel->r_info) == R_X86_64_GOTPCREL) + *(unsigned int *) loc += v + isym->gotent.offset; + else + *loc += isym->gotent.offset; + break; +# endif + +#else +# warning "no idea how to handle relocations on your arch" +#endif + + default: + printf("Warning: unhandled reloc %d\n",(int)ELF_R_TYPE(rel->r_info)); + ret = obj_reloc_unhandled; + break; + +#if defined(USE_PLT_ENTRIES) + +bb_use_plt: + + /* find the plt entry and initialize it if necessary */ + +#if defined(USE_PLT_LIST) + for (pe = isym->pltent; pe != NULL && pe->addend != rel->r_addend;) + pe = pe->next; +#else + pe = &isym->pltent; +#endif + + if (! pe->inited) { + ip = (unsigned long *) (ifile->plt->contents + pe->offset); + + /* generate some machine code */ + +#if defined(__arm__) + ip[0] = 0xe51ff004; /* ldr pc,[pc,#-4] */ + ip[1] = v; /* sym@ */ +#endif +#if defined(__powerpc__) + ip[0] = 0x3d600000 + ((v + 0x8000) >> 16); /* lis r11,sym@ha */ + ip[1] = 0x396b0000 + (v & 0xffff); /* addi r11,r11,sym@l */ + ip[2] = 0x7d6903a6; /* mtctr r11 */ + ip[3] = 0x4e800420; /* bctr */ +#endif +#if defined (__v850e__) + /* We have to trash a register, so we assume that any control + transfer more than 21-bits away must be a function call + (so we can use a call-clobbered register). */ + ip[0] = 0x0621 + ((v & 0xffff) << 16); /* mov sym, r1 ... */ + ip[1] = ((v >> 16) & 0xffff) + 0x610000; /* ...; jmp r1 */ +#endif + pe->inited = 1; + } + + /* relative distance to target */ + v -= dot; + /* if the target is too far away.... */ +#if defined (__arm__) || defined (__powerpc__) + if ((int)v < -0x02000000 || (int)v >= 0x02000000) +#elif defined (__v850e__) + if ((ElfW(Sword))v > 0x1fffff || (ElfW(Sword))v < (ElfW(Sword))-0x200000) +#endif + /* go via the plt */ + v = plt + pe->offset - dot; + +#if defined (__v850e__) + if (v & 1) +#else + if (v & 3) +#endif + ret = obj_reloc_dangerous; + + /* merge the offset into the instruction. */ +#if defined(__arm__) + /* Convert to words. */ + v >>= 2; + + *loc = (*loc & ~0x00ffffff) | ((v + *loc) & 0x00ffffff); +#endif +#if defined(__powerpc__) + *loc = (*loc & ~0x03fffffc) | (v & 0x03fffffc); +#endif +#if defined (__v850e__) + /* We write two shorts instead of a long because even 32-bit insns + only need half-word alignment, but the 32-bit data write needs + to be long-word aligned. */ + ((unsigned short *)loc)[0] = + (*(unsigned short *)loc & 0xffc0) /* opcode + reg */ + | ((v >> 16) & 0x3f); /* offs high part */ + ((unsigned short *)loc)[1] = + (v & 0xffff); /* offs low part */ +#endif + break; +#endif /* USE_PLT_ENTRIES */ + +#if defined(USE_GOT_ENTRIES) +bb_use_got: + + /* needs an entry in the .got: set it, once */ + if (!isym->gotent.inited) { + isym->gotent.inited = 1; + *(ElfW(Addr) *) (ifile->got->contents + isym->gotent.offset) = v; + } + /* make the reloc with_respect_to_.got */ +#if defined(__sh__) + *loc += isym->gotent.offset + rel->r_addend; +#elif defined(__i386__) || defined(__arm__) || defined(__mc68000__) + *loc += isym->gotent.offset; +#endif + break; + +#endif /* USE_GOT_ENTRIES */ + } + + return ret; +} + + +#if defined(USE_LIST) + +static int arch_list_add(ElfW(RelM) *rel, struct arch_list_entry **list, + int offset, int size) +{ + struct arch_list_entry *pe; + + for (pe = *list; pe != NULL; pe = pe->next) { + if (pe->addend == rel->r_addend) { + break; + } + } + + if (pe == NULL) { + pe = xmalloc(sizeof(struct arch_list_entry)); + pe->next = *list; + pe->addend = rel->r_addend; + pe->offset = offset; + pe->inited = 0; + *list = pe; + return size; + } + return 0; +} + +#endif + +#if defined(USE_SINGLE) + +static int arch_single_init(ElfW(RelM) *rel, struct arch_single_entry *single, + int offset, int size) +{ + if (single->allocated == 0) { + single->allocated = 1; + single->offset = offset; + single->inited = 0; + return size; + } + return 0; +} + +#endif + +#if defined(USE_GOT_ENTRIES) || defined(USE_PLT_ENTRIES) + +static struct obj_section *arch_xsect_init(struct obj_file *f, char *name, + int offset, int size) +{ + struct obj_section *myrelsec = obj_find_section(f, name); + + if (offset == 0) { + offset += size; + } + + if (myrelsec) { + obj_extend_section(myrelsec, offset); + } else { + myrelsec = obj_create_alloced_section(f, name, + size, offset); + } + + return myrelsec; +} + +#endif + +static void arch_create_got(struct obj_file *f) +{ +#if defined(USE_GOT_ENTRIES) || defined(USE_PLT_ENTRIES) + struct arch_file *ifile = (struct arch_file *) f; + int i; +#if defined(USE_GOT_ENTRIES) + int got_offset = 0, got_needed = 0, got_allocate; +#endif +#if defined(USE_PLT_ENTRIES) + int plt_offset = 0, plt_needed = 0, plt_allocate; +#endif + struct obj_section *relsec, *symsec, *strsec; + ElfW(RelM) *rel, *relend; + ElfW(Sym) *symtab, *extsym; + const char *strtab, *name; + struct arch_symbol *intsym; + + for (i = 0; i < f->header.e_shnum; ++i) { + relsec = f->sections[i]; + if (relsec->header.sh_type != SHT_RELM) + continue; + + symsec = f->sections[relsec->header.sh_link]; + strsec = f->sections[symsec->header.sh_link]; + + rel = (ElfW(RelM) *) relsec->contents; + relend = rel + (relsec->header.sh_size / sizeof(ElfW(RelM))); + symtab = (ElfW(Sym) *) symsec->contents; + strtab = (const char *) strsec->contents; + + for (; rel < relend; ++rel) { + extsym = &symtab[ELF_R_SYM(rel->r_info)]; + +#if defined(USE_GOT_ENTRIES) + got_allocate = 0; +#endif +#if defined(USE_PLT_ENTRIES) + plt_allocate = 0; +#endif + + switch (ELF_R_TYPE(rel->r_info)) { +#if defined(__arm__) + case R_ARM_PC24: + case R_ARM_PLT32: + plt_allocate = 1; + break; + + case R_ARM_GOTOFF: + case R_ARM_GOTPC: + got_needed = 1; + continue; + + case R_ARM_GOT32: + got_allocate = 1; + break; + +#elif defined(__i386__) + case R_386_GOTPC: + case R_386_GOTOFF: + got_needed = 1; + continue; + + case R_386_GOT32: + got_allocate = 1; + break; + +#elif defined(__powerpc__) + case R_PPC_REL24: + plt_allocate = 1; + break; + +#elif defined(__mc68000__) + case R_68K_GOT32: + got_allocate = 1; + break; + +#ifdef R_68K_GOTOFF + case R_68K_GOTOFF: + got_needed = 1; + continue; +#endif + +#elif defined(__sh__) + case R_SH_GOT32: + got_allocate = 1; + break; + + case R_SH_GOTPC: + case R_SH_GOTOFF: + got_needed = 1; + continue; + +#elif defined (__v850e__) + case R_V850_22_PCREL: + plt_needed = 1; + break; + +#endif + default: + continue; + } + + if (extsym->st_name != 0) { + name = strtab + extsym->st_name; + } else { + name = f->sections[extsym->st_shndx]->name; + } + intsym = (struct arch_symbol *) obj_find_symbol(f, name); +#if defined(USE_GOT_ENTRIES) + if (got_allocate) { + got_offset += arch_single_init( + rel, &intsym->gotent, + got_offset, GOT_ENTRY_SIZE); + + got_needed = 1; + } +#endif +#if defined(USE_PLT_ENTRIES) + if (plt_allocate) { +#if defined(USE_PLT_LIST) + plt_offset += arch_list_add( + rel, &intsym->pltent, + plt_offset, PLT_ENTRY_SIZE); +#else + plt_offset += arch_single_init( + rel, &intsym->pltent, + plt_offset, PLT_ENTRY_SIZE); +#endif + plt_needed = 1; + } +#endif + } + } + +#if defined(USE_GOT_ENTRIES) + if (got_needed) { + ifile->got = arch_xsect_init(f, ".got", got_offset, + GOT_ENTRY_SIZE); + } +#endif + +#if defined(USE_PLT_ENTRIES) + if (plt_needed) { + ifile->plt = arch_xsect_init(f, ".plt", plt_offset, + PLT_ENTRY_SIZE); + } +#endif + +#endif /* defined(USE_GOT_ENTRIES) || defined(USE_PLT_ENTRIES) */ +} + +/*======================================================================*/ + +/* Standard ELF hash function. */ +static unsigned long obj_elf_hash_n(const char *name, unsigned long n) +{ + unsigned long h = 0; + unsigned long g; + unsigned char ch; + + while (n > 0) { + ch = *name++; + h = (h << 4) + ch; + if ((g = (h & 0xf0000000)) != 0) { + h ^= g >> 24; + h &= ~g; + } + n--; + } + return h; +} + +static unsigned long obj_elf_hash(const char *name) +{ + return obj_elf_hash_n(name, strlen(name)); +} + +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING +/* String comparison for non-co-versioned kernel and module. */ + +static int ncv_strcmp(const char *a, const char *b) +{ + size_t alen = strlen(a), blen = strlen(b); + + if (blen == alen + 10 && b[alen] == '_' && b[alen + 1] == 'R') + return strncmp(a, b, alen); + else if (alen == blen + 10 && a[blen] == '_' && a[blen + 1] == 'R') + return strncmp(a, b, blen); + else + return strcmp(a, b); +} + +/* String hashing for non-co-versioned kernel and module. Here + we are simply forced to drop the crc from the hash. */ + +static unsigned long ncv_symbol_hash(const char *str) +{ + size_t len = strlen(str); + if (len > 10 && str[len - 10] == '_' && str[len - 9] == 'R') + len -= 10; + return obj_elf_hash_n(str, len); +} + +static void +obj_set_symbol_compare(struct obj_file *f, + int (*cmp) (const char *, const char *), + unsigned long (*hash) (const char *)) +{ + if (cmp) + f->symbol_cmp = cmp; + if (hash) { + struct obj_symbol *tmptab[HASH_BUCKETS], *sym, *next; + int i; + + f->symbol_hash = hash; + + memcpy(tmptab, f->symtab, sizeof(tmptab)); + memset(f->symtab, 0, sizeof(f->symtab)); + + for (i = 0; i < HASH_BUCKETS; ++i) + for (sym = tmptab[i]; sym; sym = next) { + unsigned long h = hash(sym->name) % HASH_BUCKETS; + next = sym->next; + sym->next = f->symtab[h]; + f->symtab[h] = sym; + } + } +} + +#endif /* FEATURE_INSMOD_VERSION_CHECKING */ + +static struct obj_symbol * +obj_add_symbol(struct obj_file *f, const char *name, + unsigned long symidx, int info, + int secidx, ElfW(Addr) value, + unsigned long size) +{ + struct obj_symbol *sym; + unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS; + int n_type = ELF_ST_TYPE(info); + int n_binding = ELF_ST_BIND(info); + + for (sym = f->symtab[hash]; sym; sym = sym->next) + if (f->symbol_cmp(sym->name, name) == 0) { + int o_secidx = sym->secidx; + int o_info = sym->info; + int o_type = ELF_ST_TYPE(o_info); + int o_binding = ELF_ST_BIND(o_info); + + /* A redefinition! Is it legal? */ + + if (secidx == SHN_UNDEF) + return sym; + else if (o_secidx == SHN_UNDEF) + goto found; + else if (n_binding == STB_GLOBAL && o_binding == STB_LOCAL) { + /* Cope with local and global symbols of the same name + in the same object file, as might have been created + by ld -r. The only reason locals are now seen at this + level at all is so that we can do semi-sensible things + with parameters. */ + + struct obj_symbol *nsym, **p; + + nsym = arch_new_symbol(); + nsym->next = sym->next; + nsym->ksymidx = -1; + + /* Excise the old (local) symbol from the hash chain. */ + for (p = &f->symtab[hash]; *p != sym; p = &(*p)->next) + continue; + *p = sym = nsym; + goto found; + } else if (n_binding == STB_LOCAL) { + /* Another symbol of the same name has already been defined. + Just add this to the local table. */ + sym = arch_new_symbol(); + sym->next = NULL; + sym->ksymidx = -1; + f->local_symtab[symidx] = sym; + goto found; + } else if (n_binding == STB_WEAK) + return sym; + else if (o_binding == STB_WEAK) + goto found; + /* Don't unify COMMON symbols with object types the programmer + doesn't expect. */ + else if (secidx == SHN_COMMON + && (o_type == STT_NOTYPE || o_type == STT_OBJECT)) + return sym; + else if (o_secidx == SHN_COMMON + && (n_type == STT_NOTYPE || n_type == STT_OBJECT)) + goto found; + else { + /* Don't report an error if the symbol is coming from + the kernel or some external module. */ + if (secidx <= SHN_HIRESERVE) + bb_error_msg("%s multiply defined", name); + return sym; + } + } + + /* Completely new symbol. */ + sym = arch_new_symbol(); + sym->next = f->symtab[hash]; + f->symtab[hash] = sym; + sym->ksymidx = -1; + + if (ELF_ST_BIND(info) == STB_LOCAL && symidx != -1) { + if (symidx >= f->local_symtab_size) + bb_error_msg("local symbol %s with index %ld exceeds local_symtab_size %ld", + name, (long) symidx, (long) f->local_symtab_size); + else + f->local_symtab[symidx] = sym; + } + +found: + sym->name = name; + sym->value = value; + sym->size = size; + sym->secidx = secidx; + sym->info = info; + + return sym; +} + +static struct obj_symbol * +obj_find_symbol(struct obj_file *f, const char *name) +{ + struct obj_symbol *sym; + unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS; + + for (sym = f->symtab[hash]; sym; sym = sym->next) + if (f->symbol_cmp(sym->name, name) == 0) + return sym; + + return NULL; +} + +static ElfW(Addr) obj_symbol_final_value(struct obj_file * f, struct obj_symbol * sym) +{ + if (sym) { + if (sym->secidx >= SHN_LORESERVE) + return sym->value; + + return sym->value + f->sections[sym->secidx]->header.sh_addr; + } else { + /* As a special case, a NULL sym has value zero. */ + return 0; + } +} + +static struct obj_section *obj_find_section(struct obj_file *f, const char *name) +{ + int i, n = f->header.e_shnum; + + for (i = 0; i < n; ++i) + if (strcmp(f->sections[i]->name, name) == 0) + return f->sections[i]; + + return NULL; +} + +static int obj_load_order_prio(struct obj_section *a) +{ + unsigned long af, ac; + + af = a->header.sh_flags; + + ac = 0; + if (a->name[0] != '.' || strlen(a->name) != 10 || + strcmp(a->name + 5, ".init")) + ac |= 32; + if (af & SHF_ALLOC) + ac |= 16; + if (!(af & SHF_WRITE)) + ac |= 8; + if (af & SHF_EXECINSTR) + ac |= 4; + if (a->header.sh_type != SHT_NOBITS) + ac |= 2; + + return ac; +} + +static void +obj_insert_section_load_order(struct obj_file *f, struct obj_section *sec) +{ + struct obj_section **p; + int prio = obj_load_order_prio(sec); + for (p = f->load_order_search_start; *p; p = &(*p)->load_next) + if (obj_load_order_prio(*p) < prio) + break; + sec->load_next = *p; + *p = sec; +} + +static struct obj_section *obj_create_alloced_section(struct obj_file *f, + const char *name, + unsigned long align, + unsigned long size) +{ + int newidx = f->header.e_shnum++; + struct obj_section *sec; + + f->sections = xrealloc(f->sections, (newidx + 1) * sizeof(sec)); + f->sections[newidx] = sec = arch_new_section(); + + memset(sec, 0, sizeof(*sec)); + sec->header.sh_type = SHT_PROGBITS; + sec->header.sh_flags = SHF_WRITE | SHF_ALLOC; + sec->header.sh_size = size; + sec->header.sh_addralign = align; + sec->name = name; + sec->idx = newidx; + if (size) + sec->contents = xmalloc(size); + + obj_insert_section_load_order(f, sec); + + return sec; +} + +static struct obj_section *obj_create_alloced_section_first(struct obj_file *f, + const char *name, + unsigned long align, + unsigned long size) +{ + int newidx = f->header.e_shnum++; + struct obj_section *sec; + + f->sections = xrealloc(f->sections, (newidx + 1) * sizeof(sec)); + f->sections[newidx] = sec = arch_new_section(); + + memset(sec, 0, sizeof(*sec)); + sec->header.sh_type = SHT_PROGBITS; + sec->header.sh_flags = SHF_WRITE | SHF_ALLOC; + sec->header.sh_size = size; + sec->header.sh_addralign = align; + sec->name = name; + sec->idx = newidx; + if (size) + sec->contents = xmalloc(size); + + sec->load_next = f->load_order; + f->load_order = sec; + if (f->load_order_search_start == &f->load_order) + f->load_order_search_start = &sec->load_next; + + return sec; +} + +static void *obj_extend_section(struct obj_section *sec, unsigned long more) +{ + unsigned long oldsize = sec->header.sh_size; + if (more) { + sec->contents = xrealloc(sec->contents, sec->header.sh_size += more); + } + return sec->contents + oldsize; +} + + +/* Conditionally add the symbols from the given symbol set to the + new module. */ + +static int +add_symbols_from( struct obj_file *f, + int idx, struct new_module_symbol *syms, size_t nsyms) +{ + struct new_module_symbol *s; + size_t i; + int used = 0; +#ifdef SYMBOL_PREFIX + char *name_buf = 0; + size_t name_alloced_size = 0; +#endif +#if ENABLE_FEATURE_CHECK_TAINTED_MODULE + int gpl; + + gpl = obj_gpl_license(f, NULL) == 0; +#endif + for (i = 0, s = syms; i < nsyms; ++i, ++s) { + /* Only add symbols that are already marked external. + If we override locals we may cause problems for + argument initialization. We will also create a false + dependency on the module. */ + struct obj_symbol *sym; + char *name; + + /* GPL licensed modules can use symbols exported with + * EXPORT_SYMBOL_GPL, so ignore any GPLONLY_ prefix on the + * exported names. Non-GPL modules never see any GPLONLY_ + * symbols so they cannot fudge it by adding the prefix on + * their references. + */ + if (strncmp((char *)s->name, "GPLONLY_", 8) == 0) { +#if ENABLE_FEATURE_CHECK_TAINTED_MODULE + if (gpl) + s->name += 8; + else +#endif + continue; + } + name = (char *)s->name; + +#ifdef SYMBOL_PREFIX + /* Prepend SYMBOL_PREFIX to the symbol's name (the + kernel exports `C names', but module object files + reference `linker names'). */ + size_t extra = sizeof SYMBOL_PREFIX; + size_t name_size = strlen (name) + extra; + if (name_size > name_alloced_size) { + name_alloced_size = name_size * 2; + name_buf = alloca (name_alloced_size); + } + strcpy (name_buf, SYMBOL_PREFIX); + strcpy (name_buf + extra - 1, name); + name = name_buf; +#endif /* SYMBOL_PREFIX */ + + sym = obj_find_symbol(f, name); + if (sym && !(ELF_ST_BIND(sym->info) == STB_LOCAL)) { +#ifdef SYMBOL_PREFIX + /* Put NAME_BUF into more permanent storage. */ + name = xmalloc (name_size); + strcpy (name, name_buf); +#endif + sym = obj_add_symbol(f, name, -1, + ELF_ST_INFO(STB_GLOBAL, + STT_NOTYPE), + idx, s->value, 0); + /* Did our symbol just get installed? If so, mark the + module as "used". */ + if (sym->secidx == idx) + used = 1; + } + } + + return used; +} + +static void add_kernel_symbols(struct obj_file *f) +{ + struct external_module *m; + int i, nused = 0; + + /* Add module symbols first. */ + + for (i = 0, m = ext_modules; i < n_ext_modules; ++i, ++m) + if (m->nsyms + && add_symbols_from(f, SHN_HIRESERVE + 2 + i, m->syms, + m->nsyms)) m->used = 1, ++nused; + + n_ext_modules_used = nused; + + /* And finally the symbols from the kernel proper. */ + + if (nksyms) + add_symbols_from(f, SHN_HIRESERVE + 1, ksyms, nksyms); +} + +static char *get_modinfo_value(struct obj_file *f, const char *key) +{ + struct obj_section *sec; + char *p, *v, *n, *ep; + size_t klen = strlen(key); + + sec = obj_find_section(f, ".modinfo"); + if (sec == NULL) + return NULL; + p = sec->contents; + ep = p + sec->header.sh_size; + while (p < ep) { + v = strchr(p, '='); + n = strchr(p, '\0'); + if (v) { + if (p + klen == v && strncmp(p, key, klen) == 0) + return v + 1; + } else { + if (p + klen == n && strcmp(p, key) == 0) + return n; + } + p = n + 1; + } + + return NULL; +} + + +/*======================================================================*/ +/* Functions relating to module loading after 2.1.18. */ + +static int +new_process_module_arguments(struct obj_file *f, int argc, char **argv) +{ + while (argc > 0) { + char *p, *q, *key, *sym_name; + struct obj_symbol *sym; + char *contents, *loc; + int min, max, n; + + p = *argv; + if ((q = strchr(p, '=')) == NULL) { + argc--; + continue; + } + + key = alloca(q - p + 6); + memcpy(key, "parm_", 5); + memcpy(key + 5, p, q - p); + key[q - p + 5] = 0; + + p = get_modinfo_value(f, key); + key += 5; + if (p == NULL) { + bb_error_msg("invalid parameter %s", key); + return 0; + } + +#ifdef SYMBOL_PREFIX + sym_name = alloca (strlen (key) + sizeof SYMBOL_PREFIX); + strcpy (sym_name, SYMBOL_PREFIX); + strcat (sym_name, key); +#else + sym_name = key; +#endif + sym = obj_find_symbol(f, sym_name); + + /* Also check that the parameter was not resolved from the kernel. */ + if (sym == NULL || sym->secidx > SHN_HIRESERVE) { + bb_error_msg("symbol for parameter %s not found", key); + return 0; + } + + if (isdigit(*p)) { + min = strtoul(p, &p, 10); + if (*p == '-') + max = strtoul(p + 1, &p, 10); + else + max = min; + } else + min = max = 1; + + contents = f->sections[sym->secidx]->contents; + loc = contents + sym->value; + n = (*++q != '\0'); + + while (1) { + if ((*p == 's') || (*p == 'c')) { + char *str; + + /* Do C quoting if we begin with a ", else slurp the lot. */ + if (*q == '"') { + char *r; + + str = alloca(strlen(q)); + for (r = str, q++; *q != '"'; ++q, ++r) { + if (*q == '\0') { + bb_error_msg("improperly terminated string argument for %s", + key); + return 0; + } else if (*q == '\\') + switch (*++q) { + case 'a': + *r = '\a'; + break; + case 'b': + *r = '\b'; + break; + case 'e': + *r = '\033'; + break; + case 'f': + *r = '\f'; + break; + case 'n': + *r = '\n'; + break; + case 'r': + *r = '\r'; + break; + case 't': + *r = '\t'; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int c = *q - '0'; + if (q[1] >= '0' && q[1] <= '7') { + c = (c * 8) + *++q - '0'; + if (q[1] >= '0' && q[1] <= '7') + c = (c * 8) + *++q - '0'; + } + *r = c; + } + break; + + default: + *r = *q; + break; + } else + *r = *q; + } + *r = '\0'; + ++q; + } else { + char *r; + + /* In this case, the string is not quoted. We will break + it using the coma (like for ints). If the user wants to + include comas in a string, he just has to quote it */ + + /* Search the next coma */ + r = strchr(q, ','); + + /* Found ? */ + if (r != (char *) NULL) { + /* Recopy the current field */ + str = alloca(r - q + 1); + memcpy(str, q, r - q); + + /* I don't know if it is useful, as the previous case + doesn't nul terminate the string ??? */ + str[r - q] = '\0'; + + /* Keep next fields */ + q = r; + } else { + /* last string */ + str = q; + q = ""; + } + } + + if (*p == 's') { + /* Normal string */ + obj_string_patch(f, sym->secidx, loc - contents, str); + loc += tgt_sizeof_char_p; + } else { + /* Array of chars (in fact, matrix !) */ + unsigned long charssize; /* size of each member */ + + /* Get the size of each member */ + /* Probably we should do that outside the loop ? */ + if (!isdigit(*(p + 1))) { + bb_error_msg("parameter type 'c' for %s must be followed by" + " the maximum size", key); + return 0; + } + charssize = strtoul(p + 1, (char **) NULL, 10); + + /* Check length */ + if (strlen(str) >= charssize) { + bb_error_msg("string too long for %s (max %ld)", key, + charssize - 1); + return 0; + } + + /* Copy to location */ + strcpy((char *) loc, str); + loc += charssize; + } + } else { + long v = strtoul(q, &q, 0); + switch (*p) { + case 'b': + *loc++ = v; + break; + case 'h': + *(short *) loc = v; + loc += tgt_sizeof_short; + break; + case 'i': + *(int *) loc = v; + loc += tgt_sizeof_int; + break; + case 'l': + *(long *) loc = v; + loc += tgt_sizeof_long; + break; + + default: + bb_error_msg("unknown parameter type '%c' for %s", *p, key); + return 0; + } + } + +retry_end_of_value: + switch (*q) { + case '\0': + goto end_of_arg; + + case ' ': + case '\t': + case '\n': + case '\r': + ++q; + goto retry_end_of_value; + + case ',': + if (++n > max) { + bb_error_msg("too many values for %s (max %d)", key, max); + return 0; + } + ++q; + break; + + default: + bb_error_msg("invalid argument syntax for %s", key); + return 0; + } + } + +end_of_arg: + if (n < min) { + bb_error_msg("too few values for %s (min %d)", key, min); + return 0; + } + + argc--, argv++; + } + + return 1; +} + +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING +static int new_is_module_checksummed(struct obj_file *f) +{ + const char *p = get_modinfo_value(f, "using_checksums"); + if (p) + return xatoi(p); + else + return 0; +} + +/* Get the module's kernel version in the canonical integer form. */ + +static int +new_get_module_version(struct obj_file *f, char str[STRVERSIONLEN]) +{ + char *p, *q; + int a, b, c; + + p = get_modinfo_value(f, "kernel_version"); + if (p == NULL) + return -1; + safe_strncpy(str, p, STRVERSIONLEN); + + a = strtoul(p, &p, 10); + if (*p != '.') + return -1; + b = strtoul(p + 1, &p, 10); + if (*p != '.') + return -1; + c = strtoul(p + 1, &q, 10); + if (p + 1 == q) + return -1; + + return a << 16 | b << 8 | c; +} + +#endif /* FEATURE_INSMOD_VERSION_CHECKING */ + + +/* Fetch the loaded modules, and all currently exported symbols. */ + +static int new_get_kernel_symbols(void) +{ + char *module_names, *mn; + struct external_module *modules, *m; + struct new_module_symbol *syms, *s; + size_t ret, bufsize, nmod, nsyms, i, j; + + /* Collect the loaded modules. */ + + module_names = xmalloc(bufsize = 256); +retry_modules_load: + if (query_module(NULL, QM_MODULES, module_names, bufsize, &ret)) { + if (errno == ENOSPC && bufsize < ret) { + module_names = xrealloc(module_names, bufsize = ret); + goto retry_modules_load; + } + bb_perror_msg("QM_MODULES"); + return 0; + } + + n_ext_modules = nmod = ret; + + /* Collect the modules' symbols. */ + + if (nmod) { + ext_modules = modules = xmalloc(nmod * sizeof(*modules)); + memset(modules, 0, nmod * sizeof(*modules)); + for (i = 0, mn = module_names, m = modules; + i < nmod; ++i, ++m, mn += strlen(mn) + 1) { + struct new_module_info info; + + if (query_module(mn, QM_INFO, &info, sizeof(info), &ret)) { + if (errno == ENOENT) { + /* The module was removed out from underneath us. */ + continue; + } + bb_perror_msg("query_module: QM_INFO: %s", mn); + return 0; + } + + syms = xmalloc(bufsize = 1024); +retry_mod_sym_load: + if (query_module(mn, QM_SYMBOLS, syms, bufsize, &ret)) { + switch (errno) { + case ENOSPC: + syms = xrealloc(syms, bufsize = ret); + goto retry_mod_sym_load; + case ENOENT: + /* The module was removed out from underneath us. */ + continue; + default: + bb_perror_msg("query_module: QM_SYMBOLS: %s", mn); + return 0; + } + } + nsyms = ret; + + m->name = mn; + m->addr = info.addr; + m->nsyms = nsyms; + m->syms = syms; + + for (j = 0, s = syms; j < nsyms; ++j, ++s) { + s->name += (unsigned long) syms; + } + } + } + + /* Collect the kernel's symbols. */ + + syms = xmalloc(bufsize = 16 * 1024); +retry_kern_sym_load: + if (query_module(NULL, QM_SYMBOLS, syms, bufsize, &ret)) { + if (errno == ENOSPC && bufsize < ret) { + syms = xrealloc(syms, bufsize = ret); + goto retry_kern_sym_load; + } + bb_perror_msg("kernel: QM_SYMBOLS"); + return 0; + } + nksyms = nsyms = ret; + ksyms = syms; + + for (j = 0, s = syms; j < nsyms; ++j, ++s) { + s->name += (unsigned long) syms; + } + return 1; +} + + +/* Return the kernel symbol checksum version, or zero if not used. */ + +static int new_is_kernel_checksummed(void) +{ + struct new_module_symbol *s; + size_t i; + + /* Using_Versions is not the first symbol, but it should be in there. */ + + for (i = 0, s = ksyms; i < nksyms; ++i, ++s) + if (strcmp((char *) s->name, "Using_Versions") == 0) + return s->value; + + return 0; +} + + +static int new_create_this_module(struct obj_file *f, const char *m_name) +{ + struct obj_section *sec; + + sec = obj_create_alloced_section_first(f, ".this", tgt_sizeof_long, + sizeof(struct new_module)); + memset(sec->contents, 0, sizeof(struct new_module)); + + obj_add_symbol(f, SPFX "__this_module", -1, + ELF_ST_INFO(STB_LOCAL, STT_OBJECT), sec->idx, 0, + sizeof(struct new_module)); + + obj_string_patch(f, sec->idx, offsetof(struct new_module, name), + m_name); + + return 1; +} + +#if ENABLE_FEATURE_INSMOD_KSYMOOPS_SYMBOLS +/* add an entry to the __ksymtab section, creating it if necessary */ +static void new_add_ksymtab(struct obj_file *f, struct obj_symbol *sym) +{ + struct obj_section *sec; + ElfW(Addr) ofs; + + /* ensure __ksymtab is allocated, EXPORT_NOSYMBOLS creates a non-alloc section. + * If __ksymtab is defined but not marked alloc, x out the first character + * (no obj_delete routine) and create a new __ksymtab with the correct + * characteristics. + */ + sec = obj_find_section(f, "__ksymtab"); + if (sec && !(sec->header.sh_flags & SHF_ALLOC)) { + *((char *)(sec->name)) = 'x'; /* override const */ + sec = NULL; + } + if (!sec) + sec = obj_create_alloced_section(f, "__ksymtab", + tgt_sizeof_void_p, 0); + if (!sec) + return; + sec->header.sh_flags |= SHF_ALLOC; + /* Empty section might be byte-aligned */ + sec->header.sh_addralign = tgt_sizeof_void_p; + ofs = sec->header.sh_size; + obj_symbol_patch(f, sec->idx, ofs, sym); + obj_string_patch(f, sec->idx, ofs + tgt_sizeof_void_p, sym->name); + obj_extend_section(sec, 2 * tgt_sizeof_char_p); +} +#endif /* FEATURE_INSMOD_KSYMOOPS_SYMBOLS */ + +static int new_create_module_ksymtab(struct obj_file *f) +{ + struct obj_section *sec; + int i; + + /* We must always add the module references. */ + + if (n_ext_modules_used) { + struct new_module_ref *dep; + struct obj_symbol *tm; + + sec = obj_create_alloced_section(f, ".kmodtab", tgt_sizeof_void_p, + (sizeof(struct new_module_ref) + * n_ext_modules_used)); + if (!sec) + return 0; + + tm = obj_find_symbol(f, SPFX "__this_module"); + dep = (struct new_module_ref *) sec->contents; + for (i = 0; i < n_ext_modules; ++i) + if (ext_modules[i].used) { + dep->dep = ext_modules[i].addr; + obj_symbol_patch(f, sec->idx, + (char *) &dep->ref - sec->contents, tm); + dep->next_ref = 0; + ++dep; + } + } + + if (!flag_noexport && !obj_find_section(f, "__ksymtab")) { + size_t nsyms; + int *loaded; + + sec = obj_create_alloced_section(f, "__ksymtab", tgt_sizeof_void_p, 0); + + /* We don't want to export symbols residing in sections that + aren't loaded. There are a number of these created so that + we make sure certain module options don't appear twice. */ + + loaded = alloca(sizeof(int) * (i = f->header.e_shnum)); + while (--i >= 0) + loaded[i] = (f->sections[i]->header.sh_flags & SHF_ALLOC) != 0; + + for (nsyms = i = 0; i < HASH_BUCKETS; ++i) { + struct obj_symbol *sym; + for (sym = f->symtab[i]; sym; sym = sym->next) + if (ELF_ST_BIND(sym->info) != STB_LOCAL + && sym->secidx <= SHN_HIRESERVE + && (sym->secidx >= SHN_LORESERVE + || loaded[sym->secidx])) { + ElfW(Addr) ofs = nsyms * 2 * tgt_sizeof_void_p; + + obj_symbol_patch(f, sec->idx, ofs, sym); + obj_string_patch(f, sec->idx, ofs + tgt_sizeof_void_p, + sym->name); + + nsyms++; + } + } + + obj_extend_section(sec, nsyms * 2 * tgt_sizeof_char_p); + } + + return 1; +} + + +static int +new_init_module(const char *m_name, struct obj_file *f, unsigned long m_size) +{ + struct new_module *module; + struct obj_section *sec; + void *image; + int ret; + tgt_long m_addr; + + sec = obj_find_section(f, ".this"); + if (!sec || !sec->contents) { + bb_perror_msg_and_die("corrupt module %s?",m_name); + } + module = (struct new_module *) sec->contents; + m_addr = sec->header.sh_addr; + + module->size_of_struct = sizeof(*module); + module->size = m_size; + module->flags = flag_autoclean ? NEW_MOD_AUTOCLEAN : 0; + + sec = obj_find_section(f, "__ksymtab"); + if (sec && sec->header.sh_size) { + module->syms = sec->header.sh_addr; + module->nsyms = sec->header.sh_size / (2 * tgt_sizeof_char_p); + } + + if (n_ext_modules_used) { + sec = obj_find_section(f, ".kmodtab"); + module->deps = sec->header.sh_addr; + module->ndeps = n_ext_modules_used; + } + + module->init = + obj_symbol_final_value(f, obj_find_symbol(f, SPFX "init_module")); + module->cleanup = + obj_symbol_final_value(f, obj_find_symbol(f, SPFX "cleanup_module")); + + sec = obj_find_section(f, "__ex_table"); + if (sec) { + module->ex_table_start = sec->header.sh_addr; + module->ex_table_end = sec->header.sh_addr + sec->header.sh_size; + } + + sec = obj_find_section(f, ".text.init"); + if (sec) { + module->runsize = sec->header.sh_addr - m_addr; + } + sec = obj_find_section(f, ".data.init"); + if (sec) { + if (!module->runsize || + module->runsize > sec->header.sh_addr - m_addr) + module->runsize = sec->header.sh_addr - m_addr; + } + sec = obj_find_section(f, ARCHDATA_SEC_NAME); + if (sec && sec->header.sh_size) { + module->archdata_start = (void*)sec->header.sh_addr; + module->archdata_end = module->archdata_start + sec->header.sh_size; + } + sec = obj_find_section(f, KALLSYMS_SEC_NAME); + if (sec && sec->header.sh_size) { + module->kallsyms_start = (void*)sec->header.sh_addr; + module->kallsyms_end = module->kallsyms_start + sec->header.sh_size; + } + + /* Whew! All of the initialization is complete. Collect the final + module image and give it to the kernel. */ + + image = xmalloc(m_size); + obj_create_image(f, image); + + ret = init_module(m_name, (struct new_module *) image); + if (ret) + bb_perror_msg("init_module: %s", m_name); + + free(image); + + return ret == 0; +} + + +/*======================================================================*/ + +static int +obj_string_patch(struct obj_file *f, int secidx, ElfW(Addr) offset, + const char *string) +{ + struct obj_string_patch *p; + struct obj_section *strsec; + size_t len = strlen(string) + 1; + char *loc; + + p = xmalloc(sizeof(*p)); + p->next = f->string_patches; + p->reloc_secidx = secidx; + p->reloc_offset = offset; + f->string_patches = p; + + strsec = obj_find_section(f, ".kstrtab"); + if (strsec == NULL) { + strsec = obj_create_alloced_section(f, ".kstrtab", 1, len); + p->string_offset = 0; + loc = strsec->contents; + } else { + p->string_offset = strsec->header.sh_size; + loc = obj_extend_section(strsec, len); + } + memcpy(loc, string, len); + + return 1; +} + +static int +obj_symbol_patch(struct obj_file *f, int secidx, ElfW(Addr) offset, + struct obj_symbol *sym) +{ + struct obj_symbol_patch *p; + + p = xmalloc(sizeof(*p)); + p->next = f->symbol_patches; + p->reloc_secidx = secidx; + p->reloc_offset = offset; + p->sym = sym; + f->symbol_patches = p; + + return 1; +} + +static int obj_check_undefineds(struct obj_file *f) +{ + unsigned long i; + int ret = 1; + + for (i = 0; i < HASH_BUCKETS; ++i) { + struct obj_symbol *sym; + for (sym = f->symtab[i]; sym; sym = sym->next) + if (sym->secidx == SHN_UNDEF) { + if (ELF_ST_BIND(sym->info) == STB_WEAK) { + sym->secidx = SHN_ABS; + sym->value = 0; + } else { + if (!flag_quiet) { + bb_error_msg("unresolved symbol %s", sym->name); + } + ret = 0; + } + } + } + + return ret; +} + +static void obj_allocate_commons(struct obj_file *f) +{ + struct common_entry { + struct common_entry *next; + struct obj_symbol *sym; + } *common_head = NULL; + + unsigned long i; + + for (i = 0; i < HASH_BUCKETS; ++i) { + struct obj_symbol *sym; + for (sym = f->symtab[i]; sym; sym = sym->next) + if (sym->secidx == SHN_COMMON) { + /* Collect all COMMON symbols and sort them by size so as to + minimize space wasted by alignment requirements. */ + { + struct common_entry **p, *n; + for (p = &common_head; *p; p = &(*p)->next) + if (sym->size <= (*p)->sym->size) + break; + + n = alloca(sizeof(*n)); + n->next = *p; + n->sym = sym; + *p = n; + } + } + } + + for (i = 1; i < f->local_symtab_size; ++i) { + struct obj_symbol *sym = f->local_symtab[i]; + if (sym && sym->secidx == SHN_COMMON) { + struct common_entry **p, *n; + for (p = &common_head; *p; p = &(*p)->next) + if (sym == (*p)->sym) + break; + else if (sym->size < (*p)->sym->size) { + n = alloca(sizeof(*n)); + n->next = *p; + n->sym = sym; + *p = n; + break; + } + } + } + + if (common_head) { + /* Find the bss section. */ + for (i = 0; i < f->header.e_shnum; ++i) + if (f->sections[i]->header.sh_type == SHT_NOBITS) + break; + + /* If for some reason there hadn't been one, create one. */ + if (i == f->header.e_shnum) { + struct obj_section *sec; + + f->sections = xrealloc(f->sections, (i + 1) * sizeof(sec)); + f->sections[i] = sec = arch_new_section(); + f->header.e_shnum = i + 1; + + memset(sec, 0, sizeof(*sec)); + sec->header.sh_type = SHT_PROGBITS; + sec->header.sh_flags = SHF_WRITE | SHF_ALLOC; + sec->name = ".bss"; + sec->idx = i; + } + + /* Allocate the COMMONS. */ + { + ElfW(Addr) bss_size = f->sections[i]->header.sh_size; + ElfW(Addr) max_align = f->sections[i]->header.sh_addralign; + struct common_entry *c; + + for (c = common_head; c; c = c->next) { + ElfW(Addr) align = c->sym->value; + + if (align > max_align) + max_align = align; + if (bss_size & (align - 1)) + bss_size = (bss_size | (align - 1)) + 1; + + c->sym->secidx = i; + c->sym->value = bss_size; + + bss_size += c->sym->size; + } + + f->sections[i]->header.sh_size = bss_size; + f->sections[i]->header.sh_addralign = max_align; + } + } + + /* For the sake of patch relocation and parameter initialization, + allocate zeroed data for NOBITS sections now. Note that after + this we cannot assume NOBITS are really empty. */ + for (i = 0; i < f->header.e_shnum; ++i) { + struct obj_section *s = f->sections[i]; + if (s->header.sh_type == SHT_NOBITS) { + if (s->header.sh_size != 0) + s->contents = memset(xmalloc(s->header.sh_size), + 0, s->header.sh_size); + else + s->contents = NULL; + + s->header.sh_type = SHT_PROGBITS; + } + } +} + +static unsigned long obj_load_size(struct obj_file *f) +{ + unsigned long dot = 0; + struct obj_section *sec; + + /* Finalize the positions of the sections relative to one another. */ + + for (sec = f->load_order; sec; sec = sec->load_next) { + ElfW(Addr) align; + + align = sec->header.sh_addralign; + if (align && (dot & (align - 1))) + dot = (dot | (align - 1)) + 1; + + sec->header.sh_addr = dot; + dot += sec->header.sh_size; + } + + return dot; +} + +static int obj_relocate(struct obj_file *f, ElfW(Addr) base) +{ + int i, n = f->header.e_shnum; + int ret = 1; + + /* Finalize the addresses of the sections. */ + + f->baseaddr = base; + for (i = 0; i < n; ++i) + f->sections[i]->header.sh_addr += base; + + /* And iterate over all of the relocations. */ + + for (i = 0; i < n; ++i) { + struct obj_section *relsec, *symsec, *targsec, *strsec; + ElfW(RelM) * rel, *relend; + ElfW(Sym) * symtab; + const char *strtab; + + relsec = f->sections[i]; + if (relsec->header.sh_type != SHT_RELM) + continue; + + symsec = f->sections[relsec->header.sh_link]; + targsec = f->sections[relsec->header.sh_info]; + strsec = f->sections[symsec->header.sh_link]; + + rel = (ElfW(RelM) *) relsec->contents; + relend = rel + (relsec->header.sh_size / sizeof(ElfW(RelM))); + symtab = (ElfW(Sym) *) symsec->contents; + strtab = (const char *) strsec->contents; + + for (; rel < relend; ++rel) { + ElfW(Addr) value = 0; + struct obj_symbol *intsym = NULL; + unsigned long symndx; + ElfW(Sym) * extsym = 0; + const char *errmsg; + + /* Attempt to find a value to use for this relocation. */ + + symndx = ELF_R_SYM(rel->r_info); + if (symndx) { + /* Note we've already checked for undefined symbols. */ + + extsym = &symtab[symndx]; + if (ELF_ST_BIND(extsym->st_info) == STB_LOCAL) { + /* Local symbols we look up in the local table to be sure + we get the one that is really intended. */ + intsym = f->local_symtab[symndx]; + } else { + /* Others we look up in the hash table. */ + const char *name; + if (extsym->st_name) + name = strtab + extsym->st_name; + else + name = f->sections[extsym->st_shndx]->name; + intsym = obj_find_symbol(f, name); + } + + value = obj_symbol_final_value(f, intsym); + intsym->referenced = 1; + } +#if SHT_RELM == SHT_RELA +#if defined(__alpha__) && defined(AXP_BROKEN_GAS) + /* Work around a nasty GAS bug, that is fixed as of 2.7.0.9. */ + if (!extsym || !extsym->st_name || + ELF_ST_BIND(extsym->st_info) != STB_LOCAL) +#endif + value += rel->r_addend; +#endif + + /* Do it! */ + switch (arch_apply_relocation + (f, targsec, symsec, intsym, rel, value) + ) { + case obj_reloc_ok: + break; + + case obj_reloc_overflow: + errmsg = "Relocation overflow"; + goto bad_reloc; + case obj_reloc_dangerous: + errmsg = "Dangerous relocation"; + goto bad_reloc; + case obj_reloc_unhandled: + errmsg = "Unhandled relocation"; +bad_reloc: + if (extsym) { + bb_error_msg("%s of type %ld for %s", errmsg, + (long) ELF_R_TYPE(rel->r_info), + strtab + extsym->st_name); + } else { + bb_error_msg("%s of type %ld", errmsg, + (long) ELF_R_TYPE(rel->r_info)); + } + ret = 0; + break; + } + } + } + + /* Finally, take care of the patches. */ + + if (f->string_patches) { + struct obj_string_patch *p; + struct obj_section *strsec; + ElfW(Addr) strsec_base; + strsec = obj_find_section(f, ".kstrtab"); + strsec_base = strsec->header.sh_addr; + + for (p = f->string_patches; p; p = p->next) { + struct obj_section *targsec = f->sections[p->reloc_secidx]; + *(ElfW(Addr) *) (targsec->contents + p->reloc_offset) + = strsec_base + p->string_offset; + } + } + + if (f->symbol_patches) { + struct obj_symbol_patch *p; + + for (p = f->symbol_patches; p; p = p->next) { + struct obj_section *targsec = f->sections[p->reloc_secidx]; + *(ElfW(Addr) *) (targsec->contents + p->reloc_offset) + = obj_symbol_final_value(f, p->sym); + } + } + + return ret; +} + +static int obj_create_image(struct obj_file *f, char *image) +{ + struct obj_section *sec; + ElfW(Addr) base = f->baseaddr; + + for (sec = f->load_order; sec; sec = sec->load_next) { + char *secimg; + + if (sec->contents == 0 || sec->header.sh_size == 0) + continue; + + secimg = image + (sec->header.sh_addr - base); + + /* Note that we allocated data for NOBITS sections earlier. */ + memcpy(secimg, sec->contents, sec->header.sh_size); + } + + return 1; +} + +/*======================================================================*/ + +static struct obj_file *obj_load(FILE * fp, int loadprogbits) +{ + struct obj_file *f; + ElfW(Shdr) * section_headers; + int shnum, i; + char *shstrtab; + + /* Read the file header. */ + + f = arch_new_file(); + memset(f, 0, sizeof(*f)); + f->symbol_cmp = strcmp; + f->symbol_hash = obj_elf_hash; + f->load_order_search_start = &f->load_order; + + fseek(fp, 0, SEEK_SET); + if (fread(&f->header, sizeof(f->header), 1, fp) != 1) { + bb_perror_msg("error reading ELF header"); + return NULL; + } + + if (f->header.e_ident[EI_MAG0] != ELFMAG0 + || f->header.e_ident[EI_MAG1] != ELFMAG1 + || f->header.e_ident[EI_MAG2] != ELFMAG2 + || f->header.e_ident[EI_MAG3] != ELFMAG3) { + bb_error_msg("not an ELF file"); + return NULL; + } + if (f->header.e_ident[EI_CLASS] != ELFCLASSM + || f->header.e_ident[EI_DATA] != (BB_BIG_ENDIAN + ? ELFDATA2MSB : ELFDATA2LSB) + || f->header.e_ident[EI_VERSION] != EV_CURRENT + || !MATCH_MACHINE(f->header.e_machine)) { + bb_error_msg("ELF file not for this architecture"); + return NULL; + } + if (f->header.e_type != ET_REL) { + bb_error_msg("ELF file not a relocatable object"); + return NULL; + } + + /* Read the section headers. */ + + if (f->header.e_shentsize != sizeof(ElfW(Shdr))) { + bb_error_msg("section header size mismatch: %lu != %lu", + (unsigned long) f->header.e_shentsize, + (unsigned long) sizeof(ElfW(Shdr))); + return NULL; + } + + shnum = f->header.e_shnum; + f->sections = xmalloc(sizeof(struct obj_section *) * shnum); + memset(f->sections, 0, sizeof(struct obj_section *) * shnum); + + section_headers = alloca(sizeof(ElfW(Shdr)) * shnum); + fseek(fp, f->header.e_shoff, SEEK_SET); + if (fread(section_headers, sizeof(ElfW(Shdr)), shnum, fp) != shnum) { + bb_perror_msg("error reading ELF section headers"); + return NULL; + } + + /* Read the section data. */ + + for (i = 0; i < shnum; ++i) { + struct obj_section *sec; + + f->sections[i] = sec = arch_new_section(); + memset(sec, 0, sizeof(*sec)); + + sec->header = section_headers[i]; + sec->idx = i; + + if(sec->header.sh_size) { + switch (sec->header.sh_type) { + case SHT_NULL: + case SHT_NOTE: + case SHT_NOBITS: + /* ignore */ + break; + + case SHT_PROGBITS: +#if LOADBITS + if (!loadprogbits) { + sec->contents = NULL; + break; + } +#endif + case SHT_SYMTAB: + case SHT_STRTAB: + case SHT_RELM: + if (sec->header.sh_size > 0) { + sec->contents = xmalloc(sec->header.sh_size); + fseek(fp, sec->header.sh_offset, SEEK_SET); + if (fread(sec->contents, sec->header.sh_size, 1, fp) != 1) { + bb_perror_msg("error reading ELF section data"); + return NULL; + } + } else { + sec->contents = NULL; + } + break; + +#if SHT_RELM == SHT_REL + case SHT_RELA: + bb_error_msg("RELA relocations not supported on this architecture"); + return NULL; +#else + case SHT_REL: + bb_error_msg("REL relocations not supported on this architecture"); + return NULL; +#endif + + default: + if (sec->header.sh_type >= SHT_LOPROC) { + /* Assume processor specific section types are debug + info and can safely be ignored. If this is ever not + the case (Hello MIPS?), don't put ifdefs here but + create an arch_load_proc_section(). */ + break; + } + + bb_error_msg("can't handle sections of type %ld", + (long) sec->header.sh_type); + return NULL; + } + } + } + + /* Do what sort of interpretation as needed by each section. */ + + shstrtab = f->sections[f->header.e_shstrndx]->contents; + + for (i = 0; i < shnum; ++i) { + struct obj_section *sec = f->sections[i]; + sec->name = shstrtab + sec->header.sh_name; + } + + for (i = 0; i < shnum; ++i) { + struct obj_section *sec = f->sections[i]; + + /* .modinfo should be contents only but gcc has no attribute for that. + * The kernel may have marked .modinfo as ALLOC, ignore this bit. + */ + if (strcmp(sec->name, ".modinfo") == 0) + sec->header.sh_flags &= ~SHF_ALLOC; + + if (sec->header.sh_flags & SHF_ALLOC) + obj_insert_section_load_order(f, sec); + + switch (sec->header.sh_type) { + case SHT_SYMTAB: + { + unsigned long nsym, j; + char *strtab; + ElfW(Sym) * sym; + + if (sec->header.sh_entsize != sizeof(ElfW(Sym))) { + bb_error_msg("symbol size mismatch: %lu != %lu", + (unsigned long) sec->header.sh_entsize, + (unsigned long) sizeof(ElfW(Sym))); + return NULL; + } + + nsym = sec->header.sh_size / sizeof(ElfW(Sym)); + strtab = f->sections[sec->header.sh_link]->contents; + sym = (ElfW(Sym) *) sec->contents; + + /* Allocate space for a table of local symbols. */ + j = f->local_symtab_size = sec->header.sh_info; + f->local_symtab = xzalloc(j * sizeof(struct obj_symbol *)); + + /* Insert all symbols into the hash table. */ + for (j = 1, ++sym; j < nsym; ++j, ++sym) { + ElfW(Addr) val = sym->st_value; + const char *name; + if (sym->st_name) + name = strtab + sym->st_name; + else if (sym->st_shndx < shnum) + name = f->sections[sym->st_shndx]->name; + else + continue; +#if defined(__SH5__) + /* + * For sh64 it is possible that the target of a branch + * requires a mode switch (32 to 16 and back again). + * + * This is implied by the lsb being set in the target + * address for SHmedia mode and clear for SHcompact. + */ + val |= sym->st_other & 4; +#endif + + obj_add_symbol(f, name, j, sym->st_info, sym->st_shndx, + val, sym->st_size); + } + } + break; + + case SHT_RELM: + if (sec->header.sh_entsize != sizeof(ElfW(RelM))) { + bb_error_msg("relocation entry size mismatch: %lu != %lu", + (unsigned long) sec->header.sh_entsize, + (unsigned long) sizeof(ElfW(RelM))); + return NULL; + } + break; + /* XXX Relocation code from modutils-2.3.19 is not here. + * Why? That's about 20 lines of code from obj/obj_load.c, + * which gets done in a second pass through the sections. + * This BusyBox insmod does similar work in obj_relocate(). */ + } + } + + return f; +} + +#if ENABLE_FEATURE_INSMOD_LOADINKMEM +/* + * load the unloaded sections directly into the memory allocated by + * kernel for the module + */ + +static int obj_load_progbits(FILE * fp, struct obj_file* f, char* imagebase) +{ + ElfW(Addr) base = f->baseaddr; + struct obj_section* sec; + + for (sec = f->load_order; sec; sec = sec->load_next) { + + /* section already loaded? */ + if (sec->contents != NULL) + continue; + + if (sec->header.sh_size == 0) + continue; + + sec->contents = imagebase + (sec->header.sh_addr - base); + fseek(fp, sec->header.sh_offset, SEEK_SET); + if (fread(sec->contents, sec->header.sh_size, 1, fp) != 1) { + bb_perror_msg("error reading ELF section data"); + return 0; + } + + } + return 1; +} +#endif + +static void hide_special_symbols(struct obj_file *f) +{ + static const char *const specials[] = { + SPFX "cleanup_module", + SPFX "init_module", + SPFX "kernel_version", + NULL + }; + + struct obj_symbol *sym; + const char *const *p; + + for (p = specials; *p; ++p) { + sym = obj_find_symbol(f, *p); + if (sym != NULL) + sym->info = ELF_ST_INFO(STB_LOCAL, ELF_ST_TYPE(sym->info)); + } +} + + +#if ENABLE_FEATURE_CHECK_TAINTED_MODULE +static int obj_gpl_license(struct obj_file *f, const char **license) +{ + struct obj_section *sec; + /* This list must match *exactly* the list of allowable licenses in + * linux/include/linux/module.h. Checking for leading "GPL" will not + * work, somebody will use "GPL sucks, this is proprietary". + */ + static const char * const gpl_licenses[] = { + "GPL", + "GPL v2", + "GPL and additional rights", + "Dual BSD/GPL", + "Dual MPL/GPL", + }; + + sec = obj_find_section(f, ".modinfo"); + if (sec) { + const char *value, *ptr, *endptr; + ptr = sec->contents; + endptr = ptr + sec->header.sh_size; + while (ptr < endptr) { + value = strchr(ptr, '='); + if (value && strncmp(ptr, "license", value-ptr) == 0) { + int i; + if (license) + *license = value+1; + for (i = 0; i < sizeof(gpl_licenses)/sizeof(gpl_licenses[0]); ++i) { + if (strcmp(value+1, gpl_licenses[i]) == 0) + return 0; + } + return 2; + } + if (strchr(ptr, '\0')) + ptr = strchr(ptr, '\0') + 1; + else + ptr = endptr; + } + } + return 1; +} + +#define TAINT_FILENAME "/proc/sys/kernel/tainted" +#define TAINT_PROPRIETORY_MODULE (1<<0) +#define TAINT_FORCED_MODULE (1<<1) +#define TAINT_UNSAFE_SMP (1<<2) +#define TAINT_URL "http://www.tux.org/lkml/#export-tainted" + +static void set_tainted(struct obj_file *f, int fd, char *m_name, + int kernel_has_tainted, int taint, const char *text1, const char *text2) +{ + char buf[80]; + int oldval; + static int first = 1; + if (fd < 0 && !kernel_has_tainted) + return; /* New modutils on old kernel */ + printf("Warning: loading %s will taint the kernel: %s%s\n", + m_name, text1, text2); + if (first) { + printf(" See %s for information about tainted modules\n", TAINT_URL); + first = 0; + } + if (fd >= 0) { + read(fd, buf, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + oldval = strtoul(buf, NULL, 10); + sprintf(buf, "%d\n", oldval | taint); + write(fd, buf, strlen(buf)); + } +} + +/* Check if loading this module will taint the kernel. */ +static void check_tainted_module(struct obj_file *f, char *m_name) +{ + static const char tainted_file[] = TAINT_FILENAME; + int fd, kernel_has_tainted; + const char *ptr; + + kernel_has_tainted = 1; + fd = open(tainted_file, O_RDWR); + if (fd < 0) { + if (errno == ENOENT) + kernel_has_tainted = 0; + else if (errno == EACCES) + kernel_has_tainted = 1; + else { + perror(tainted_file); + kernel_has_tainted = 0; + } + } + + switch (obj_gpl_license(f, &ptr)) { + case 0: + break; + case 1: + set_tainted(f, fd, m_name, kernel_has_tainted, TAINT_PROPRIETORY_MODULE, "no license", ""); + break; + case 2: + /* The module has a non-GPL license so we pretend that the + * kernel always has a taint flag to get a warning even on + * kernels without the proc flag. + */ + set_tainted(f, fd, m_name, 1, TAINT_PROPRIETORY_MODULE, "non-GPL license - ", ptr); + break; + default: + set_tainted(f, fd, m_name, 1, TAINT_PROPRIETORY_MODULE, "Unexpected return from obj_gpl_license", ""); + break; + } + + if (flag_force_load) + set_tainted(f, fd, m_name, 1, TAINT_FORCED_MODULE, "forced load", ""); + + if (fd >= 0) + close(fd); +} +#else /* FEATURE_CHECK_TAINTED_MODULE */ +#define check_tainted_module(x, y) do { } while(0); +#endif /* FEATURE_CHECK_TAINTED_MODULE */ + +#if ENABLE_FEATURE_INSMOD_KSYMOOPS_SYMBOLS +/* add module source, timestamp, kernel version and a symbol for the + * start of some sections. this info is used by ksymoops to do better + * debugging. + */ +static int +get_module_version(struct obj_file *f, char str[STRVERSIONLEN]) +{ +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING + return new_get_module_version(f, str); +#else /* FEATURE_INSMOD_VERSION_CHECKING */ + strncpy(str, "???", sizeof(str)); + return -1; +#endif /* FEATURE_INSMOD_VERSION_CHECKING */ +} + +/* add module source, timestamp, kernel version and a symbol for the + * start of some sections. this info is used by ksymoops to do better + * debugging. + */ +static void +add_ksymoops_symbols(struct obj_file *f, const char *filename, + const char *m_name) +{ + static const char symprefix[] = "__insmod_"; + struct obj_section *sec; + struct obj_symbol *sym; + char *name, *absolute_filename; + char str[STRVERSIONLEN], real[PATH_MAX]; + int i, l, lm_name, lfilename, use_ksymtab, version; + struct stat statbuf; + + static const char *section_names[] = { + ".text", + ".rodata", + ".data", + ".bss", + ".sbss" + }; + + if (realpath(filename, real)) { + absolute_filename = xstrdup(real); + } + else { + int save_errno = errno; + bb_error_msg("cannot get realpath for %s", filename); + errno = save_errno; + perror(""); + absolute_filename = xstrdup(filename); + } + + lm_name = strlen(m_name); + lfilename = strlen(absolute_filename); + + /* add to ksymtab if it already exists or there is no ksymtab and other symbols + * are not to be exported. otherwise leave ksymtab alone for now, the + * "export all symbols" compatibility code will export these symbols later. + */ + use_ksymtab = obj_find_section(f, "__ksymtab") || flag_noexport; + + if ((sec = obj_find_section(f, ".this"))) { + /* tag the module header with the object name, last modified + * timestamp and module version. worst case for module version + * is 0xffffff, decimal 16777215. putting all three fields in + * one symbol is less readable but saves kernel space. + */ + l = sizeof(symprefix)+ /* "__insmod_" */ + lm_name+ /* module name */ + 2+ /* "_O" */ + lfilename+ /* object filename */ + 2+ /* "_M" */ + 2*sizeof(statbuf.st_mtime)+ /* mtime in hex */ + 2+ /* "_V" */ + 8+ /* version in dec */ + 1; /* nul */ + name = xmalloc(l); + if (stat(absolute_filename, &statbuf) != 0) + statbuf.st_mtime = 0; + version = get_module_version(f, str); /* -1 if not found */ + snprintf(name, l, "%s%s_O%s_M%0*lX_V%d", + symprefix, m_name, absolute_filename, + (int)(2*sizeof(statbuf.st_mtime)), statbuf.st_mtime, + version); + sym = obj_add_symbol(f, name, -1, + ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE), + sec->idx, sec->header.sh_addr, 0); + if (use_ksymtab) + new_add_ksymtab(f, sym); + } + free(absolute_filename); +#ifdef _NOT_SUPPORTED_ + /* record where the persistent data is going, same address as previous symbol */ + + if (f->persist) { + l = sizeof(symprefix)+ /* "__insmod_" */ + lm_name+ /* module name */ + 2+ /* "_P" */ + strlen(f->persist)+ /* data store */ + 1; /* nul */ + name = xmalloc(l); + snprintf(name, l, "%s%s_P%s", + symprefix, m_name, f->persist); + sym = obj_add_symbol(f, name, -1, ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE), + sec->idx, sec->header.sh_addr, 0); + if (use_ksymtab) + new_add_ksymtab(f, sym); + } +#endif /* _NOT_SUPPORTED_ */ + /* tag the desired sections if size is non-zero */ + + for (i = 0; i < sizeof(section_names)/sizeof(section_names[0]); ++i) { + if ((sec = obj_find_section(f, section_names[i])) && + sec->header.sh_size) { + l = sizeof(symprefix)+ /* "__insmod_" */ + lm_name+ /* module name */ + 2+ /* "_S" */ + strlen(sec->name)+ /* section name */ + 2+ /* "_L" */ + 8+ /* length in dec */ + 1; /* nul */ + name = xmalloc(l); + snprintf(name, l, "%s%s_S%s_L%ld", + symprefix, m_name, sec->name, + (long)sec->header.sh_size); + sym = obj_add_symbol(f, name, -1, ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE), + sec->idx, sec->header.sh_addr, 0); + if (use_ksymtab) + new_add_ksymtab(f, sym); + } + } +} +#endif /* FEATURE_INSMOD_KSYMOOPS_SYMBOLS */ + +#if ENABLE_FEATURE_INSMOD_LOAD_MAP +static void print_load_map(struct obj_file *f) +{ + struct obj_symbol *sym; + struct obj_symbol **all, **p; + struct obj_section *sec; + int i, nsyms, *loaded; + + /* Report on the section layout. */ + + printf("Sections: Size %-*s Align\n", + (int) (2 * sizeof(void *)), "Address"); + + for (sec = f->load_order; sec; sec = sec->load_next) { + int a; + unsigned long tmp; + + for (a = -1, tmp = sec->header.sh_addralign; tmp; ++a) + tmp >>= 1; + if (a == -1) + a = 0; + + printf("%-15s %08lx %0*lx 2**%d\n", + sec->name, + (long)sec->header.sh_size, + (int) (2 * sizeof(void *)), + (long)sec->header.sh_addr, + a); + } +#if ENABLE_FEATURE_INSMOD_LOAD_MAP_FULL + /* Quick reference which section indicies are loaded. */ + + loaded = alloca(sizeof(int) * (i = f->header.e_shnum)); + while (--i >= 0) + loaded[i] = (f->sections[i]->header.sh_flags & SHF_ALLOC) != 0; + + /* Collect the symbols we'll be listing. */ + + for (nsyms = i = 0; i < HASH_BUCKETS; ++i) + for (sym = f->symtab[i]; sym; sym = sym->next) + if (sym->secidx <= SHN_HIRESERVE + && (sym->secidx >= SHN_LORESERVE || loaded[sym->secidx])) + ++nsyms; + + all = alloca(nsyms * sizeof(struct obj_symbol *)); + + for (i = 0, p = all; i < HASH_BUCKETS; ++i) + for (sym = f->symtab[i]; sym; sym = sym->next) + if (sym->secidx <= SHN_HIRESERVE + && (sym->secidx >= SHN_LORESERVE || loaded[sym->secidx])) + *p++ = sym; + + /* And list them. */ + printf("\nSymbols:\n"); + for (p = all; p < all + nsyms; ++p) { + char type = '?'; + unsigned long value; + + sym = *p; + if (sym->secidx == SHN_ABS) { + type = 'A'; + value = sym->value; + } else if (sym->secidx == SHN_UNDEF) { + type = 'U'; + value = 0; + } else { + sec = f->sections[sym->secidx]; + + if (sec->header.sh_type == SHT_NOBITS) + type = 'B'; + else if (sec->header.sh_flags & SHF_ALLOC) { + if (sec->header.sh_flags & SHF_EXECINSTR) + type = 'T'; + else if (sec->header.sh_flags & SHF_WRITE) + type = 'D'; + else + type = 'R'; + } + value = sym->value + sec->header.sh_addr; + } + + if (ELF_ST_BIND(sym->info) == STB_LOCAL) + type = tolower(type); + + printf("%0*lx %c %s\n", (int) (2 * sizeof(void *)), value, + type, sym->name); + } +#endif +} +#else /* !FEATURE_INSMOD_LOAD_MAP */ +void print_load_map(struct obj_file *f); +#endif + +int insmod_main( int argc, char **argv) +{ + char *opt_o, *arg1; + int len; + int k_crcs; + char *tmp, *tmp1; + unsigned long m_size; + ElfW(Addr) m_addr; + struct obj_file *f; + struct stat st; + char *m_name = 0; + int exit_status = EXIT_FAILURE; + int m_has_modinfo; +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING + struct utsname uts_info; + char m_strversion[STRVERSIONLEN]; + int m_version, m_crcs; +#endif +#if ENABLE_FEATURE_CLEAN_UP + FILE *fp = 0; +#else + FILE *fp; +#endif + int k_version = 0; + struct utsname myuname; + + /* Parse any options */ + getopt32(argc, argv, OPTION_STR, &opt_o); + arg1 = argv[optind]; + if (option_mask32 & OPT_o) { // -o /* name the output module */ + free(m_name); + m_name = xstrdup(opt_o); + } + + if (arg1 == NULL) { + bb_show_usage(); + } + + /* Grab the module name */ + tmp1 = xstrdup(arg1); + tmp = basename(tmp1); + len = strlen(tmp); + + if (uname(&myuname) == 0) { + if (myuname.release[0] == '2') { + k_version = myuname.release[2] - '0'; + } + } + +#if ENABLE_FEATURE_2_6_MODULES + if (k_version > 4 && len > 3 && tmp[len - 3] == '.' + && tmp[len - 2] == 'k' && tmp[len - 1] == 'o' + ) { + len -= 3; + tmp[len] = '\0'; + } else +#endif + if (len > 2 && tmp[len - 2] == '.' && tmp[len - 1] == 'o') { + len -= 2; + tmp[len] = '\0'; + } + + +#if ENABLE_FEATURE_2_6_MODULES + if (k_version > 4) + m_fullName = xasprintf("%s.ko", tmp); + else +#endif + m_fullName = xasprintf("%s.o", tmp); + + if (!m_name) { + m_name = tmp; + } else { + free(tmp1); + tmp1 = 0; /* flag for free(m_name) before exit() */ + } + + /* Get a filedesc for the module. Check we we have a complete path */ + if (stat(arg1, &st) < 0 || !S_ISREG(st.st_mode) + || (fp = fopen(arg1, "r")) == NULL + ) { + /* Hmm. Could not open it. First search under /lib/modules/`uname -r`, + * but do not error out yet if we fail to find it... */ + if (k_version) { /* uname succeedd */ + char *module_dir; + char *tmdn; + char real_module_dir[FILENAME_MAX]; + + tmdn = concat_path_file(_PATH_MODULES, myuname.release); + /* Jump through hoops in case /lib/modules/`uname -r` + * is a symlink. We do not want recursive_action to + * follow symlinks, but we do want to follow the + * /lib/modules/`uname -r` dir, So resolve it ourselves + * if it is a link... */ + if (realpath(tmdn, real_module_dir) == NULL) + module_dir = tmdn; + else + module_dir = real_module_dir; + recursive_action(module_dir, TRUE, FALSE, FALSE, + check_module_name_match, 0, m_fullName, 0); + free(tmdn); + } + + /* Check if we have found anything yet */ + if (m_filename == 0 || ((fp = fopen(m_filename, "r")) == NULL)) { + char module_dir[FILENAME_MAX]; + + free(m_filename); + m_filename = 0; + if (realpath (_PATH_MODULES, module_dir) == NULL) + strcpy(module_dir, _PATH_MODULES); + /* No module found under /lib/modules/`uname -r`, this + * time cast the net a bit wider. Search /lib/modules/ */ + if (!recursive_action(module_dir, TRUE, FALSE, FALSE, + check_module_name_match, 0, m_fullName, 0) + ) { + if (m_filename == 0 + || ((fp = fopen(m_filename, "r")) == NULL) + ) { + bb_error_msg("%s: no module by that name found", m_fullName); + goto out; + } + } else + bb_error_msg_and_die("%s: no module by that name found", m_fullName); + } + } else + m_filename = xstrdup(arg1); + + if (flag_verbose) + printf("Using %s\n", m_filename); + +#if ENABLE_FEATURE_2_6_MODULES + if (k_version > 4) { + argv[optind] = m_filename; + optind--; + return insmod_ng_main(argc - optind, argv + optind); + } +#endif + + f = obj_load(fp, LOADBITS); + if (f == NULL) + bb_perror_msg_and_die("cannot load the module"); + + if (get_modinfo_value(f, "kernel_version") == NULL) + m_has_modinfo = 0; + else + m_has_modinfo = 1; + +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING + /* Version correspondence? */ + if (!flag_quiet) { + if (uname(&uts_info) < 0) + uts_info.release[0] = '\0'; + if (m_has_modinfo) { + m_version = new_get_module_version(f, m_strversion); + if (m_version == -1) { + bb_error_msg("cannot find the kernel version the module was " + "compiled for"); + goto out; + } + } + + if (strncmp(uts_info.release, m_strversion, STRVERSIONLEN) != 0) { + if (flag_force_load) { + bb_error_msg("warning: kernel-module version mismatch\n" + "\t%s was compiled for kernel version %s\n" + "\twhile this kernel is version %s", + m_filename, m_strversion, uts_info.release); + } else { + bb_error_msg("kernel-module version mismatch\n" + "\t%s was compiled for kernel version %s\n" + "\twhile this kernel is version %s.", + m_filename, m_strversion, uts_info.release); + goto out; + } + } + } + k_crcs = 0; +#endif /* FEATURE_INSMOD_VERSION_CHECKING */ + + if (!query_module(NULL, 0, NULL, 0, NULL)) { + if (!new_get_kernel_symbols()) + goto out; + k_crcs = new_is_kernel_checksummed(); + } else { + bb_error_msg("not configured to support old kernels"); + goto out; + } + +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING + m_crcs = 0; + if (m_has_modinfo) + m_crcs = new_is_module_checksummed(f); + + if (m_crcs != k_crcs) + obj_set_symbol_compare(f, ncv_strcmp, ncv_symbol_hash); +#endif /* FEATURE_INSMOD_VERSION_CHECKING */ + + /* Let the module know about the kernel symbols. */ + add_kernel_symbols(f); + + /* Allocate common symbols, symbol tables, and string tables. */ + + if (!new_create_this_module(f, m_name)) { + goto out; + } + + if (!obj_check_undefineds(f)) { + goto out; + } + obj_allocate_commons(f); + check_tainted_module(f, m_name); + + /* done with the module name, on to the optional var=value arguments */ + ++optind; + if (optind < argc) { + if (!new_process_module_arguments(f, argc - optind, argv + optind)) { + goto out; + } + } + + arch_create_got(f); + hide_special_symbols(f); + +#if ENABLE_FEATURE_INSMOD_KSYMOOPS_SYMBOLS + add_ksymoops_symbols(f, m_filename, m_name); +#endif /* FEATURE_INSMOD_KSYMOOPS_SYMBOLS */ + + new_create_module_ksymtab(f); + + /* Find current size of the module */ + m_size = obj_load_size(f); + + + m_addr = create_module(m_name, m_size); + if (m_addr == -1) switch (errno) { + case EEXIST: + bb_error_msg("a module named %s already exists", m_name); + goto out; + case ENOMEM: + bb_error_msg("can't allocate kernel memory for module; needed %lu bytes", + m_size); + goto out; + default: + bb_perror_msg("create_module: %s", m_name); + goto out; + } + +#if !LOADBITS + /* + * the PROGBITS section was not loaded by the obj_load + * now we can load them directly into the kernel memory + */ + if (!obj_load_progbits(fp, f, (char*)m_addr)) { + delete_module(m_name); + goto out; + } +#endif + + if (!obj_relocate(f, m_addr)) { + delete_module(m_name); + goto out; + } + + if (!new_init_module(m_name, f, m_size)) { + delete_module(m_name); + goto out; + } + + if(flag_print_load_map) + print_load_map(f); + + exit_status = EXIT_SUCCESS; + +out: +#if ENABLE_FEATURE_CLEAN_UP + if(fp) + fclose(fp); + free(tmp1); + if(!tmp1) + free(m_name); + free(m_filename); +#endif + return exit_status; +} + + +#endif + + +#if ENABLE_FEATURE_2_6_MODULES + +#include +#include +#include + +/* We use error numbers in a loose translation... */ +static const char *moderror(int err) +{ + switch (err) { + case ENOEXEC: + return "Invalid module format"; + case ENOENT: + return "Unknown symbol in module"; + case ESRCH: + return "Module has wrong symbol version"; + case EINVAL: + return "Invalid parameters"; + default: + return strerror(err); + } +} + +int insmod_ng_main(int argc, char **argv) +{ + long ret; + size_t len; + void *map; + char *filename, *options; + + filename = *++argv; + if (!filename) + bb_show_usage(); + + /* Rest is options */ + options = xstrdup(""); + while (*++argv) { + int optlen = strlen(options); + options = xrealloc(options, optlen + 2 + strlen(*argv) + 2); + /* Spaces handled by "" pairs, but no way of escaping quotes */ + sprintf(options + optlen, (strchr(*argv,' ') ? "\"%s\" " : "%s "), *argv); + } + +#if 0 + /* Any special reason why mmap? It isn't performace critical... */ + int fd; + struct stat st; + unsigned long len; + fd = xopen(filename, O_RDONLY); + fstat(fd, &st); + len = st.st_size; + map = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + bb_perror_msg_and_die("cannot mmap '%s'", filename); + } + + /* map == NULL on Blackfin, probably on other MMU-less systems too. Workaround. */ + if (map == NULL) { + map = xmalloc(len); + xread(fd, map, len); + } +#else + len = MAXINT(ssize_t); + map = xmalloc_open_read_close(filename, &len); +#endif + + ret = syscall(__NR_init_module, map, len, options); + if (ret != 0) { + bb_perror_msg_and_die("cannot insert '%s': %s (%li)", + filename, moderror(errno), ret); + } + + return 0; +} + +#endif diff --git a/modutils/lsmod.c b/modutils/lsmod.c new file mode 100644 index 000000000..ffde3d829 --- /dev/null +++ b/modutils/lsmod.c @@ -0,0 +1,188 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini lsmod implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Modified by Alcove, Julien Gaulmin and + * Nicolas Ferre to support pre 2.1 kernels + * (which lack the query_module() interface). + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + + +#ifndef CONFIG_FEATURE_CHECK_TAINTED_MODULE +static void check_tainted(void) { puts(""); } +#else +#define TAINT_FILENAME "/proc/sys/kernel/tainted" +#define TAINT_PROPRIETORY_MODULE (1<<0) +#define TAINT_FORCED_MODULE (1<<1) +#define TAINT_UNSAFE_SMP (1<<2) + +static void check_tainted(void) +{ + int tainted; + FILE *f; + + tainted = 0; + if ((f = fopen(TAINT_FILENAME, "r"))) { + fscanf(f, "%d", &tainted); + fclose(f); + } + if (f && tainted) { + printf(" Tainted: %c%c%c\n", + tainted & TAINT_PROPRIETORY_MODULE ? 'P' : 'G', + tainted & TAINT_FORCED_MODULE ? 'F' : ' ', + tainted & TAINT_UNSAFE_SMP ? 'S' : ' '); + } + else { + printf(" Not tainted\n"); + } +} +#endif + +#ifdef CONFIG_FEATURE_QUERY_MODULE_INTERFACE + +struct module_info +{ + unsigned long addr; + unsigned long size; + unsigned long flags; + long usecount; +}; + + +int query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret); + +enum { +/* Values for query_module's which. */ + QM_MODULES = 1, + QM_DEPS = 2, + QM_REFS = 3, + QM_SYMBOLS = 4, + QM_INFO = 5, + +/* Bits of module.flags. */ + NEW_MOD_RUNNING = 1, + NEW_MOD_DELETED = 2, + NEW_MOD_AUTOCLEAN = 4, + NEW_MOD_VISITED = 8, + NEW_MOD_USED_ONCE = 16, + NEW_MOD_INITIALIZING = 64 +}; + +int lsmod_main(int argc, char **argv) +{ + struct module_info info; + char *module_names, *mn, *deps, *dn; + size_t bufsize, depsize, nmod, count, i, j; + + module_names = deps = NULL; + bufsize = depsize = 0; + while(query_module(NULL, QM_MODULES, module_names, bufsize, &nmod)) { + if (errno != ENOSPC) bb_perror_msg_and_die("QM_MODULES"); + module_names = xmalloc(bufsize = nmod); + } + + deps = xmalloc(depsize = 256); + printf("Module\t\t\tSize Used by"); + check_tainted(); + + for (i = 0, mn = module_names; i < nmod; mn += strlen(mn) + 1, i++) { + if (query_module(mn, QM_INFO, &info, sizeof(info), &count)) { + if (errno == ENOENT) { + /* The module was removed out from underneath us. */ + continue; + } + /* else choke */ + bb_perror_msg_and_die("module %s: QM_INFO", mn); + } + while (query_module(mn, QM_REFS, deps, depsize, &count)) { + if (errno == ENOENT) { + /* The module was removed out from underneath us. */ + continue; + } else if (errno != ENOSPC) + bb_perror_msg_and_die("module %s: QM_REFS", mn); + deps = xrealloc(deps, count); + } + printf("%-20s%8lu%4ld", mn, info.size, info.usecount); + if (info.flags & NEW_MOD_DELETED) + printf(" (deleted)"); + else if (info.flags & NEW_MOD_INITIALIZING) + printf(" (initializing)"); + else if (!(info.flags & NEW_MOD_RUNNING)) + printf(" (uninitialized)"); + else { + if (info.flags & NEW_MOD_AUTOCLEAN) + printf(" (autoclean) "); + if (!(info.flags & NEW_MOD_USED_ONCE)) + printf(" (unused)"); + } + if (count) printf(" ["); + for (j = 0, dn = deps; j < count; dn += strlen(dn) + 1, j++) { + printf("%s%s", dn, (j==count-1)? "":" "); + } + if (count) printf("]"); + + puts(""); + } + +#ifdef CONFIG_FEATURE_CLEAN_UP + free(module_names); +#endif + + return 0; +} + +#else /* CONFIG_FEATURE_QUERY_MODULE_INTERFACE */ + +int lsmod_main(int argc, char **argv) +{ + FILE *file = xfopen("/proc/modules", "r"); + + printf("Module Size Used by"); + check_tainted(); +#if defined(CONFIG_FEATURE_LSMOD_PRETTY_2_6_OUTPUT) + { + char *line; + while ((line = xmalloc_fgets(file)) != NULL) { + char *tok; + + tok = strtok(line, " \t"); + printf("%-19s", tok); + tok = strtok(NULL, " \t\n"); + printf(" %8s", tok); + tok = strtok(NULL, " \t\n"); + /* Null if no module unloading support. */ + if (tok) { + printf(" %s", tok); + tok = strtok(NULL, "\n"); + if (!tok) + tok = ""; + /* New-style has commas, or -. If so, + truncate (other fields might follow). */ + else if (strchr(tok, ',')) { + tok = strtok(tok, "\t "); + /* Strip trailing comma. */ + if (tok[strlen(tok)-1] == ',') + tok[strlen(tok)-1] = '\0'; + } else if (tok[0] == '-' + && (tok[1] == '\0' || isspace(tok[1]))) + tok = ""; + printf(" %s", tok); + } + puts(""); + free(line); + } + fclose(file); + } +#else + xprint_and_close_file(file); +#endif /* CONFIG_FEATURE_2_6_MODULES */ + return EXIT_SUCCESS; +} + +#endif /* CONFIG_FEATURE_QUERY_MODULE_INTERFACE */ diff --git a/modutils/modprobe.c b/modutils/modprobe.c new file mode 100644 index 000000000..dcab493f1 --- /dev/null +++ b/modutils/modprobe.c @@ -0,0 +1,905 @@ +/* vi: set sw=4 ts=4: */ +/* + * Modprobe written from scratch for BusyBox + * + * Copyright (c) 2002 by Robert Griebl, griebl@gmx.de + * Copyright (c) 2003 by Andrew Dennison, andrew.dennison@motec.com.au + * Copyright (c) 2005 by Jim Bauer, jfbauer@nfr.com + * + * Portions Copyright (c) 2005 by Yann E. MORIN, yann.morin.1998@anciens.enib.fr + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. +*/ + +#include "busybox.h" +#include +#include + +struct mod_opt_t { /* one-way list of options to pass to a module */ + char * m_opt_val; + struct mod_opt_t * m_next; +}; + +struct dep_t { /* one-way list of dependency rules */ + /* a dependency rule */ + char * m_name; /* the module name*/ + char * m_path; /* the module file path */ + struct mod_opt_t * m_options; /* the module options */ + + int m_isalias : 1; /* the module is an alias */ + int m_reserved : 15; /* stuffin' */ + + int m_depcnt : 16; /* the number of dependable module(s) */ + char ** m_deparr; /* the list of dependable module(s) */ + + struct dep_t * m_next; /* the next dependency rule */ +}; + +struct mod_list_t { /* two-way list of modules to process */ + /* a module description */ + char * m_name; + char * m_path; + struct mod_opt_t * m_options; + + struct mod_list_t * m_prev; + struct mod_list_t * m_next; +}; + + +static struct dep_t *depend; + +#define main_options "acdklnqrst:vVC:" +#define INSERT_ALL 1 /* a */ +#define DUMP_CONF_EXIT 2 /* c */ +#define D_OPT_IGNORED 4 /* d */ +#define AUTOCLEAN_FLG 8 /* k */ +#define LIST_ALL 16 /* l */ +#define SHOW_ONLY 32 /* n */ +#define QUIET 64 /* q */ +#define REMOVE_OPT 128 /* r */ +#define DO_SYSLOG 256 /* s */ +#define RESTRICT_DIR 512 /* t */ +#define VERBOSE 1024 /* v */ +#define VERSION_ONLY 2048 /* V */ +#define CONFIG_FILE 4096 /* C */ + +#define autoclean (main_opts & AUTOCLEAN_FLG) +#define show_only (main_opts & SHOW_ONLY) +#define quiet (main_opts & QUIET) +#define remove_opt (main_opts & REMOVE_OPT) +#define do_syslog (main_opts & DO_SYSLOG) +#define verbose (main_opts & VERBOSE) + +static int main_opts; + +static int parse_tag_value(char *buffer, char **ptag, char **pvalue) +{ + char *tag, *value; + + buffer = skip_whitespace(buffer); + tag = value = buffer; + while (!isspace(*value)) + if (!*value) return 0; + else value++; + *value++ = 0; + value = skip_whitespace(value); + if (!*value) return 0; + + *ptag = tag; + *pvalue = value; + + return 1; +} + +/* + * This function appends an option to a list + */ +static struct mod_opt_t *append_option(struct mod_opt_t *opt_list, char *opt) +{ + struct mod_opt_t *ol = opt_list; + + if (ol) { + while (ol->m_next) { + ol = ol->m_next; + } + ol->m_next = xmalloc(sizeof(struct mod_opt_t)); + ol = ol->m_next; + } else { + ol = opt_list = xmalloc(sizeof(struct mod_opt_t)); + } + + ol->m_opt_val = xstrdup(opt); + ol->m_next = NULL; + + return opt_list; +} + +#if ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS +/* static char* parse_command_string(char* src, char **dst); + * src: pointer to string containing argument + * dst: pointer to where to store the parsed argument + * return value: the pointer to the first char after the parsed argument, + * NULL if there was no argument parsed (only trailing spaces). + * Note that memory is allocated with xstrdup when a new argument was + * parsed. Don't forget to free it! + */ +#define ARG_EMPTY 0x00 +#define ARG_IN_DQUOTES 0x01 +#define ARG_IN_SQUOTES 0x02 +static char *parse_command_string(char *src, char **dst) +{ + int opt_status = ARG_EMPTY; + char* tmp_str; + + /* Dumb you, I have nothing to do... */ + if (src == NULL) return src; + + /* Skip leading spaces */ + while (*src == ' ') { + src++; + } + /* Is the end of string reached? */ + if (*src == '\0') { + return NULL; + } + /* Reached the start of an argument + * By the way, we duplicate a little too much + * here but what is too much is freed later. */ + *dst = tmp_str = xstrdup(src); + /* Get to the end of that argument */ + while (*tmp_str != '\0' + && (*tmp_str != ' ' || (opt_status & (ARG_IN_DQUOTES | ARG_IN_SQUOTES))) + ) { + switch (*tmp_str) { + case '\'': + if (opt_status & ARG_IN_DQUOTES) { + /* Already in double quotes, keep current char as is */ + } else { + /* shift left 1 char, until end of string: get rid of the opening/closing quotes */ + memmove(tmp_str, tmp_str + 1, strlen(tmp_str)); + /* mark me: we enter or leave single quotes */ + opt_status ^= ARG_IN_SQUOTES; + /* Back one char, as we need to re-scan the new char there. */ + tmp_str--; + } + break; + case '"': + if (opt_status & ARG_IN_SQUOTES) { + /* Already in single quotes, keep current char as is */ + } else { + /* shift left 1 char, until end of string: get rid of the opening/closing quotes */ + memmove(tmp_str, tmp_str + 1, strlen(tmp_str)); + /* mark me: we enter or leave double quotes */ + opt_status ^= ARG_IN_DQUOTES; + /* Back one char, as we need to re-scan the new char there. */ + tmp_str--; + } + break; + case '\\': + if (opt_status & ARG_IN_SQUOTES) { + /* Between single quotes: keep as is. */ + } else { + switch (*(tmp_str+1)) { + case 'a': + case 'b': + case 't': + case 'n': + case 'v': + case 'f': + case 'r': + case '0': + /* We escaped a special character. For now, keep + * both the back-slash and the following char. */ + tmp_str++; src++; + break; + default: + /* We escaped a space or a single or double quote, + * or a back-slash, or a non-escapable char. Remove + * the '\' and keep the new current char as is. */ + memmove(tmp_str, tmp_str + 1, strlen(tmp_str)); + break; + } + } + break; + /* Any other char that is special shall appear here. + * Example: $ starts a variable + case '$': + do_variable_expansion(); + break; + * */ + default: + /* any other char is kept as is. */ + break; + } + tmp_str++; /* Go to next char */ + src++; /* Go to next char to find the end of the argument. */ + } + /* End of string, but still no ending quote */ + if (opt_status & (ARG_IN_DQUOTES | ARG_IN_SQUOTES)) { + bb_error_msg_and_die("unterminated (single or double) quote in options list: %s", src); + } + *tmp_str++ = '\0'; + *dst = xrealloc(*dst, (tmp_str - *dst)); + return src; +} +#else +#define parse_command_string(src, dst) (0) +#endif /* ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS */ + +/* + * This function reads aliases and default module options from a configuration file + * (/etc/modprobe.conf syntax). It supports includes (only files, no directories). + */ +static void include_conf(struct dep_t **first, struct dep_t **current, char *buffer, int buflen, int fd) +{ + int continuation_line = 0; + + // alias parsing is not 100% correct (no correct handling of continuation lines within an alias) ! + + while (reads(fd, buffer, buflen)) { + int l; + char *p; + + p = strchr(buffer, '#'); + if (p) + *p = 0; + + l = strlen(buffer); + + while (l && isspace(buffer[l-1])) { + buffer[l-1] = 0; + l--; + } + + if (l == 0) { + continuation_line = 0; + continue; + } + + if (!continuation_line) { + if ((strncmp(buffer, "alias", 5) == 0) && isspace(buffer[5])) { + char *alias, *mod; + + if (parse_tag_value(buffer + 6, &alias, &mod)) { + /* handle alias as a module dependent on the aliased module */ + if (!*current) { + (*first) = (*current) = xzalloc(sizeof(struct dep_t)); + } + else { + (*current)->m_next = xzalloc(sizeof(struct dep_t)); + (*current) = (*current)->m_next; + } + (*current)->m_name = xstrdup(alias); + (*current)->m_isalias = 1; + + if ((strcmp(mod, "off") == 0) || (strcmp(mod, "null") == 0)) { + (*current)->m_depcnt = 0; + (*current)->m_deparr = 0; + } + else { + (*current)->m_depcnt = 1; + (*current)->m_deparr = xmalloc(1 * sizeof(char *)); + (*current)->m_deparr[0] = xstrdup(mod); + } + (*current)->m_next = 0; + } + } + else if ((strncmp(buffer, "options", 7) == 0) && isspace(buffer[7])) { + char *mod, *opt; + + /* split the line in the module/alias name, and options */ + if (parse_tag_value(buffer + 8, &mod, &opt)) { + struct dep_t *dt; + + /* find the corresponding module */ + for (dt = *first; dt; dt = dt->m_next) { + if (strcmp(dt->m_name, mod) == 0) + break; + } + if (dt) { + if (ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS) { + char* new_opt = NULL; + while ((opt = parse_command_string(opt, &new_opt))) { + dt->m_options = append_option(dt->m_options, new_opt); + } + } else { + dt->m_options = append_option(dt->m_options, opt); + } + } + } + } + else if ((strncmp(buffer, "include", 7) == 0) && isspace(buffer[7])) { + int fdi; char *filename; + + filename = skip_whitespace(buffer + 8); + + if ((fdi = open(filename, O_RDONLY)) >= 0) { + include_conf(first, current, buffer, buflen, fdi); + close(fdi); + } + } + } + } +} + +/* + * This function builds a list of dependency rules from /lib/modules/`uname -r`\modules.dep. + * It then fills every modules and aliases with their default options, found by parsing + * modprobe.conf (or modules.conf, or conf.modules). + */ +static struct dep_t *build_dep(void) +{ + int fd; + struct utsname un; + struct dep_t *first = 0; + struct dep_t *current = 0; + char buffer[2048]; + char *filename; + int continuation_line = 0; + int k_version; + + if (uname(&un)) + bb_error_msg_and_die("can't determine kernel version"); + + k_version = 0; + if (un.release[0] == '2') { + k_version = un.release[2] - '0'; + } + + filename = xasprintf("/lib/modules/%s/modules.dep", un.release); + fd = open(filename, O_RDONLY); + if (ENABLE_FEATURE_CLEAN_UP) + free(filename); + if (fd < 0) { + /* Ok, that didn't work. Fall back to looking in /lib/modules */ + fd = open("/lib/modules/modules.dep", O_RDONLY); + if (fd < 0) { + return 0; + } + } + + while (reads(fd, buffer, sizeof(buffer))) { + int l = strlen(buffer); + char *p = 0; + + while (l > 0 && isspace(buffer[l-1])) { + buffer[l-1] = 0; + l--; + } + + if (l == 0) { + continuation_line = 0; + continue; + } + + /* Is this a new module dep description? */ + if (!continuation_line) { + /* find the dep beginning */ + char *col = strchr(buffer, ':'); + char *dot = col; + + if (col) { + /* This line is a dep description */ + char *mods; + char *modpath; + char *mod; + + /* Find the beginning of the module file name */ + *col = 0; + mods = strrchr(buffer, '/'); + + if (!mods) + mods = buffer; /* no path for this module */ + else + mods++; /* there was a path for this module... */ + + /* find the path of the module */ + modpath = strchr(buffer, '/'); /* ... and this is the path */ + if (!modpath) + modpath = buffer; /* module with no path */ + /* find the end of the module name in the file name */ + if (ENABLE_FEATURE_2_6_MODULES && + (k_version > 4) && (*(col-3) == '.') && + (*(col-2) == 'k') && (*(col-1) == 'o')) + dot = col - 3; + else + if ((*(col-2) == '.') && (*(col-1) == 'o')) + dot = col - 2; + + mod = xstrndup(mods, dot - mods); + + /* enqueue new module */ + if (!current) { + first = current = xmalloc(sizeof(struct dep_t)); + } + else { + current->m_next = xmalloc(sizeof(struct dep_t)); + current = current->m_next; + } + current->m_name = mod; + current->m_path = xstrdup(modpath); + current->m_options = NULL; + current->m_isalias = 0; + current->m_depcnt = 0; + current->m_deparr = 0; + current->m_next = 0; + + p = col + 1; + } + else + /* this line is not a dep description */ + p = 0; + } + else + /* It's a dep description continuation */ + p = buffer; + + while (p && *p && isblank(*p)) + p++; + + /* p points to the first dependable module; if NULL, no dependable module */ + if (p && *p) { + char *end = &buffer[l-1]; + char *deps; + char *dep; + char *next; + int ext = 0; + + while (isblank(*end) || (*end == '\\')) + end--; + + do { + /* search the end of the dependency */ + next = strchr(p, ' '); + if (next) { + *next = 0; + next--; + } + else + next = end; + + /* find the beginning of the module file name */ + deps = strrchr(p, '/'); + + if (!deps || (deps < p)) { + deps = p; + + while (isblank(*deps)) + deps++; + } else + deps++; + + /* find the end of the module name in the file name */ + if (ENABLE_FEATURE_2_6_MODULES + && (k_version > 4) && (*(next-2) == '.') + && (*(next-1) == 'k') && (*next == 'o')) + ext = 3; + else + if ((*(next-1) == '.') && (*next == 'o')) + ext = 2; + + /* Cope with blank lines */ + if ((next-deps-ext+1) <= 0) + continue; + dep = xstrndup(deps, next - deps - ext + 1); + + /* Add the new dependable module name */ + current->m_depcnt++; + current->m_deparr = xrealloc(current->m_deparr, + sizeof(char *) * current->m_depcnt); + current->m_deparr[current->m_depcnt - 1] = dep; + + p = next + 2; + } while (next < end); + } + + /* is there other dependable module(s) ? */ + if (buffer[l-1] == '\\') + continuation_line = 1; + else + continuation_line = 0; + } + close(fd); + + /* + * First parse system-specific options and aliases + * as they take precedence over the kernel ones. + */ + if (!ENABLE_FEATURE_2_6_MODULES + || (fd = open("/etc/modprobe.conf", O_RDONLY)) < 0) + if ((fd = open("/etc/modules.conf", O_RDONLY)) < 0) + fd = open("/etc/conf.modules", O_RDONLY); + + if (fd >= 0) { + include_conf(&first, ¤t, buffer, sizeof(buffer), fd); + close(fd); + } + + /* Only 2.6 has a modules.alias file */ + if (ENABLE_FEATURE_2_6_MODULES) { + /* Parse kernel-declared aliases */ + filename = xasprintf("/lib/modules/%s/modules.alias", un.release); + fd = open(filename, O_RDONLY); + if (fd < 0) { + /* Ok, that didn't work. Fall back to looking in /lib/modules */ + fd = open("/lib/modules/modules.alias", O_RDONLY); + } + if (ENABLE_FEATURE_CLEAN_UP) + free(filename); + + if (fd >= 0) { + include_conf(&first, ¤t, buffer, sizeof(buffer), fd); + close(fd); + } + } + + return first; +} + +/* return 1 = loaded, 0 = not loaded, -1 = can't tell */ +static int already_loaded(const char *name) +{ + int fd, ret = 0; + char buffer[4096]; + + fd = open("/proc/modules", O_RDONLY); + if (fd < 0) + return -1; + + while (reads(fd, buffer, sizeof(buffer))) { + char *p; + + p = strchr (buffer, ' '); + if (p) { + const char *n; + + // Truncate buffer at first space and check for matches, with + // the idiosyncrasy that _ and - are interchangeable because the + // 2.6 kernel does weird things. + + *p = 0; + for (p = buffer, n = name; ; p++, n++) { + if (*p != *n) { + if ((*p == '_' || *p == '-') && (*n == '_' || *n == '-')) + continue; + break; + } + // If we made it to the end, that's a match. + if (!*p) { + ret = 1; + goto done; + } + } + } + } +done: + close (fd); + return ret; +} + +static int mod_process(struct mod_list_t *list, int do_insert) +{ + int rc = 0; + char **argv = NULL; + struct mod_opt_t *opts; + int argc_malloc; /* never used when CONFIG_FEATURE_CLEAN_UP not defined */ + int argc; + + while (list) { + argc = 0; + if (ENABLE_FEATURE_CLEAN_UP) + argc_malloc = 0; + /* If CONFIG_FEATURE_CLEAN_UP is not defined, then we leak memory + * each time we allocate memory for argv. + * But it is (quite) small amounts of memory that leak each + * time a module is loaded, and it is reclaimed when modprobe + * exits anyway (even when standalone shell?). + * This could become a problem when loading a module with LOTS of + * dependencies, with LOTS of options for each dependencies, with + * very little memory on the target... But in that case, the module + * would not load because there is no more memory, so there's no + * problem. */ + /* enough for minimal insmod (5 args + NULL) or rmmod (3 args + NULL) */ + argv = xmalloc(6 * sizeof(char*)); + if (do_insert) { + if (already_loaded(list->m_name) != 1) { + argv[argc++] = "insmod"; + if (ENABLE_FEATURE_2_4_MODULES) { + if (do_syslog) + argv[argc++] = "-s"; + if (autoclean) + argv[argc++] = "-k"; + if (quiet) + argv[argc++] = "-q"; + else if (verbose) /* verbose and quiet are mutually exclusive */ + argv[argc++] = "-v"; + } + argv[argc++] = list->m_path; + if (ENABLE_FEATURE_CLEAN_UP) + argc_malloc = argc; + opts = list->m_options; + while (opts) { + /* Add one more option */ + argc++; + argv = xrealloc(argv,(argc + 1)* sizeof(char*)); + argv[argc-1] = opts->m_opt_val; + opts = opts->m_next; + } + } + } else { + /* modutils uses short name for removal */ + if (already_loaded(list->m_name) != 0) { + argv[argc++] = "rmmod"; + if (do_syslog) + argv[argc++] = "-s"; + argv[argc++] = list->m_name; + if (ENABLE_FEATURE_CLEAN_UP) + argc_malloc = argc; + } + } + argv[argc] = NULL; + + if (argc) { + if (verbose) { + printf("%s module %s\n", do_insert?"Loading":"Unloading", list->m_name); + } + if (!show_only) { + int rc2 = wait4pid(spawn(argv)); + + if (do_insert) { + rc = rc2; /* only last module matters */ + } + else if (!rc2) { + rc = 0; /* success if remove any mod */ + } + } + if (ENABLE_FEATURE_CLEAN_UP) { + /* the last value in the array has index == argc, but + * it is the terminating NULL, so we must not free it. */ + while (argc_malloc < argc) { + free(argv[argc_malloc++]); + } + } + } + if (ENABLE_FEATURE_CLEAN_UP) { + free(argv); + argv = NULL; + } + list = do_insert ? list->m_prev : list->m_next; + } + return (show_only) ? 0 : rc; +} + +/* + * Check the matching between a pattern and a module name. + * We need this as *_* is equivalent to *-*, even in pattern matching. + */ +static int check_pattern(const char* pat_src, const char* mod_src) { + int ret; + + if (ENABLE_FEATURE_MODPROBE_FANCY_ALIAS) { + char* pat; + char* mod; + char* p; + + pat = xstrdup (pat_src); + mod = xstrdup (mod_src); + + for (p = pat; (p = strchr(p, '-')); *p++ = '_'); + for (p = mod; (p = strchr(p, '-')); *p++ = '_'); + + ret = fnmatch(pat, mod, 0); + + if (ENABLE_FEATURE_CLEAN_UP) { + free (pat); + free (mod); + } + + return ret; + } else { + return fnmatch(pat_src, mod_src, 0); + } +} + +/* + * Builds the dependency list (aka stack) of a module. + * head: the highest module in the stack (last to insmod, first to rmmod) + * tail: the lowest module in the stack (first to insmod, last to rmmod) + */ +static void check_dep(char *mod, struct mod_list_t **head, struct mod_list_t **tail) +{ + struct mod_list_t *find; + struct dep_t *dt; + struct mod_opt_t *opt = 0; + char *path = 0; + + /* Search for the given module name amongst all dependency rules. + * The module name in a dependency rule can be a shell pattern, + * so try to match the given module name against such a pattern. + * Of course if the name in the dependency rule is a plain string, + * then we consider it a pattern, and matching will still work. */ + for (dt = depend; dt; dt = dt->m_next) { + if (check_pattern(dt->m_name, mod) == 0) { + break; + } + } + + if (!dt) { + bb_error_msg("module %s not found", mod); + return; + } + + // resolve alias names + while (dt->m_isalias) { + if (dt->m_depcnt == 1) { + struct dep_t *adt; + + for (adt = depend; adt; adt = adt->m_next) { + if (check_pattern(adt->m_name, dt->m_deparr[0]) == 0) + break; + } + if (adt) { + /* This is the module we are aliased to */ + struct mod_opt_t *opts = dt->m_options; + /* Option of the alias are appended to the options of the module */ + while (opts) { + adt->m_options = append_option(adt->m_options, opts->m_opt_val); + opts = opts->m_next; + } + dt = adt; + } + else { + bb_error_msg("module %s not found", mod); + return; + } + } + else { + bb_error_msg("bad alias %s", dt->m_name); + return; + } + } + + mod = dt->m_name; + path = dt->m_path; + opt = dt->m_options; + + // search for duplicates + for (find = *head; find; find = find->m_next) { + if (!strcmp(mod, find->m_name)) { + // found ->dequeue it + + if (find->m_prev) + find->m_prev->m_next = find->m_next; + else + *head = find->m_next; + + if (find->m_next) + find->m_next->m_prev = find->m_prev; + else + *tail = find->m_prev; + + break; // there can be only one duplicate + } + } + + if (!find) { // did not find a duplicate + find = xmalloc(sizeof(struct mod_list_t)); + find->m_name = mod; + find->m_path = path; + find->m_options = opt; + } + + // enqueue at tail + if (*tail) + (*tail)->m_next = find; + find->m_prev = *tail; + find->m_next = 0; + + if (!*head) + *head = find; + *tail = find; + + if (dt) { + int i; + + /* Add all dependable module for that new module */ + for (i = 0; i < dt->m_depcnt; i++) + check_dep(dt->m_deparr[i], head, tail); + } +} + +static int mod_insert(char *mod, int argc, char **argv) +{ + struct mod_list_t *tail = 0; + struct mod_list_t *head = 0; + int rc; + + // get dep list for module mod + check_dep(mod, &head, &tail); + + if (head && tail) { + if (argc) { + int i; + // append module args + for (i = 0; i < argc; i++) + head->m_options = append_option(head->m_options, argv[i]); + } + + // process tail ---> head + if ((rc = mod_process(tail, 1)) != 0) { + /* + * In case of using udev, multiple instances of modprobe can be + * spawned to load the same module (think of two same usb devices, + * for example; or cold-plugging at boot time). Thus we shouldn't + * fail if the module was loaded, and not by us. + */ + if (already_loaded(mod)) + rc = 0; + } + } + else + rc = 1; + + return rc; +} + +static int mod_remove(char *mod) +{ + int rc; + static struct mod_list_t rm_a_dummy = { "-a", NULL, NULL, NULL, NULL }; + + struct mod_list_t *head = 0; + struct mod_list_t *tail = 0; + + if (mod) + check_dep(mod, &head, &tail); + else // autoclean + head = tail = &rm_a_dummy; + + if (head && tail) + rc = mod_process(head, 0); // process head ---> tail + else + rc = 1; + return rc; + +} + +int modprobe_main(int argc, char** argv) +{ + int rc = EXIT_SUCCESS; + char *unused; + + opt_complementary = "?V-:q-v:v-q"; + main_opts = getopt32(argc, argv, "acdklnqrst:vVC:", + &unused, &unused); + if (main_opts & (DUMP_CONF_EXIT | LIST_ALL)) + return EXIT_SUCCESS; + if (main_opts & (RESTRICT_DIR | CONFIG_FILE)) + bb_error_msg_and_die("-t and -C not supported"); + + depend = build_dep(); + + if (!depend) + bb_error_msg_and_die("cannot parse modules.dep"); + + if (remove_opt) { + do { + if (mod_remove(optind < argc ? + argv[optind] : NULL)) { + bb_error_msg("failed to remove module %s", + argv[optind]); + rc = EXIT_FAILURE; + } + } while (++optind < argc); + } else { + if (optind >= argc) + bb_error_msg_and_die("no module or pattern provided"); + + if (mod_insert(argv[optind], argc - optind - 1, argv + optind + 1)) + bb_error_msg_and_die("failed to load module %s", argv[optind]); + } + + /* Here would be a good place to free up memory allocated during the dependencies build. */ + + return rc; +} diff --git a/modutils/rmmod.c b/modutils/rmmod.c new file mode 100644 index 000000000..22e864d57 --- /dev/null +++ b/modutils/rmmod.c @@ -0,0 +1,96 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini rmmod implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + +#ifdef CONFIG_FEATURE_2_6_MODULES +static inline void filename2modname(char *modname, const char *afterslash) +{ + unsigned int i; + int kr_chk = 1; + + if (ENABLE_FEATURE_2_4_MODULES + && get_linux_version_code() <= KERNEL_VERSION(2,6,0)) + kr_chk = 0; + + /* Convert to underscores, stop at first . */ + for (i = 0; afterslash[i] && afterslash[i] != '.'; i++) { + if (kr_chk && (afterslash[i] == '-')) + modname[i] = '_'; + else + modname[i] = afterslash[i]; + } + modname[i] = '\0'; +} +#else +void filename2modname(char *modname, const char *afterslash); +#endif + +// There really should be a header file for this... + +int query_module(const char *name, int which, void *buf, + size_t bufsize, size_t *ret); + +int rmmod_main(int argc, char **argv) +{ + int n, ret = EXIT_SUCCESS; + unsigned int flags = O_NONBLOCK|O_EXCL; + + /* Parse command line. */ + n = getopt32(argc, argv, "wfa"); + if((n & 1)) // --wait + flags &= ~O_NONBLOCK; + if((n & 2)) // --force + flags |= O_TRUNC; + if((n & 4)) { + /* Unload _all_ unused modules via NULL delete_module() call */ + /* until the number of modules does not change */ + size_t nmod = 0; /* number of modules */ + size_t pnmod = -1; /* previous number of modules */ + + while (nmod != pnmod) { + if (syscall(__NR_delete_module, NULL, flags) != 0) { + if (errno == EFAULT) + return ret; + bb_perror_msg_and_die("rmmod"); + } + pnmod = nmod; + // the 1 here is QM_MODULES. + if (ENABLE_FEATURE_QUERY_MODULE_INTERFACE && query_module(NULL, + 1, bb_common_bufsiz1, sizeof(bb_common_bufsiz1), + &nmod)) + { + bb_perror_msg_and_die("QM_MODULES"); + } + } + return EXIT_SUCCESS; + } + + if (optind == argc) + bb_show_usage(); + + for (n = optind; n < argc; n++) { + if (ENABLE_FEATURE_2_6_MODULES) { + const char *afterslash; + + afterslash = strrchr(argv[n], '/'); + if (!afterslash) afterslash = argv[n]; + else afterslash++; + filename2modname(bb_common_bufsiz1, afterslash); + } + + if (syscall(__NR_delete_module, ENABLE_FEATURE_2_6_MODULES ? bb_common_bufsiz1 : argv[n], flags)) { + bb_perror_msg("%s", argv[n]); + ret = EXIT_FAILURE; + } + } + + return ret; +} diff --git a/networking/Config.in b/networking/Config.in new file mode 100644 index 000000000..e6711078c --- /dev/null +++ b/networking/Config.in @@ -0,0 +1,720 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Networking Utilities" + +config FEATURE_IPV6 + bool "Enable IPv6 support" + default n + help + Enable IPv6 support in busybox. + This adds IPv6 support in the networking applets. + +config ARPING + bool "arping" + default n + help + Ping hosts by ARP packets + +config DNSD + bool "dnsd" + default n + help + Small and static DNS server daemon. + +config ETHER_WAKE + bool "ether-wake" + default n + help + Send a magic packet to wake up sleeping machines. + +config FAKEIDENTD + bool "fakeidentd" + default n + select FEATURE_SYSLOG + help + fakeidentd listens on the ident port and returns a predefined + fake value on any query. + +config FTPGET + bool "ftpget" + default n + help + Retrieve a remote file via FTP. + +config FTPPUT + bool "ftpput" + default n + help + Store a remote file via FTP. + +config FEATURE_FTPGETPUT_LONG_OPTIONS + bool "Enable long options in ftpget/ftpput" + default n + depends on GETOPT_LONG && (FTPGET || FTPPUT) + help + Support long options for the ftpget/ftpput applet. + +config HOSTNAME + bool "hostname" + default n + help + Show or set the system's host name + +config HTTPD + bool "httpd" + default n + help + Serve web pages via an HTTP server. + +config FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + bool "Support reloading the global config file using hup signal" + default n + depends on HTTPD && FEATURE_HTTPD_WITHOUT_INETD + help + This option enables processing of SIGHUP to reload cached + configuration settings. + +config FEATURE_HTTPD_SETUID + bool "Enable support -u option" + default n + depends on HTTPD && FEATURE_HTTPD_WITHOUT_INETD + help + This option allows the server to run as a specific user + rather than defaulting to the user that starts the server. + Use of this option requires special privileges to change to a + different user. + +config FEATURE_HTTPD_BASIC_AUTH + bool "Enable Basic http Authentication" + default y + depends on HTTPD + help + Utilizes password settings from /etc/httpd.conf for basic + authentication on a per url basis. + +config FEATURE_HTTPD_AUTH_MD5 + bool "Support MD5 crypted passwords for http Authentication" + default n + depends on FEATURE_HTTPD_BASIC_AUTH + help + Enables basic per URL authentication from /etc/httpd.conf + using md5 passwords. + +config FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + bool "Support loading additional MIME types at run-time" + default n + depends on HTTPD + help + This option enables support for additional MIME types at + run-time to be specified in the configuration file. + +config FEATURE_HTTPD_CGI + bool "Support Common Gateway Interface (CGI)" + default y + depends on HTTPD + help + This option allows scripts and executables to be invoked + when specific URLs are requested. + +config FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + bool "Enable support for running scripts through an interpreter" + default n + depends on FEATURE_HTTPD_CGI + help + This option enables support for running scripts through an + interpreter. Turn this on if you want PHP scripts to work + properly. You need to supply an addition line in your httpd + config file: + *.php:/path/to/your/php + +config FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV + bool "Support the REMOTE_PORT environment variable for CGI" + default n + depends on FEATURE_HTTPD_CGI + help + Use of this option can assist scripts in generating + references that contain a unique port number. + +config FEATURE_HTTPD_ENCODE_URL_STR + bool "Enable the -e option for shell script CGI simplification." + default y + depends on HTTPD + help + This option allows html encoding arbitrary + strings for display of the browser. Output goes to stdout. + For example, httpd -e "" as + "<Hello World>". + +config IFCONFIG + bool "ifconfig" + default n + help + Ifconfig is used to configure the kernel-resident network interfaces. + +config FEATURE_IFCONFIG_STATUS + bool "Enable status reporting output (+7k)" + default y + depends on IFCONFIG + help + If ifconfig is called with no arguments it will display the status + of the currently active interfaces. + +config FEATURE_IFCONFIG_SLIP + bool "Enable slip-specific options \"keepalive\" and \"outfill\"" + default n + depends on IFCONFIG + help + Allow "keepalive" and "outfill" support for SLIP. If you're not + planning on using serial lines, leave this unchecked. + +config FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + bool "Enable options \"mem_start\", \"io_addr\", and \"irq\"" + default n + depends on IFCONFIG + help + Allow the start address for shared memory, start address for I/O, + and/or the interrupt line used by the specified device. + +config FEATURE_IFCONFIG_HW + bool "Enable option \"hw\" (ether only)" + default y + depends on IFCONFIG + help + Set the hardware address of this interface, if the device driver + supports this operation. Currently, we only support the 'ether' + class. + +config FEATURE_IFCONFIG_BROADCAST_PLUS + bool "Set the broadcast automatically" + default n + depends on IFCONFIG + help + Setting this will make ifconfig attempt to find the broadcast + automatically if the value '+' is used. + +config IFUPDOWN + bool "ifupdown" + default n + select RUN_PARTS + help + Activate or deactivate the specified interfaces. This applet makes + use of either "ifconfig" and "route" or the "ip" command to actually + configure network interfaces. Therefore, you will probably also want + to enable either IFCONFIG and ROUTE, or enable + FEATURE_IFUPDOWN_IP and the various IP options. Of + course you could use non-busybox versions of these programs, so + against my better judgement (since this will surely result in plenty + of support questions on the mailing list), I do not force you to + enable these additional options. It is up to you to supply either + "ifconfig" and "route" or the "ip" command, either via busybox or via + standalone utilities. + +config FEATURE_IFUPDOWN_IP + bool "Use ip applet" + default n + depends on IFUPDOWN + help + Use the iproute "ip" command to implement "ifup" and "ifdown", rather + than the default of using the older 'ifconfig' and 'route' utilities. + +config FEATURE_IFUPDOWN_IP_BUILTIN + bool "Use busybox ip applet" + default y + depends on FEATURE_IFUPDOWN_IP + select IP + select FEATURE_IP_ADDRESS + select FEATURE_IP_LINK + select FEATURE_IP_ROUTE + help + Use the busybox iproute "ip" applet to implement "ifupdown". + + If left disabled, you must install the full-blown iproute2 + utility or the "ifup" and "ifdown" applets will not work. + +config FEATURE_IFUPDOWN_IFCONFIG_BUILTIN + bool "Use busybox ifconfig and route applets" + default y + depends on IFUPDOWN && !FEATURE_IFUPDOWN_IP + select IFCONFIG + select ROUTE + help + Use the busybox iproute "ifconfig" and "route" applets to + implement the "ifup" and "ifdown" utilities. + + If left disabled, you must install the full-blown ifconfig + and route utilities, or the "ifup" and "ifdown" applets will not + work. + +config FEATURE_IFUPDOWN_IPV4 + bool "Enable support for IPv4" + default y + depends on IFUPDOWN + help + If you want busybox to talk IPv4, leave this on. + +config FEATURE_IFUPDOWN_IPV6 + bool "Enable support for IPv6" + default n + depends on IFUPDOWN && FEATURE_IPV6 + help + If you need support for IPv6, turn this option on. + +config FEATURE_IFUPDOWN_IPX + bool "Enable support for IPX" + default n + depends on IFUPDOWN + help + If this option is selected you can use busybox to work with IPX + networks. + +config FEATURE_IFUPDOWN_MAPPING + bool "Enable mapping support" + default n + depends on IFUPDOWN + help + This enables support for the "mapping" stanza, unless you have + a weird network setup you don't need it. + +config INETD + bool "inetd" + default n + select FEATURE_SYSLOG + help + Internet superserver daemon + +config FEATURE_INETD_SUPPORT_BUILTIN_ECHO + bool "Support echo service" + default y + depends on INETD + help + Echo received data internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_DISCARD + bool "Support discard service" + default y + depends on INETD + help + Internet /dev/null internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_TIME + bool "Support time service" + default y + depends on INETD + help + Return 32 bit time since 1900 internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME + bool "Support daytime service" + default y + depends on INETD + help + Return human-readable time internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN + bool "Support chargen service" + default y + depends on INETD + help + Familiar character generator internal inetd service + +config FEATURE_INETD_RPC + bool "Support RPC services" + default n + depends on INETD + depends on FEATURE_HAVE_RPC + help + Support Sun-RPC based services + +config IP + bool "ip" + default n + help + The "ip" applet is a TCP/IP interface configuration and routing + utility. You generally don't need "ip" to use busybox with + TCP/IP. + +config FEATURE_IP_ADDRESS + bool "ip address" + default y + depends on IP + help + Address manipulation support for the "ip" applet. + +config FEATURE_IP_LINK + bool "ip link" + default y + depends on IP + help + Configure network devices with "ip". + +config FEATURE_IP_ROUTE + bool "ip route" + default y + depends on IP + help + Add support for routing table management to "ip". + +config FEATURE_IP_TUNNEL + bool "ip tunnel" + default n + depends on IP + help + Add support for tunneling commands to "ip". + +config FEATURE_IP_RULE + bool "ip rule" + default n + depends on IP + help + Add support for rule commands to "ip". + +config FEATURE_IP_SHORT_FORMS + bool "Support short forms of ip commands." + default n + depends on IP + help + Also support short-form of ip commands: + ip addr -> ipaddr + ip link -> iplink + ip route -> iproute + ip tunnel -> iptunnel + + Say N unless you desparately need the short form of the ip + object commands. + +config IPADDR + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_ADDRESS + +config IPLINK + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_LINK + +config IPROUTE + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_ROUTE + +config IPTUNNEL + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_TUNNEL + +config IPRULE + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_RULE + +config IPCALC + bool "ipcalc" + default n + help + ipcalc takes an IP address and netmask and calculates the + resulting broadcast, network, and host range. + +config FEATURE_IPCALC_FANCY + bool "Fancy IPCALC, more options, adds 1 kbyte" + default y + depends on IPCALC + help + Adds the options hostname, prefix and silent to the output of "ipcalc". + +config FEATURE_IPCALC_LONG_OPTIONS + bool "Enable long options" + default n + depends on IPCALC && GETOPT_LONG + help + Support long options for the ipcalc applet. + +config NAMEIF + bool "nameif" + default n + select FEATURE_SYSLOG + help + nameif is used to rename network interface by its MAC address. + Renamed interfaces MUST be in the down state. + It is possible to use a file (default: /etc/mactab) + with list of new interface names and MACs. + Maximum interface name length: IF_NAMESIZE = 16 + File fields are separated by space or tab. + File format: + # Comment + new_interface_name XX:XX:XX:XX:XX:XX + +config NC + bool "nc" + default n + help + A simple Unix utility which reads and writes data across network + connections. + +config NC_SERVER + bool "Netcat server options (-lp)" + default n + depends on NC + help + Allow netcat to act as a server. + +config NC_EXTRA + bool "Netcat extensions (-eiw and filename)" + default n + depends on NC + help + Add -e (support for executing the rest of the command line after + making or receiving a successful connection), -i (delay interval for + lines sent), -w (timeout for initial connection). + +config NETSTAT + bool "netstat" + default n + help + netstat prints information about the Linux networking subsystem. + +config NSLOOKUP + bool "nslookup" + default n + help + nslookup is a tool to query Internet name servers. + +config PING + bool "ping" + default n + help + ping uses the ICMP protocol's mandatory ECHO_REQUEST datagram to + elicit an ICMP ECHO_RESPONSE from a host or gateway. + +config FEATURE_FANCY_PING + bool "Enable fancy ping output" + default y + depends on PING + help + Make the output from the ping applet include statistics, and at the + same time provide full support for ICMP packets. + +config PING6 + bool "ping6" + default n + depends on FEATURE_IPV6 + help + This will give you a ping that can talk IPv6. + +config FEATURE_FANCY_PING6 + bool "Enable fancy ping6 output" + default y + depends on PING6 + help + Make the output from the ping6 applet include statistics, and at the + same time provide full support for ICMP packets. + +config ROUTE + bool "route" + default n + help + Route displays or manipulates the kernel's IP routing tables. + +config TELNET + bool "telnet" + default n + help + Telnet is an interface to the TELNET protocol, but is also commonly + used to test other simple protocols. + +config FEATURE_TELNET_TTYPE + bool "Pass TERM type to remote host" + default y + depends on TELNET + help + Setting this option will forward the TERM environment variable to the + remote host you are connecting to. This is useful to make sure that + things like ANSI colors and other control sequences behave. + +config FEATURE_TELNET_AUTOLOGIN + bool "Pass USER type to remote host" + default y + depends on TELNET + help + Setting this option will forward the USER environment variable to the + remote host you are connecting to. This is useful when you need to + log into a machine without telling the username (autologin). This + option enables `-a' and `-l USER' arguments. + +config TELNETD + bool "telnetd" + default n + select FEATURE_SYSLOG + help + A daemon for the TELNET protocol, allowing you to log onto the host + running the daemon. Please keep in mind that the TELNET protocol + sends passwords in plain text. If you can't afford the space for an + SSH daemon and you trust your network, you may say 'y' here. As a + more secure alternative, you should seriously consider installing the + very small Dropbear SSH daemon instead: + http://matt.ucc.asn.au/dropbear/dropbear.html + + Note that for busybox telnetd to work you need several things: + First of all, your kernel needs: + UNIX98_PTYS=y + DEVPTS_FS=y + + Next, you need a /dev/pts directory on your root filesystem: + + $ ls -ld /dev/pts + drwxr-xr-x 2 root root 0 Sep 23 13:21 /dev/pts/ + + Next you need the pseudo terminal master multiplexer /dev/ptmx: + + $ ls -la /dev/ptmx + crw-rw-rw- 1 root tty 5, 2 Sep 23 13:55 /dev/ptmx + + Any /dev/ttyp[0-9]* files you may have can be removed. + Next, you need to mount the devpts filesystem on /dev/pts using: + + mount -t devpts devpts /dev/pts + + You need to be sure that Busybox has LOGIN and + FEATURE_SUID enabled. And finally, you should make + certain that Busybox has been installed setuid root: + + chown root.root /bin/busybox + chmod 4755 /bin/busybox + + with all that done, telnetd _should_ work.... + + +config FEATURE_TELNETD_STANDALONE + bool "Support standalone telnetd (not inetd only)" + default n + depends on TELNETD + help + Selecting this will make telnetd able to run standalone. + +config TFTP + bool "tftp" + default n + help + This enables the Trivial File Transfer Protocol client program. TFTP + is usually used for simple, small transfers such as a root image + for a network-enabled bootloader. + +config FEATURE_TFTP_GET + bool "Enable \"get\" command" + default y + depends on TFTP + help + Add support for the GET command within the TFTP client. This allows + a client to retrieve a file from a TFTP server. + +config FEATURE_TFTP_PUT + bool "Enable \"put\" command" + default y + depends on TFTP + help + Add support for the PUT command within the TFTP client. This allows + a client to transfer a file to a TFTP server. + +config FEATURE_TFTP_BLOCKSIZE + bool "Enable \"blocksize\" command" + default n + depends on TFTP + help + Allow the client to specify the desired block size for transfers. + +config DEBUG_TFTP + bool "Enable debug" + default n + depends on TFTP + help + Enable debug settings for tftp. This is useful if you're running + into problems with tftp as the protocol doesn't help you much when + you run into problems. + +config TRACEROUTE + bool "traceroute" + default n + help + Utility to trace the route of IP packets + +config FEATURE_TRACEROUTE_VERBOSE + bool "Enable verbose output" + default n + depends on TRACEROUTE + help + Add some verbosity to traceroute. This includes amongst other things + hostnames and ICMP response types. + +config FEATURE_TRACEROUTE_SOURCE_ROUTE + bool "Enable loose source route" + default n + depends on TRACEROUTE + help + Add option to specify a loose source route gateway + (8 maximum). + +config FEATURE_TRACEROUTE_USE_ICMP + bool "Use ICMP instead of UDP" + default n + depends on TRACEROUTE + help + Add feature to allow for ICMP ECHO instead of UDP datagrams. + +source networking/udhcp/Config.in + +config VCONFIG + bool "vconfig" + default n + help + Creates, removes, and configures VLAN interfaces + +config WGET + bool "wget" + default n + help + wget is a utility for non-interactive download of files from HTTP, + HTTPS, and FTP servers. + +config FEATURE_WGET_STATUSBAR + bool "Enable a nifty process meter (+2k)" + default y + depends on WGET + help + Enable the transfer progress bar for wget transfers. + +config FEATURE_WGET_AUTHENTICATION + bool "Enable HTTP authentication" + default y + depends on WGET + help + Support authenticated HTTP transfers. + +config FEATURE_WGET_IP6_LITERAL + bool "Enable IPv6 literal addresses" + default y + depends on WGET && FEATURE_IPV6 + help + Support IPv6 address literal notation in URLs. + +config FEATURE_WGET_LONG_OPTIONS + bool "Enable long options" + default n + depends on WGET && GETOPT_LONG + help + Support long options for the wget applet. + +config ZCIP + bool "zcip" + default n + select FEATURE_SYSLOG + help + ZCIP provides ZeroConf IPv4 address selection, according to RFC 3927. + It's a daemon that allocates and defends a dynamically assigned + address on the 169.254/16 network, requiring no system administrator. + + See http://www.zeroconf.org for further details, and "zcip.script" + in the busybox examples. + +endmenu diff --git a/networking/Kbuild b/networking/Kbuild new file mode 100644 index 000000000..a9a51fc2e --- /dev/null +++ b/networking/Kbuild @@ -0,0 +1,39 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_ARPING) += arping.o +lib-$(CONFIG_DNSD) += dnsd.o +lib-$(CONFIG_ETHER_WAKE) += ether-wake.o +lib-$(CONFIG_FAKEIDENTD) += fakeidentd.o +lib-$(CONFIG_FTPGET) += ftpgetput.o +lib-$(CONFIG_FTPPUT) += ftpgetput.o +lib-$(CONFIG_HOSTNAME) += hostname.o +lib-$(CONFIG_HTTPD) += httpd.o +lib-$(CONFIG_IFCONFIG) += ifconfig.o interface.o +lib-$(CONFIG_IFUPDOWN) += ifupdown.o +lib-$(CONFIG_INETD) += inetd.o +lib-$(CONFIG_IP) += ip.o +lib-$(CONFIG_IPCALC) += ipcalc.o +lib-$(CONFIG_IPADDR) += ipaddr.o +lib-$(CONFIG_IPLINK) += iplink.o +lib-$(CONFIG_IPROUTE) += iproute.o +lib-$(CONFIG_IPRULE) += iprule.o +lib-$(CONFIG_IPTUNNEL) += iptunnel.o +lib-$(CONFIG_NAMEIF) += nameif.o +lib-$(CONFIG_NC) += nc.o +lib-$(CONFIG_NETSTAT) += netstat.o +lib-$(CONFIG_NSLOOKUP) += nslookup.o +lib-$(CONFIG_PING) += ping.o +lib-$(CONFIG_PING6) += ping6.o +lib-$(CONFIG_ROUTE) += route.o +lib-$(CONFIG_TELNET) += telnet.o +lib-$(CONFIG_TELNETD) += telnetd.o +lib-$(CONFIG_TFTP) += tftp.o +lib-$(CONFIG_TRACEROUTE) += traceroute.o +lib-$(CONFIG_VCONFIG) += vconfig.o +lib-$(CONFIG_WGET) += wget.o +lib-$(CONFIG_ZCIP) += zcip.o diff --git a/networking/arping.c b/networking/arping.c new file mode 100644 index 000000000..2d92bf4be --- /dev/null +++ b/networking/arping.c @@ -0,0 +1,446 @@ +/* vi: set sw=4 ts=4: */ +/* + * arping.c - Ping hosts by ARP requests/replies + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Author: Alexey Kuznetsov + * Busybox port: Nick Fedchik + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "busybox.h" + +static struct in_addr src; +static struct in_addr dst; +static struct sockaddr_ll me; +static struct sockaddr_ll he; +static struct timeval last; + +enum cfg_e { + dad = 1, + unsolicited = 2, + advert = 4, + quiet = 8, + quit_on_reply = 16, + broadcast_only = 32, + unicasting = 64 +}; +static int cfg; + +static int s; +static unsigned count = UINT_MAX; +static unsigned timeout; +static int sent; +static int brd_sent; +static int received; +static int brd_recv; +static int req_recv; + + +#define MS_TDIFF(tv1,tv2) ( ((tv1).tv_sec-(tv2).tv_sec)*1000 + \ + ((tv1).tv_usec-(tv2).tv_usec)/1000 ) +static int send_pack(int sock, struct in_addr *src_addr, + struct in_addr *dst_addr, struct sockaddr_ll *ME, + struct sockaddr_ll *HE) +{ + int err; + struct timeval now; + RESERVE_CONFIG_UBUFFER(buf, 256); + struct arphdr *ah = (struct arphdr *) buf; + unsigned char *p = (unsigned char *) (ah + 1); + + ah->ar_hrd = htons(ME->sll_hatype); + ah->ar_hrd = htons(ARPHRD_ETHER); + ah->ar_pro = htons(ETH_P_IP); + ah->ar_hln = ME->sll_halen; + ah->ar_pln = 4; + ah->ar_op = cfg&advert ? htons(ARPOP_REPLY) : htons(ARPOP_REQUEST); + + memcpy(p, &ME->sll_addr, ah->ar_hln); + p += ME->sll_halen; + + memcpy(p, src_addr, 4); + p += 4; + + if (cfg&advert) + memcpy(p, &ME->sll_addr, ah->ar_hln); + else + memcpy(p, &HE->sll_addr, ah->ar_hln); + p += ah->ar_hln; + + memcpy(p, dst_addr, 4); + p += 4; + + gettimeofday(&now, NULL); + err = sendto(sock, buf, p - buf, 0, (struct sockaddr *) HE, sizeof(*HE)); + if (err == p - buf) { + last = now; + sent++; + if (!(cfg&unicasting)) + brd_sent++; + } + RELEASE_CONFIG_BUFFER(buf); + return err; +} + +static void finish(void) +{ + if (!(cfg&quiet)) { + printf("Sent %d probes (%d broadcast(s))\n" + "Received %d repl%s", + sent, brd_sent, + received, (received > 1) ? "ies" : "y"); + if (brd_recv || req_recv) { + printf(" ("); + if (req_recv) + printf("%d request(s)", req_recv); + if (brd_recv) + printf("%s%d broadcast(s)", req_recv ? ", " : "", brd_recv); + putchar(')'); + } + putchar('\n'); + fflush(stdout); + } + if (cfg&dad) + exit(!!received); + if (cfg&unsolicited) + exit(0); + exit(!received); +} + +static void catcher(void) +{ + struct timeval tv; + static struct timeval start; + + gettimeofday(&tv, NULL); + + if (start.tv_sec == 0) + start = tv; + + if (count-- == 0 + || (timeout && MS_TDIFF(tv, start) > timeout * 1000 + 500)) + finish(); + + if (last.tv_sec == 0 || MS_TDIFF(tv, last) > 500) { + send_pack(s, &src, &dst, &me, &he); + if (count == 0 && cfg&unsolicited) + finish(); + } + alarm(1); +} + +static int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM) +{ + struct arphdr *ah = (struct arphdr *) buf; + unsigned char *p = (unsigned char *) (ah + 1); + struct in_addr src_ip, dst_ip; + + /* Filter out wild packets */ + if (FROM->sll_pkttype != PACKET_HOST && + FROM->sll_pkttype != PACKET_BROADCAST && + FROM->sll_pkttype != PACKET_MULTICAST) + return 0; + + /* Only these types are recognised */ + if (ah->ar_op != htons(ARPOP_REQUEST) && ah->ar_op != htons(ARPOP_REPLY)) + return 0; + + /* ARPHRD check and this darned FDDI hack here :-( */ + if (ah->ar_hrd != htons(FROM->sll_hatype) && + (FROM->sll_hatype != ARPHRD_FDDI + || ah->ar_hrd != htons(ARPHRD_ETHER))) + return 0; + + /* Protocol must be IP. */ + if (ah->ar_pro != htons(ETH_P_IP)) + return 0; + if (ah->ar_pln != 4) + return 0; + if (ah->ar_hln != me.sll_halen) + return 0; + if (len < sizeof(*ah) + 2 * (4 + ah->ar_hln)) + return 0; + memcpy(&src_ip, p + ah->ar_hln, 4); + memcpy(&dst_ip, p + ah->ar_hln + 4 + ah->ar_hln, 4); + if (!(cfg&dad)) { + if (src_ip.s_addr != dst.s_addr) + return 0; + if (src.s_addr != dst_ip.s_addr) + return 0; + if (memcmp(p + ah->ar_hln + 4, &me.sll_addr, ah->ar_hln)) + return 0; + } else { + /* DAD packet was: + src_ip = 0 (or some src) + src_hw = ME + dst_ip = tested address + dst_hw = + + We fail, if receive request/reply with: + src_ip = tested_address + src_hw != ME + if src_ip in request was not zero, check + also that it matches to dst_ip, otherwise + dst_ip/dst_hw do not matter. + */ + if (src_ip.s_addr != dst.s_addr) + return 0; + if (memcmp(p, &me.sll_addr, me.sll_halen) == 0) + return 0; + if (src.s_addr && src.s_addr != dst_ip.s_addr) + return 0; + } + if (!(cfg&quiet)) { + int s_printed = 0; + struct timeval tv; + + gettimeofday(&tv, NULL); + + printf("%s %s from %s [%s]", + FROM->sll_pkttype == PACKET_HOST ? "Unicast" : "Broadcast", + ah->ar_op == htons(ARPOP_REPLY) ? "reply" : "request", + inet_ntoa(src_ip), + ether_ntoa((struct ether_addr *) p)); + if (dst_ip.s_addr != src.s_addr) { + printf("for %s ", inet_ntoa(dst_ip)); + s_printed = 1; + } + if (memcmp(p + ah->ar_hln + 4, me.sll_addr, ah->ar_hln)) { + if (!s_printed) + printf("for "); + printf("[%s]", + ether_ntoa((struct ether_addr *) p + ah->ar_hln + 4)); + } + + if (last.tv_sec) { + long usecs = (tv.tv_sec - last.tv_sec) * 1000000 + + tv.tv_usec - last.tv_usec; + long msecs = (usecs + 500) / 1000; + + usecs -= msecs * 1000 - 500; + printf(" %ld.%03ldms\n", msecs, usecs); + } else { + printf(" UNSOLICITED?\n"); + } + fflush(stdout); + } + received++; + if (FROM->sll_pkttype != PACKET_HOST) + brd_recv++; + if (ah->ar_op == htons(ARPOP_REQUEST)) + req_recv++; + if (cfg&quit_on_reply) + finish(); + if (!(cfg&broadcast_only)) { + memcpy(he.sll_addr, p, me.sll_halen); + cfg |= unicasting; + } + return 1; +} + +int arping_main(int argc, char **argv) +{ + char *device = "eth0"; + int ifindex; + char *source = NULL; + char *target; + + s = xsocket(PF_PACKET, SOCK_DGRAM, 0); + + // Drop suid root privileges + xsetuid(getuid()); + + { + unsigned opt; + char *_count, *_timeout; + + /* Dad also sets quit_on_reply. + * Advert also sets unsolicited. + */ + opt_complementary = "Df:AU"; + opt = getopt32(argc, argv, "DUAqfbc:w:i:s:", + &_count, &_timeout, &device, &source); + cfg |= opt & 0x3f; /* set respective flags */ + if (opt & 0x40) /* -c: count */ + count = xatou(_count); + if (opt & 0x80) /* -w: timeout */ + timeout = xatoul_range(_timeout, 0, INT_MAX/2000); + if (opt & 0x100) { /* -i: interface */ + if (strlen(device) > IF_NAMESIZE) { + bb_error_msg_and_die("interface name '%s' is too long", + device); + } + } + //if (opt & 0x200) /* -s: source */ + } + argc -= optind; + argv += optind; + + if (argc != 1) + bb_show_usage(); + + target = *argv; + + xfunc_error_retval = 2; + + { + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, device, IFNAMSIZ - 1); + if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { + bb_error_msg_and_die("interface %s not found", device); + } + ifindex = ifr.ifr_ifindex; + + if (ioctl(s, SIOCGIFFLAGS, (char *) &ifr)) { + bb_error_msg_and_die("SIOCGIFFLAGS"); + } + if (!(ifr.ifr_flags & IFF_UP)) { + bb_error_msg_and_die("interface %s is down", device); + } + if (ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)) { + bb_error_msg("interface %s is not ARPable", device); + exit(cfg&dad ? 0 : 2); + } + } + + if (!inet_aton(target, &dst)) { + struct hostent *hp; + + hp = gethostbyname2(target, AF_INET); + if (!hp) { + bb_error_msg_and_die("invalid or unknown target %s", target); + } + memcpy(&dst, hp->h_addr, 4); + } + + if (source && !inet_aton(source, &src)) { + bb_error_msg_and_die("invalid source address %s", source); + } + + if (!(cfg&dad) && cfg&unsolicited && src.s_addr == 0) + src = dst; + + if (!(cfg&dad) || src.s_addr) { + struct sockaddr_in saddr; + int probe_fd = xsocket(AF_INET, SOCK_DGRAM, 0); + + if (device) { + if (setsockopt + (probe_fd, SOL_SOCKET, SO_BINDTODEVICE, device, + strlen(device) + 1) == -1) + bb_error_msg("warning: interface %s is ignored", device); + } + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + if (src.s_addr) { + saddr.sin_addr = src; + if (bind(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr)) == -1) { + bb_error_msg_and_die("bind"); + } + } else if (!(cfg&dad)) { + static const int on = 1; + socklen_t alen = sizeof(saddr); + + saddr.sin_port = htons(1025); + saddr.sin_addr = dst; + + if (setsockopt + (probe_fd, SOL_SOCKET, SO_DONTROUTE, (char *) &on, + sizeof(on)) == -1) + bb_perror_msg("warning: setsockopt(SO_DONTROUTE)"); + if (connect(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr)) + == -1) { + bb_error_msg_and_die("connect"); + } + if (getsockname(probe_fd, (struct sockaddr *) &saddr, &alen) == + -1) { + bb_error_msg_and_die("getsockname"); + } + src = saddr.sin_addr; + } + close(probe_fd); + }; + + me.sll_family = AF_PACKET; + me.sll_ifindex = ifindex; + me.sll_protocol = htons(ETH_P_ARP); + if (bind(s, (struct sockaddr *) &me, sizeof(me)) == -1) { + bb_error_msg_and_die("bind"); + } + + { + socklen_t alen = sizeof(me); + + if (getsockname(s, (struct sockaddr *) &me, &alen) == -1) { + bb_error_msg_and_die("getsockname"); + } + } + if (me.sll_halen == 0) { + bb_error_msg("interface \"%s\" is not ARPable (no ll address)", device); + exit(cfg&dad ? 0 : 2); + } + he = me; + memset(he.sll_addr, -1, he.sll_halen); + + if (!(cfg&quiet)) { + printf("ARPING to %s from %s via %s\n", + inet_ntoa(dst), inet_ntoa(src), + device ? device : "unknown"); + } + + if (!src.s_addr && !(cfg&dad)) { + bb_error_msg_and_die("no src address in the non-DAD mode"); + } + + { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_RESTART; + + sa.sa_handler = (void (*)(int)) finish; + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = (void (*)(int)) catcher; + sigaction(SIGALRM, &sa, NULL); + } + + catcher(); + + while (1) { + sigset_t sset, osset; + RESERVE_CONFIG_UBUFFER(packet, 4096); + struct sockaddr_ll from; + socklen_t alen = sizeof(from); + int cc; + + if ((cc = recvfrom(s, packet, 4096, 0, + (struct sockaddr *) &from, &alen)) < 0) { + bb_perror_msg("recvfrom"); + continue; + } + sigemptyset(&sset); + sigaddset(&sset, SIGALRM); + sigaddset(&sset, SIGINT); + sigprocmask(SIG_BLOCK, &sset, &osset); + recv_pack(packet, cc, &from); + sigprocmask(SIG_SETMASK, &osset, NULL); + RELEASE_CONFIG_BUFFER(packet); + } +} diff --git a/networking/dnsd.c b/networking/dnsd.c new file mode 100644 index 000000000..5e9cf52f1 --- /dev/null +++ b/networking/dnsd.c @@ -0,0 +1,456 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini DNS server implementation for busybox + * + * Copyright (C) 2005 Roberto A. Foglietta (me@roberto.foglietta.name) + * Copyright (C) 2005 Odd Arild Olsen (oao at fibula dot no) + * Copyright (C) 2003 Paul Sheer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Odd Arild Olsen started out with the sheerdns [1] of Paul Sheer and rewrote + * it into a shape which I believe is both easier to understand and maintain. + * I also reused the input buffer for output and removed services he did not + * need. [1] http://threading.2038bug.com/sheerdns/ + * + * Some bugfix and minor changes was applied by Roberto A. Foglietta who made + * the first porting of oao' scdns to busybox also. + */ + +#include "busybox.h" + +static char *fileconf = "/etc/dnsd.conf"; +#define LOCK_FILE "/var/run/dnsd.lock" +#define LOG_FILE "/var/log/dnsd.log" + +// Must matct getopt32 call +#define OPT_daemon (option_mask32 & 0x10) +#define OPT_verbose (option_mask32 & 0x20) + +//#define DEBUG 1 + +enum { + MAX_HOST_LEN = 16, // longest host name allowed is 15 + IP_STRING_LEN = 18, // .xxx.xxx.xxx.xxx\0 + +//must be strlen('.in-addr.arpa') larger than IP_STRING_LEN + MAX_NAME_LEN = (IP_STRING_LEN + 13), + +/* Cannot get bigger packets than 512 per RFC1035 + In practice this can be set considerably smaller: + Length of response packet is header (12B) + 2*type(4B) + 2*class(4B) + + ttl(4B) + rlen(2B) + r (MAX_NAME_LEN =21B) + + 2*querystring (2 MAX_NAME_LEN= 42B), all together 90 Byte +*/ + MAX_PACK_LEN = 512 + 1, + + DEFAULT_TTL = 30, // increase this when not testing? + + REQ_A = 1, + REQ_PTR = 12 +}; + +struct dns_repl { // resource record, add 0 or 1 to accepted dns_msg in resp + uint16_t rlen; + uint8_t *r; // resource + uint16_t flags; +}; + +struct dns_head { // the message from client and first part of response mag + uint16_t id; + uint16_t flags; + uint16_t nquer; // accepts 0 + uint16_t nansw; // 1 in response + uint16_t nauth; // 0 + uint16_t nadd; // 0 +}; +struct dns_prop { + uint16_t type; + uint16_t class; +}; +struct dns_entry { // element of known name, ip address and reversed ip address + struct dns_entry *next; + char ip[IP_STRING_LEN]; // dotted decimal IP + char rip[IP_STRING_LEN]; // length decimal reversed IP + char name[MAX_HOST_LEN]; +}; + +static struct dns_entry *dnsentry = NULL; +// FIXME! unused! :( +static int daemonmode; +static uint32_t ttl = DEFAULT_TTL; + +/* + * Convert host name from C-string to dns length/string. + */ +static void convname(char *a, uint8_t *q) +{ + int i = (q[0] == '.') ? 0 : 1; + for (; i < MAX_HOST_LEN-1 && *q; i++, q++) + a[i] = tolower(*q); + a[0] = i - 1; + a[i] = 0; +} + +/* + * Insert length of substrings instead of dots + */ +static void undot(uint8_t * rip) +{ + int i = 0, s = 0; + while (rip[i]) + i++; + for (--i; i >= 0; i--) { + if (rip[i] == '.') { + rip[i] = s; + s = 0; + } else s++; + } +} + +/* + * Append message to log file + */ +static void log_message(char *filename, char *message) +{ + FILE *logfile; + if (!daemonmode) + return; + logfile = fopen(filename, "a"); + if (!logfile) + return; + fprintf(logfile, "%s\n", message); + fclose(logfile); +} + +/* + * Read one line of hostname/IP from file + * Returns 0 for each valid entry read, -1 at EOF + * Assumes all host names are lower case only + * Hostnames with more than one label is not handled correctly. + * Presently the dot is copied into name without + * converting to a length/string substring for that label. + */ + +static int getfileentry(FILE * fp, struct dns_entry *s) +{ + unsigned int a,b,c,d; + char *r, *name; + + restart: + r = xmalloc_fgets(fp); + if (!r) + return -1; + while (*r == ' ' || *r == '\t') { + r++; + if (!*r || *r == '#' || *r == '\n') + goto restart; /* skipping empty/blank and commented lines */ + } + name = r; + while (*r != ' ' && *r != '\t') + r++; + *r++ = 0; + if (sscanf(r, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) + goto restart; /* skipping wrong lines */ + + sprintf(s->ip, "%u.%u.%u.%u", a, b, c, d); + sprintf(s->rip, ".%u.%u.%u.%u", d, c, b, a); + undot((uint8_t*)s->rip); + convname(s->name,(uint8_t*)name); + + if (OPT_verbose) + fprintf(stderr, "\tname:%s, ip:%s\n", &(s->name[1]),s->ip); + + return 0; +} + +/* + * Read hostname/IP records from file + */ +static void dnsentryinit(void) +{ + FILE *fp; + struct dns_entry *m, *prev; + prev = dnsentry = NULL; + + fp = xfopen(fileconf, "r"); + + while (1) { + m = xmalloc(sizeof(struct dns_entry)); + + m->next = NULL; + if (getfileentry(fp, m)) + break; + + if (prev == NULL) + dnsentry = m; + else + prev->next = m; + prev = m; + } + fclose(fp); +} + + +/* + * Set up UDP socket + */ +static int listen_socket(char *iface_addr, int listen_port) +{ + struct sockaddr_in a; + char msg[100]; + int s; + s = xsocket(PF_INET, SOCK_DGRAM, 0); + if (setsockopt_reuseaddr(s) < 0) + bb_perror_msg_and_die("setsockopt() failed"); + memset(&a, 0, sizeof(a)); + a.sin_port = htons(listen_port); + a.sin_family = AF_INET; + if (!inet_aton(iface_addr, &a.sin_addr)) + bb_perror_msg_and_die("bad iface address"); + xbind(s, (struct sockaddr *)&a, sizeof(a)); + xlisten(s, 50); + sprintf(msg, "accepting UDP packets on addr:port %s:%d\n", + iface_addr, (int)listen_port); + log_message(LOG_FILE, msg); + return s; +} + +/* + * Look query up in dns records and return answer if found + * qs is the query string, first byte the string length + */ +static int table_lookup(uint16_t type, uint8_t * as, uint8_t * qs) +{ + int i; + struct dns_entry *d=dnsentry; + + do { +#ifdef DEBUG + char *p,*q; + q = (char *)&(qs[1]); + p = &(d->name[1]); + fprintf(stderr, "\n%s: %d/%d p:%s q:%s %d", + __FUNCTION__, strlen(p), (int)(d->name[0]), p, q, strlen(q)); +#endif + if (type == REQ_A) { /* search by host name */ + for (i = 1; i <= (int)(d->name[0]); i++) + if (tolower(qs[i]) != d->name[i]) + break; + if (i > (int)(d->name[0])) { +#ifdef DEBUG + fprintf(stderr, " OK"); +#endif + strcpy((char *)as, d->ip); +#ifdef DEBUG + fprintf(stderr, " as:%s\n", as); +#endif + return 0; + } + } else + if (type == REQ_PTR) { /* search by IP-address */ + if (!strncmp((char*)&d->rip[1], (char*)&qs[1], strlen(d->rip)-1)) { + strcpy((char *)as, d->name); + return 0; + } + } + d = d->next; + } while (d); + return -1; +} + + +/* + * Decode message and generate answer + */ +#define eret(s) do { fputs(s, stderr); return -1; } while (0) +static int process_packet(uint8_t * buf) +{ + struct dns_head *head; + struct dns_prop *qprop; + struct dns_repl outr; + void *next, *from, *answb; + + uint8_t answstr[MAX_NAME_LEN + 1]; + int lookup_result, type, len, packet_len; + uint16_t flags; + + answstr[0] = '\0'; + + head = (struct dns_head *)buf; + if (head->nquer == 0) + eret("no queries\n"); + + if (head->flags & 0x8000) + eret("ignoring response packet\n"); + + from = (void *)&head[1]; // start of query string + next = answb = from + strlen((char *)from) + 1 + sizeof(struct dns_prop); // where to append answer block + + outr.rlen = 0; // may change later + outr.r = NULL; + outr.flags = 0; + + qprop = (struct dns_prop *)(answb - 4); + type = ntohs(qprop->type); + + // only let REQ_A and REQ_PTR pass + if (!(type == REQ_A || type == REQ_PTR)) { + goto empty_packet; /* we can't handle the query type */ + } + + if (ntohs(qprop->class) != 1 /* class INET */ ) { + outr.flags = 4; /* not supported */ + goto empty_packet; + } + /* we only support standard queries */ + + if ((ntohs(head->flags) & 0x7800) != 0) + goto empty_packet; + + // We have a standard query + log_message(LOG_FILE, (char *)from); + lookup_result = table_lookup(type, answstr, (uint8_t*)from); + if (lookup_result != 0) { + outr.flags = 3 | 0x0400; //name do not exist and auth + goto empty_packet; + } + if (type == REQ_A) { // return an address + struct in_addr a; + if (!inet_aton((char*)answstr, &a)) {//dotted dec to long conv + outr.flags = 1; /* Frmt err */ + goto empty_packet; + } + memcpy(answstr, &a.s_addr, 4); // save before a disappears + outr.rlen = 4; // uint32_t IP + } + else + outr.rlen = strlen((char *)answstr) + 1; // a host name + outr.r = answstr; // 32 bit ip or a host name + outr.flags |= 0x0400; /* authority-bit */ + // we have an answer + head->nansw = htons(1); + + // copy query block to answer block + len = answb - from; + memcpy(answb, from, len); + next += len; + + // and append answer rr + *(uint32_t *) next = htonl(ttl); + next += 4; + *(uint16_t *) next = htons(outr.rlen); + next += 2; + memcpy(next, (void *)answstr, outr.rlen); + next += outr.rlen; + + empty_packet: + + flags = ntohs(head->flags); + // clear rcode and RA, set responsebit and our new flags + flags |= (outr.flags & 0xff80) | 0x8000; + head->flags = htons(flags); + head->nauth = head->nadd = htons(0); + head->nquer = htons(1); + + packet_len = next - (void *)buf; + return packet_len; +} + +/* + * Exit on signal + */ +static void interrupt(int x) +{ + unlink(LOCK_FILE); + write(2, "interrupt exiting\n", 18); + exit(2); +} + +int dnsd_main(int argc, char **argv) +{ + int udps; + uint16_t port = 53; + uint8_t buf[MAX_PACK_LEN]; + char *listen_interface = "0.0.0.0"; + char *sttl, *sport; + + getopt32(argc, argv, "i:c:t:p:dv", &listen_interface, &fileconf, &sttl, &sport); + //if (option_mask32 & 0x1) // -i + //if (option_mask32 & 0x2) // -c + if (option_mask32 & 0x4) // -t + if (!(ttl = atol(sttl))) + bb_show_usage(); + if (option_mask32 & 0x8) // -p + if (!(port = atol(sport))) + bb_show_usage(); + + if (OPT_verbose) { + bb_info_msg("listen_interface: %s", listen_interface); + bb_info_msg("ttl: %d, port: %d", ttl, port); + bb_info_msg("fileconf: %s", fileconf); + } + + if (OPT_daemon) +#ifdef BB_NOMMU + /* reexec for vfork() do continue parent */ + vfork_daemon_rexec(1, 0, argc, argv, "-d"); +#else + xdaemon(1, 0); +#endif + + dnsentryinit(); + + signal(SIGINT, interrupt); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif +#ifdef SIGURG + signal(SIGURG, SIG_IGN); +#endif + + udps = listen_socket(listen_interface, port); + if (udps < 0) + exit(1); + + while (1) { + fd_set fdset; + int r; + + FD_ZERO(&fdset); + FD_SET(udps, &fdset); + // Block until a message arrives + r = select(udps + 1, &fdset, NULL, NULL, NULL); + if (r < 0) + bb_perror_msg_and_die("select error"); + if (r == 0) + bb_perror_msg_and_die("select spurious return"); + + /* Can this test ever be false? - yes */ + if (FD_ISSET(udps, &fdset)) { + struct sockaddr_in from; + int fromlen = sizeof(from); + r = recvfrom(udps, buf, sizeof(buf), 0, + (struct sockaddr *)&from, + (void *)&fromlen); + if (OPT_verbose) + fprintf(stderr, "\n--- Got UDP "); + log_message(LOG_FILE, "\n--- Got UDP "); + + if (r < 12 || r > 512) { + bb_error_msg("invalid packet size"); + continue; + } + if (r > 0) { + r = process_packet(buf); + if (r > 0) + sendto(udps, buf, + r, 0, (struct sockaddr *)&from, + fromlen); + } + } // end if + } // end while + return 0; +} diff --git a/networking/ether-wake.c b/networking/ether-wake.c new file mode 100644 index 000000000..e205ffc00 --- /dev/null +++ b/networking/ether-wake.c @@ -0,0 +1,284 @@ +/* vi: set sw=4 ts=4: */ +/* + * ether-wake.c - Send a magic packet to wake up sleeping machines. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Author: Donald Becker, http://www.scyld.com/"; http://www.scyld.com/wakeonlan.html + * Busybox port: Christian Volkmann + * Used version of ether-wake.c: v1.09 11/12/2003 Donald Becker, http://www.scyld.com/"; + */ + +/* full usage according Donald Becker + * usage: ether-wake [-i ] [-p aa:bb:cc:dd[:ee:ff]] 00:11:22:33:44:55\n" + * + * This program generates and transmits a Wake-On-LAN (WOL)\n" + * \"Magic Packet\", used for restarting machines that have been\n" + * soft-powered-down (ACPI D3-warm state).\n" + * It currently generates the standard AMD Magic Packet format, with\n" + * an optional password appended.\n" + * + * The single required parameter is the Ethernet MAC (station) address\n" + * of the machine to wake or a host ID with known NSS 'ethers' entry.\n" + * The MAC address may be found with the 'arp' program while the target\n" + * machine is awake.\n" + * + * Options:\n" + * -b Send wake-up packet to the broadcast address.\n" + * -D Increase the debug level.\n" + * -i ifname Use interface IFNAME instead of the default 'eth0'.\n" + * -p Append the four or six byte password PW to the packet.\n" + * A password is only required for a few adapter types.\n" + * The password may be specified in ethernet hex format\n" + * or dotted decimal (Internet address)\n" + * -p 00:22:44:66:88:aa\n" + * -p 192.168.1.1\n"; + * + * + * This program generates and transmits a Wake-On-LAN (WOL) "Magic Packet", + * used for restarting machines that have been soft-powered-down + * (ACPI D3-warm state). It currently generates the standard AMD Magic Packet + * format, with an optional password appended. + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + * Contact the author for use under other terms. + * + * This source file was originally part of the network tricks package, and + * is now distributed to support the Scyld Beowulf system. + * Copyright 1999-2003 Donald Becker and Scyld Computing Corporation. + * + * The author may be reached as becker@scyld, or C/O + * Scyld Computing Corporation + * 914 Bay Ridge Road, Suite 220 + * Annapolis MD 21403 + * + * Notes: + * On some systems dropping root capability allows the process to be + * dumped, traced or debugged. + * If someone traces this program, they get control of a raw socket. + * Linux handles this safely, but beware when porting this program. + * + * An alternative to needing 'root' is using a UDP broadcast socket, however + * doing so only works with adapters configured for unicast+broadcast Rx + * filter. That configuration consumes more power. +*/ + + +#include +#include +#include +#include + +#include "busybox.h" + +/* Note: PF_INET, SOCK_DGRAM, IPPROTO_UDP would allow SIOCGIFHWADDR to + * work as non-root, but we need SOCK_PACKET to specify the Ethernet + * destination address. + */ +#ifdef PF_PACKET +# define whereto_t sockaddr_ll +# define make_socket() xsocket(PF_PACKET, SOCK_RAW, 0) +#else +# define whereto_t sockaddr +# define make_socket() xsocket(AF_INET, SOCK_PACKET, SOCK_PACKET) +#endif + +#ifdef DEBUG +# define bb_debug_msg(fmt, args...) fprintf(stderr, fmt, ## args) +void bb_debug_dump_packet(unsigned char *outpack, int pktsize) +{ + int i; + printf("packet dump:\n"); + for (i = 0; i < pktsize; ++i) { + printf("%2.2x ", outpack[i]); + if (i % 20 == 19) puts(""); + } + printf("\n\n"); +} +#else +# define bb_debug_msg(fmt, args...) +# define bb_debug_dump_packet(outpack, pktsize) +#endif + +static inline void get_dest_addr(const char *arg, struct ether_addr *eaddr); +static inline int get_fill(unsigned char *pkt, struct ether_addr *eaddr, int broadcast); +static inline int get_wol_pw(const char *ethoptarg, unsigned char *wol_passwd); + +int ether_wake_main(int argc, char *argv[]) +{ + char *ifname = "eth0", *pass = NULL; + unsigned long flags; + unsigned char wol_passwd[6]; + int wol_passwd_sz = 0; + + int s; /* Raw socket */ + int pktsize; + unsigned char outpack[1000]; + + struct ether_addr eaddr; + struct whereto_t whereto; /* who to wake up */ + + /* handle misc user options */ + flags = getopt32(argc, argv, "bi:p:", &ifname, &pass); + if (optind == argc) + bb_show_usage(); + if (pass) + wol_passwd_sz = get_wol_pw(pass, wol_passwd); + + /* create the raw socket */ + s = make_socket(); + + /* now that we have a raw socket we can drop root */ + xsetuid(getuid()); + + /* look up the dest mac address */ + get_dest_addr(argv[optind], &eaddr); + + /* fill out the header of the packet */ + pktsize = get_fill(outpack, &eaddr, flags /*& 1 [OPT_BROADCAST]*/); + + bb_debug_dump_packet(outpack, pktsize); + + /* Fill in the source address, if possible. */ +#ifdef __linux__ + { + struct ifreq if_hwaddr; + + strncpy(if_hwaddr.ifr_name, ifname, sizeof(if_hwaddr.ifr_name)); + if (ioctl(s, SIOCGIFHWADDR, &if_hwaddr) < 0) + bb_perror_msg_and_die("SIOCGIFHWADDR on %s failed", ifname); + + memcpy(outpack+6, if_hwaddr.ifr_hwaddr.sa_data, 6); + +# ifdef DEBUG + { + unsigned char *hwaddr = if_hwaddr.ifr_hwaddr.sa_data; + printf("The hardware address (SIOCGIFHWADDR) of %s is type %d " + "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n\n", ifname, + if_hwaddr.ifr_hwaddr.sa_family, hwaddr[0], hwaddr[1], + hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]); + } +# endif + } +#endif /* __linux__ */ + + bb_debug_dump_packet(outpack, pktsize); + + /* append the password if specified */ + if (wol_passwd_sz > 0) { + memcpy(outpack+pktsize, wol_passwd, wol_passwd_sz); + pktsize += wol_passwd_sz; + } + + bb_debug_dump_packet(outpack, pktsize); + + /* This is necessary for broadcasts to work */ + if (flags /*& 1 [OPT_BROADCAST]*/) { + if (setsockopt_broadcast(s) < 0) + bb_perror_msg("SO_BROADCAST"); + } + +#if defined(PF_PACKET) + { + struct ifreq ifr; + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFINDEX, &ifr) == -1) + bb_perror_msg_and_die("SIOCGIFINDEX"); + memset(&whereto, 0, sizeof(whereto)); + whereto.sll_family = AF_PACKET; + whereto.sll_ifindex = ifr.ifr_ifindex; + /* The manual page incorrectly claims the address must be filled. + We do so because the code may change to match the docs. */ + whereto.sll_halen = ETH_ALEN; + memcpy(whereto.sll_addr, outpack, ETH_ALEN); + } +#else + whereto.sa_family = 0; + strcpy(whereto.sa_data, ifname); +#endif + + if (sendto(s, outpack, pktsize, 0, (struct sockaddr *)&whereto, sizeof(whereto)) < 0) + bb_perror_msg(bb_msg_write_error); + + close(s); + + return EXIT_SUCCESS; +} + +/* Convert the host ID string to a MAC address. + * The string may be a: + * Host name + * IP address string + * MAC address string +*/ +static inline void get_dest_addr(const char *hostid, struct ether_addr *eaddr) +{ + struct ether_addr *eap; + + eap = ether_aton(hostid); + if (eap) { + *eaddr = *eap; + bb_debug_msg("The target station address is %s\n\n", ether_ntoa(eaddr)); +#if !defined(__UCLIBC__) + } else if (ether_hostton(hostid, eaddr) == 0) { + bb_debug_msg("Station address for hostname %s is %s\n\n", hostid, ether_ntoa(eaddr)); +#else +# warning Need to implement ether_hostton() for uClibc +#endif + } else + bb_show_usage(); +} + +static inline int get_fill(unsigned char *pkt, struct ether_addr *eaddr, int broadcast) +{ + int offset, i; + unsigned char *station_addr = eaddr->ether_addr_octet; + + if (broadcast) + memset(pkt+0, 0xff, 6); + else + memcpy(pkt, station_addr, 6); + memcpy(pkt+6, station_addr, 6); + pkt[12] = 0x08; /* Or 0x0806 for ARP, 0x8035 for RARP */ + pkt[13] = 0x42; + offset = 14; + + memset(pkt+offset, 0xff, 6); + offset += 6; + + for (i = 0; i < 16; ++i) { + memcpy(pkt+offset, station_addr, 6); + offset += 6; + } + + return offset; +} + +static inline int get_wol_pw(const char *ethoptarg, unsigned char *wol_passwd) +{ + int passwd[6]; + int byte_cnt, i; + + /* handle MAC format */ + byte_cnt = sscanf(ethoptarg, "%2x:%2x:%2x:%2x:%2x:%2x", + &passwd[0], &passwd[1], &passwd[2], + &passwd[3], &passwd[4], &passwd[5]); + /* handle IP format */ + if (byte_cnt < 4) + byte_cnt = sscanf(ethoptarg, "%d.%d.%d.%d", + &passwd[0], &passwd[1], &passwd[2], &passwd[3]); + if (byte_cnt < 4) { + bb_error_msg("cannot read Wake-On-LAN pass"); + return 0; + } + + for (i = 0; i < byte_cnt; ++i) + wol_passwd[i] = passwd[i]; + + bb_debug_msg("password: %2.2x %2.2x %2.2x %2.2x (%d)\n\n", + wol_passwd[0], wol_passwd[1], wol_passwd[2], wol_passwd[3], + byte_cnt); + + return byte_cnt; +} diff --git a/networking/fakeidentd.c b/networking/fakeidentd.c new file mode 100644 index 000000000..04138cca3 --- /dev/null +++ b/networking/fakeidentd.c @@ -0,0 +1,387 @@ +/* vi: set sw=4 ts=4: */ +/* + * A fake identd server + * + * Adapted to busybox by Thomas Lundquist + * Original Author: Tomi Ollila + * http://www.guru-group.fi/~too/sw/ + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include + + +#define IDENT_PORT 113 +#define MAXCONNS 20 +#define MAXIDLETIME 45 + +static const char ident_substr[] = " : USERID : UNIX : "; +enum { ident_substr_len = sizeof(ident_substr) - 1 }; +#define PIDFILE "/var/run/identd.pid" + +/* + * We have to track the 'first connection socket' so that we + * don't go around closing file descriptors for non-clients. + * + * descriptor setup normally + * 0 = server socket + * 1 = syslog fd (hopefully -- otherwise this won't work) + * 2 = connection socket after detached from tty. standard error before that + * 3 - 2 + MAXCONNS = rest connection sockets + * + * To try to make sure that syslog fd is what is "requested", the that fd + * is closed before openlog() call. It can only severely fail if fd 0 + * is initially closed. + */ +#define FCS 2 + +/* + * FD of the connection is always the index of the connection structure + * in `conns' array + FCS + */ +static struct { + char buf[20]; + int len; + time_t lasttime; +} conns[MAXCONNS]; + +/* When using global variables, bind those at least to a structure. */ +static struct { + const char *identuser; + fd_set readfds; + int conncnt; +} G; + +/* + * Prototypes + */ +static void reply(int s, char *buf); +static void replyError(int s, char *buf); + +static const char *nobodystr = "nobody"; /* this needs to be declared like this */ +static char *bind_ip_address = "0.0.0.0"; + +static void movefd(int from, int to) +{ + if (from != to) { + dup2(from, to); + close(from); + } +} + +static void inetbind(void) +{ + int s, port; + struct sockaddr_in addr; + int len = sizeof(addr); + struct servent *se; + + se = getservbyname("identd", "tcp"); + port = IDENT_PORT; + if (se) + port = se->s_port; + + s = xsocket(AF_INET, SOCK_STREAM, 0); + + setsockopt_reuseaddr(s); + + memset(&addr, 0, sizeof(addr)); + addr.sin_addr.s_addr = inet_addr(bind_ip_address); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + xbind(s, (struct sockaddr *)&addr, len); + xlisten(s, 5); + + movefd(s, 0); +} + +static void handlexitsigs(int signum) +{ + if (unlink(PIDFILE) < 0) + close(open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0644)); + exit(0); +} + +/* May succeed. If not, won't care. */ +static void writepid(uid_t nobody, uid_t nogrp) +{ + char buf[sizeof(int)*3 + 2]; + int fd = open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0664); + + if (fd < 0) + return; + + sprintf(buf, "%d\n", getpid()); + write(fd, buf, strlen(buf)); + fchown(fd, nobody, nogrp); + close(fd); + + /* should this handle ILL, ... (see signal(7)) */ + signal(SIGTERM, handlexitsigs); + signal(SIGINT, handlexitsigs); + signal(SIGQUIT, handlexitsigs); +} + +/* return 0 as parent, 1 as child */ +static int godaemon(void) +{ + uid_t nobody, nogrp; + struct passwd *pw; + + switch (fork()) { + case -1: + bb_perror_msg_and_die("fork"); + + case 0: + pw = getpwnam(nobodystr); + if (pw == NULL) + bb_error_msg_and_die("cannot find uid/gid of user '%s'", nobodystr); + nobody = pw->pw_uid; + nogrp = pw->pw_gid; + writepid(nobody, nogrp); + + close(0); + inetbind(); + xsetgid(nogrp); + xsetuid(nobody); + close(1); + close(2); + + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); /* connection closed when writing (raises ???) */ + + setsid(); + + return 1; + } + + return 0; +} + +static void deleteConn(int s) +{ + int i = s - FCS; + + close(s); + + G.conncnt--; + + /* + * Most of the time there is 0 connections. Most often that there + * is connections, there is just one connection. When this one connection + * closes, i == G.conncnt = 0 -> no copying. + * When there is more than one connection, the oldest connections closes + * earlier on average. When this happens, the code below starts copying + * the connection structure w/ highest index to the place which which is + * just deleted. This means that the connection structures are no longer + * in chronological order. I'd quess this means that when there is more + * than 1 connection, on average every other connection structure needs + * to be copied over the time all these connections are deleted. + */ + if (i != G.conncnt) { + memcpy(&conns[i], &conns[G.conncnt], sizeof(conns[0])); + movefd(G.conncnt + FCS, s); + } + + FD_CLR(G.conncnt + FCS, &G.readfds); +} + +static int closeOldest(void) +{ + time_t min = conns[0].lasttime; + int idx = 0; + int i; + + for (i = 1; i < MAXCONNS; i++) + if (conns[i].lasttime < min) + idx = i; + + replyError(idx + FCS, "X-SERVER-TOO-BUSY"); + close(idx + FCS); + + return idx; +} + +static int checkInput(char *buf, int len, int l) +{ + int i; + for (i = len; i < len + l; ++i) + if (buf[i] == '\n') + return 1; + return 0; +} + +int fakeidentd_main(int argc, char **argv) +{ + /* This applet is an inetd-style daemon */ + openlog(applet_name, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + + memset(conns, 0, sizeof(conns)); + memset(&G, 0, sizeof(G)); + FD_ZERO(&G.readfds); + FD_SET(0, &G.readfds); + + /* handle -b parameter */ + getopt32(argc, argv, "b:", &bind_ip_address); + /* handle optional REPLY STRING */ + if (optind < argc) + G.identuser = argv[optind]; + else + G.identuser = nobodystr; + + /* daemonize and have the parent return */ + if (godaemon() == 0) + return 0; + + /* main loop where we process all events and never exit */ + while (1) { + fd_set rfds = G.readfds; + struct timeval tv = { 15, 0 }; + int i; + int tim = time(NULL); + + select(G.conncnt + FCS, &rfds, NULL, NULL, G.conncnt? &tv: NULL); + + for (i = G.conncnt - 1; i >= 0; i--) { + int s = i + FCS; + + if (FD_ISSET(s, &rfds)) { + char *buf = conns[i].buf; + unsigned int len = conns[i].len; + unsigned int l; + + if ((l = read(s, buf + len, sizeof(conns[0].buf) - len)) > 0) { + if (checkInput(buf, len, l)) { + reply(s, buf); + goto deleteconn; + } else if (len + l >= sizeof(conns[0].buf)) { + replyError(s, "X-INVALID-REQUEST"); + goto deleteconn; + } else { + conns[i].len += l; + } + } else { + goto deleteconn; + } + + conns[i].lasttime = tim; + continue; + +deleteconn: + deleteConn(s); + } else { + /* implement as time_after() in linux kernel sources ... */ + if (conns[i].lasttime + MAXIDLETIME <= tim) { + replyError(s, "X-TIMEOUT"); + deleteConn(s); + } + } + } + + if (FD_ISSET(0, &rfds)) { + int s = accept(0, NULL, 0); + + if (s < 0) { + if (errno != EINTR) /* EINTR */ + bb_perror_msg("accept"); + } else { + if (G.conncnt == MAXCONNS) + i = closeOldest(); + else + i = G.conncnt++; + + movefd(s, i + FCS); /* move if not already there */ + FD_SET(i + FCS, &G.readfds); + + conns[i].len = 0; + conns[i].lasttime = time(NULL); + } + } + } /* end of while(1) */ + + return 0; +} + +static int parseAddrs(char *ptr, char **myaddr, char **heraddr); +static void reply(int s, char *buf) +{ + char *myaddr, *heraddr; + + myaddr = heraddr = NULL; + + if (parseAddrs(buf, &myaddr, &heraddr)) + replyError(s, "X-INVALID-REQUEST"); + else { + struct iovec iv[6]; + iv[0].iov_base = myaddr; iv[0].iov_len = strlen(myaddr); + iv[1].iov_base = ", "; iv[1].iov_len = 2; + iv[2].iov_base = heraddr; iv[2].iov_len = strlen(heraddr); + iv[3].iov_base = (void *)ident_substr; iv[3].iov_len = ident_substr_len; + iv[4].iov_base = (void *)G.identuser; iv[4].iov_len = strlen(G.identuser); + iv[5].iov_base = "\r\n"; iv[5].iov_len = 2; + writev(s, iv, 6); + } +} + +static void replyError(int s, char *buf) +{ + struct iovec iv[3]; + iv[0].iov_base = "0, 0 : ERROR : "; iv[0].iov_len = 15; + iv[1].iov_base = buf; iv[1].iov_len = strlen(buf); + iv[2].iov_base = "\r\n"; iv[2].iov_len = 2; + writev(s, iv, 3); +} + +static int chmatch(char c, char *chars) +{ + for (; *chars; chars++) + if (c == *chars) + return 1; + return 0; +} + +static int skipchars(char **p, char *chars) +{ + while (chmatch(**p, chars)) + (*p)++; + if (**p == '\r' || **p == '\n') + return 0; + return 1; +} + +static int parseAddrs(char *ptr, char **myaddr, char **heraddr) +{ + /* parse , */ + + if (!skipchars(&ptr, " \t")) + return -1; + + *myaddr = ptr; + + if (!skipchars(&ptr, "1234567890")) + return -1; + + if (!chmatch(*ptr, " \t,")) + return -1; + + *ptr++ = '\0'; + + if (!skipchars(&ptr, " \t,") ) + return -1; + + *heraddr = ptr; + + skipchars(&ptr, "1234567890"); + + if (!chmatch(*ptr, " \n\r")) + return -1; + + *ptr = '\0'; + + return 0; +} diff --git a/networking/ftpgetput.c b/networking/ftpgetput.c new file mode 100644 index 000000000..223d2435c --- /dev/null +++ b/networking/ftpgetput.c @@ -0,0 +1,340 @@ +/* vi: set sw=4 ts=4: */ +/* + * ftpget + * + * Mini implementation of FTP to retrieve a remote file. + * + * Copyright (C) 2002 Jeff Angielski, The PTR Group + * Copyright (C) 2002 Glenn McGrath + * + * Based on wget.c by Chip Rosenthal Covad Communications + * + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + +typedef struct ftp_host_info_s { + char *user; + char *password; + struct sockaddr_in *s_in; +} ftp_host_info_t; + +static char verbose_flag = 0; +static char do_continue = 0; + +static int ftpcmd(const char *s1, const char *s2, FILE *stream, char *buf) +{ + if (verbose_flag) { + bb_error_msg("cmd %s%s", s1, s2); + } + + if (s1) { + if (s2) { + fprintf(stream, "%s%s\r\n", s1, s2); + } else { + fprintf(stream, "%s\r\n", s1); + } + } + do { + char *buf_ptr; + + if (fgets(buf, 510, stream) == NULL) { + bb_perror_msg_and_die("fgets"); + } + buf_ptr = strstr(buf, "\r\n"); + if (buf_ptr) { + *buf_ptr = '\0'; + } + } while (!isdigit(buf[0]) || buf[3] != ' '); + + return xatou(buf); +} + +static int xconnect_ftpdata(ftp_host_info_t *server, const char *buf) +{ + char *buf_ptr; + unsigned short port_num; + + buf_ptr = strrchr(buf, ','); + *buf_ptr = '\0'; + port_num = xatoul_range(buf_ptr + 1, 0, 255); + + buf_ptr = strrchr(buf, ','); + *buf_ptr = '\0'; + port_num += xatoul_range(buf_ptr + 1, 0, 255) * 256; + + server->s_in->sin_port = htons(port_num); + return xconnect_tcp_v4(server->s_in); +} + +static FILE *ftp_login(ftp_host_info_t *server) +{ + FILE *control_stream; + char buf[512]; + + /* Connect to the command socket */ + control_stream = fdopen(xconnect_tcp_v4(server->s_in), "r+"); + if (control_stream == NULL) { + bb_perror_msg_and_die("cannot open control stream"); + } + + if (ftpcmd(NULL, NULL, control_stream, buf) != 220) { + bb_error_msg_and_die("%s", buf + 4); + } + + /* Login to the server */ + switch (ftpcmd("USER ", server->user, control_stream, buf)) { + case 230: + break; + case 331: + if (ftpcmd("PASS ", server->password, control_stream, buf) != 230) { + bb_error_msg_and_die("PASS error: %s", buf + 4); + } + break; + default: + bb_error_msg_and_die("USER error: %s", buf + 4); + } + + ftpcmd("TYPE I", NULL, control_stream, buf); + + return control_stream; +} + +#if !ENABLE_FTPGET +int ftp_receive(ftp_host_info_t *server, FILE *control_stream, + const char *local_path, char *server_path); +#else +static +int ftp_receive(ftp_host_info_t *server, FILE *control_stream, + const char *local_path, char *server_path) +{ + char buf[512]; + off_t filesize = 0; + int fd_data; + int fd_local = -1; + off_t beg_range = 0; + + /* Connect to the data socket */ + if (ftpcmd("PASV", NULL, control_stream, buf) != 227) { + bb_error_msg_and_die("PASV error: %s", buf + 4); + } + fd_data = xconnect_ftpdata(server, buf); + + if (ftpcmd("SIZE ", server_path, control_stream, buf) == 213) { + filesize = BB_STRTOOFF(buf + 4, NULL, 10); + if (errno || filesize < 0) + bb_error_msg_and_die("SIZE error: %s", buf + 4); + } else { + filesize = -1; + do_continue = 0; + } + + if ((local_path[0] == '-') && (local_path[1] == '\0')) { + fd_local = STDOUT_FILENO; + do_continue = 0; + } + + if (do_continue) { + struct stat sbuf; + if (lstat(local_path, &sbuf) < 0) { + bb_perror_msg_and_die("lstat"); + } + if (sbuf.st_size > 0) { + beg_range = sbuf.st_size; + } else { + do_continue = 0; + } + } + + if (do_continue) { + sprintf(buf, "REST %"OFF_FMT"d", beg_range); + if (ftpcmd(buf, NULL, control_stream, buf) != 350) { + do_continue = 0; + } else { + filesize -= beg_range; + } + } + + if (ftpcmd("RETR ", server_path, control_stream, buf) > 150) { + bb_error_msg_and_die("RETR error: %s", buf + 4); + } + + /* only make a local file if we know that one exists on the remote server */ + if (fd_local == -1) { + if (do_continue) { + fd_local = xopen(local_path, O_APPEND | O_WRONLY); + } else { + fd_local = xopen(local_path, O_CREAT | O_TRUNC | O_WRONLY); + } + } + + /* Copy the file */ + if (filesize != -1) { + if (-1 == bb_copyfd_size(fd_data, fd_local, filesize)) + exit(EXIT_FAILURE); + } else { + if (-1 == bb_copyfd_eof(fd_data, fd_local)) + exit(EXIT_FAILURE); + } + + /* close it all down */ + close(fd_data); + if (ftpcmd(NULL, NULL, control_stream, buf) != 226) { + bb_error_msg_and_die("ftp error: %s", buf + 4); + } + ftpcmd("QUIT", NULL, control_stream, buf); + + return EXIT_SUCCESS; +} +#endif + +#if !ENABLE_FTPPUT +int ftp_send(ftp_host_info_t *server, FILE *control_stream, + const char *server_path, char *local_path); +#else +static +int ftp_send(ftp_host_info_t *server, FILE *control_stream, + const char *server_path, char *local_path) +{ + struct stat sbuf; + char buf[512]; + int fd_data; + int fd_local; + int response; + + /* Connect to the data socket */ + if (ftpcmd("PASV", NULL, control_stream, buf) != 227) { + bb_error_msg_and_die("PASV error: %s", buf + 4); + } + fd_data = xconnect_ftpdata(server, buf); + + /* get the local file */ + if ((local_path[0] == '-') && (local_path[1] == '\0')) { + fd_local = STDIN_FILENO; + } else { + fd_local = xopen(local_path, O_RDONLY); + fstat(fd_local, &sbuf); + + sprintf(buf, "ALLO %lu", (unsigned long)sbuf.st_size); + response = ftpcmd(buf, NULL, control_stream, buf); + switch (response) { + case 200: + case 202: + break; + default: + close(fd_local); + bb_error_msg_and_die("ALLO error: %s", buf + 4); + break; + } + } + response = ftpcmd("STOR ", server_path, control_stream, buf); + switch (response) { + case 125: + case 150: + break; + default: + close(fd_local); + bb_error_msg_and_die("STOR error: %s", buf + 4); + } + + /* transfer the file */ + if (bb_copyfd_eof(fd_local, fd_data) == -1) { + exit(EXIT_FAILURE); + } + + /* close it all down */ + close(fd_data); + if (ftpcmd(NULL, NULL, control_stream, buf) != 226) { + bb_error_msg_and_die("error: %s", buf + 4); + } + ftpcmd("QUIT", NULL, control_stream, buf); + + return EXIT_SUCCESS; +} +#endif + +#define FTPGETPUT_OPT_CONTINUE 1 +#define FTPGETPUT_OPT_VERBOSE 2 +#define FTPGETPUT_OPT_USER 4 +#define FTPGETPUT_OPT_PASSWORD 8 +#define FTPGETPUT_OPT_PORT 16 + +#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS +static const struct option ftpgetput_long_options[] = { + { "continue", 1, NULL, 'c' }, + { "verbose", 0, NULL, 'v' }, + { "username", 1, NULL, 'u' }, + { "password", 1, NULL, 'p' }, + { "port", 1, NULL, 'P' }, + { 0, 0, 0, 0 } +}; +#endif + +int ftpgetput_main(int argc, char **argv) +{ + /* content-length of the file */ + unsigned opt; + char *port = "ftp"; + + /* socket to ftp server */ + FILE *control_stream; + struct sockaddr_in s_in; + + /* continue a prev transfer (-c) */ + ftp_host_info_t *server; + + int (*ftp_action)(ftp_host_info_t *, FILE *, const char *, char *) = NULL; + + /* Check to see if the command is ftpget or ftput */ + if (ENABLE_FTPPUT && (!ENABLE_FTPGET || applet_name[3] == 'p')) { + ftp_action = ftp_send; + } + if (ENABLE_FTPGET && (!ENABLE_FTPPUT || applet_name[3] == 'g')) { + ftp_action = ftp_receive; + } + + /* Set default values */ + server = xmalloc(sizeof(ftp_host_info_t)); + server->user = "anonymous"; + server->password = "busybox@"; + verbose_flag = 0; + + /* + * Decipher the command line + */ +#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS + applet_long_options = ftpgetput_long_options; +#endif + opt = getopt32(argc, argv, "cvu:p:P:", &server->user, &server->password, &port); + + /* Process the non-option command line arguments */ + if (argc - optind != 3) { + bb_show_usage(); + } + + if (opt & FTPGETPUT_OPT_CONTINUE) { + do_continue = 1; + } + if (opt & FTPGETPUT_OPT_VERBOSE) { + verbose_flag = 1; + } + + /* We want to do exactly _one_ DNS lookup, since some + * sites (i.e. ftp.us.debian.org) use round-robin DNS + * and we want to connect to only one IP... */ + server->s_in = &s_in; + bb_lookup_host(&s_in, argv[optind]); + s_in.sin_port = bb_lookup_port(port, "tcp", 21); + if (verbose_flag) { + printf("Connecting to %s[%s]:%d\n", + argv[optind], inet_ntoa(s_in.sin_addr), ntohs(s_in.sin_port)); + } + + /* Connect/Setup/Configure the FTP session */ + control_stream = ftp_login(server); + + return ftp_action(server, control_stream, argv[optind + 1], argv[optind + 2]); +} diff --git a/networking/hostname.c b/networking/hostname.c new file mode 100644 index 000000000..4fe28d740 --- /dev/null +++ b/networking/hostname.c @@ -0,0 +1,102 @@ +/* vi: set sw=4 ts=4: */ +/* + * $Id: hostname.c,v 1.36 2003/07/14 21:21:01 andersen Exp $ + * Mini hostname implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung + * + * adjusted by Erik Andersen to remove + * use of long options and GNU getopt. Improved the usage info. + * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +static void do_sethostname(char *s, int isfile) +{ + FILE *f; + char buf[256]; + + if (!s) + return; + if (!isfile) { + if (sethostname(s, strlen(s)) < 0) { + if (errno == EPERM) + bb_error_msg_and_die("you must be root to change the hostname"); + else + bb_perror_msg_and_die("sethostname"); + } + } else { + f = xfopen(s, "r"); + while (fgets(buf, sizeof(buf), f) != NULL) { + if (buf[0] =='#') { + continue; + } + chomp(buf); + do_sethostname(buf, 0); + } +#ifdef CONFIG_FEATURE_CLEAN_UP + fclose(f); +#endif + } +} + +int hostname_main(int argc, char **argv) +{ + enum { + OPT_d = 0x1, + OPT_f = 0x2, + OPT_i = 0x4, + OPT_s = 0x8, + OPT_dfis = 0xf, + }; + + char buf[256]; + unsigned opt; + char *hostname_str = NULL; + + if (argc < 1) + bb_show_usage(); + + opt = getopt32(argc, argv, "dfisF:", &hostname_str); + + /* Output in desired format */ + if (opt & OPT_dfis) { + struct hostent *hp; + char *p; + gethostname(buf, sizeof(buf)); + hp = xgethostbyname(buf); + p = strchr(hp->h_name, '.'); + if (opt & OPT_f) { + puts(hp->h_name); + } else if (opt & OPT_s) { + if (p != NULL) { + *p = 0; + } + puts(hp->h_name); + } else if (opt & OPT_d) { + if (p) puts(p + 1); + } else if (opt & OPT_i) { + while (hp->h_addr_list[0]) { + printf("%s ", inet_ntoa(*(struct in_addr *) (*hp->h_addr_list++))); + } + puts(""); + } + } + /* Set the hostname */ + else if (hostname_str != NULL) { + do_sethostname(hostname_str, 1); + } else if (optind < argc) { + do_sethostname(argv[optind], 0); + } + /* Or if all else fails, + * just print the current hostname */ + else { + gethostname(buf, sizeof(buf)); + puts(buf); + } + return 0; +} diff --git a/networking/httpd.c b/networking/httpd.c new file mode 100644 index 000000000..49c2c76be --- /dev/null +++ b/networking/httpd.c @@ -0,0 +1,1970 @@ +/* vi: set sw=4 ts=4: */ +/* + * httpd implementation for busybox + * + * Copyright (C) 2002,2003 Glenn Engel + * Copyright (C) 2003-2006 Vladimir Oleynik + * + * simplify patch stolen from libbb without using strdup + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + ***************************************************************************** + * + * Typical usage: + * for non root user + * httpd -p 8080 -h $HOME/public_html + * or for daemon start from rc script with uid=0: + * httpd -u www + * This is equivalent if www user have uid=80 to + * httpd -p 80 -u 80 -h /www -c /etc/httpd.conf -r "Web Server Authentication" + * + * + * When a url contains "cgi-bin" it is assumed to be a cgi script. The + * server changes directory to the location of the script and executes it + * after setting QUERY_STRING and other environment variables. + * + * Doc: + * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html + * + * The server can also be invoked as a url arg decoder and html text encoder + * as follows: + * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World" + * bar=`httpd -e ""` # encode as "<Hello World>" + * Note that url encoding for arguments is not the same as html encoding for + * presentation. -d decodes a url-encoded argument while -e encodes in html + * for page display. + * + * httpd.conf has the following format: + * + * A:172.20. # Allow address from 172.20.0.0/16 + * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127 + * A:10.0.0.0/255.255.255.128 # Allow any address that previous set + * A:127.0.0.1 # Allow local loopback connections + * D:* # Deny from other IP connections + * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/ + * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/ + * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/ + * .au:audio/basic # additional mime type for audio.au files + * *.php:/path/php # running cgi.php scripts through an interpreter + * + * A/D may be as a/d or allow/deny - first char case insensitive + * Deny IP rules take precedence over allow rules. + * + * + * The Deny/Allow IP logic: + * + * - Default is to allow all. No addresses are denied unless + * denied with a D: rule. + * - Order of Deny/Allow rules is significant + * - Deny rules take precedence over allow rules. + * - If a deny all rule (D:*) is used it acts as a catch-all for unmatched + * addresses. + * - Specification of Allow all (A:*) is a no-op + * + * Example: + * 1. Allow only specified addresses + * A:172.20 # Allow any address that begins with 172.20. + * A:10.10. # Allow any address that begins with 10.10. + * A:127.0.0.1 # Allow local loopback connections + * D:* # Deny from other IP connections + * + * 2. Only deny specified addresses + * D:1.2.3. # deny from 1.2.3.0 - 1.2.3.255 + * D:2.3.4. # deny from 2.3.4.0 - 2.3.4.255 + * A:* # (optional line added for clarity) + * + * If a sub directory contains a config file it is parsed and merged with + * any existing settings as if it was appended to the original configuration. + * + * subdir paths are relative to the containing subdir and thus cannot + * affect the parent rules. + * + * Note that since the sub dir is parsed in the forked thread servicing the + * subdir http request, any merge is discarded when the process exits. As a + * result, the subdir settings only have a lifetime of a single request. + * + * + * If -c is not set, an attempt will be made to open the default + * root configuration file. If -c is set and the file is not found, the + * server exits with an error. + * +*/ + + +#include "busybox.h" + + +static const char httpdVersion[] = "busybox httpd/1.35 6-Oct-2004"; +static const char default_path_httpd_conf[] = "/etc"; +static const char httpd_conf[] = "httpd.conf"; +static const char home[] = "./"; + +#define TIMEOUT 60 + +// Note: busybox xfuncs are not used because we want the server to keep running +// if something bad happens due to a malformed user request. +// As a result, all memory allocation after daemonize +// is checked rigorously + +//#define DEBUG 1 + +#ifndef DEBUG +# define DEBUG 0 +#endif + +#define MAX_MEMORY_BUFF 8192 /* IO buffer */ + +typedef struct HT_ACCESS { + char *after_colon; + struct HT_ACCESS *next; + char before_colon[1]; /* really bigger, must last */ +} Htaccess; + +typedef struct HT_ACCESS_IP { + unsigned int ip; + unsigned int mask; + int allow_deny; + struct HT_ACCESS_IP *next; +} Htaccess_IP; + +typedef struct { + char buf[MAX_MEMORY_BUFF]; + + USE_FEATURE_HTTPD_BASIC_AUTH(const char *realm;) + USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) + + const char *query; + + USE_FEATURE_HTTPD_CGI(char *referer;) + + const char *configFile; + + unsigned int rmt_ip; +#if ENABLE_FEATURE_HTTPD_CGI || DEBUG + char rmt_ip_str[16]; /* for set env REMOTE_ADDR */ +#endif + unsigned port; /* server initial port and for + set env REMOTE_PORT */ + const char *found_mime_type; + const char *found_moved_temporarily; + + off_t ContentLength; /* -1 - unknown */ + time_t last_mod; + + Htaccess_IP *ip_a_d; /* config allow/deny lines */ + int flg_deny_all; +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + Htaccess *auth; /* config user:password lines */ +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *mime_a; /* config mime types */ +#endif + + int server_socket; + int accepted_socket; + volatile int alarm_signaled; + +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + Htaccess *script_i; /* config script interpreters */ +#endif +} HttpdConfig; + +static HttpdConfig *config; + +static const char request_GET[] = "GET"; /* size algorithmic optimize */ + +static const char* const suffixTable [] = { +/* Warning: shorted equivalent suffix in one line must be first */ + ".htm.html", "text/html", + ".jpg.jpeg", "image/jpeg", + ".gif", "image/gif", + ".png", "image/png", + ".txt.h.c.cc.cpp", "text/plain", + ".css", "text/css", + ".wav", "audio/wav", + ".avi", "video/x-msvideo", + ".qt.mov", "video/quicktime", + ".mpe.mpeg", "video/mpeg", + ".mid.midi", "audio/midi", + ".mp3", "audio/mpeg", +#if 0 /* unpopular */ + ".au", "audio/basic", + ".pac", "application/x-ns-proxy-autoconfig", + ".vrml.wrl", "model/vrml", +#endif + 0, "application/octet-stream" /* default */ +}; + +typedef enum { + HTTP_OK = 200, + HTTP_MOVED_TEMPORARILY = 302, + HTTP_BAD_REQUEST = 400, /* malformed syntax */ + HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ + HTTP_NOT_FOUND = 404, + HTTP_FORBIDDEN = 403, + HTTP_REQUEST_TIMEOUT = 408, + HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ + HTTP_INTERNAL_SERVER_ERROR = 500, +#if 0 /* future use */ + HTTP_CONTINUE = 100, + HTTP_SWITCHING_PROTOCOLS = 101, + HTTP_CREATED = 201, + HTTP_ACCEPTED = 202, + HTTP_NON_AUTHORITATIVE_INFO = 203, + HTTP_NO_CONTENT = 204, + HTTP_MULTIPLE_CHOICES = 300, + HTTP_MOVED_PERMANENTLY = 301, + HTTP_NOT_MODIFIED = 304, + HTTP_PAYMENT_REQUIRED = 402, + HTTP_BAD_GATEWAY = 502, + HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ + HTTP_RESPONSE_SETSIZE = 0xffffffff +#endif +} HttpResponseNum; + +typedef struct { + HttpResponseNum type; + const char *name; + const char *info; +} HttpEnumString; + +static const HttpEnumString httpResponseNames[] = { + { HTTP_OK, "OK", NULL }, + { HTTP_MOVED_TEMPORARILY, "Found", "Directories must end with a slash." }, + { HTTP_REQUEST_TIMEOUT, "Request Timeout", + "No request appeared within a reasonable time period." }, + { HTTP_NOT_IMPLEMENTED, "Not Implemented", + "The requested method is not recognized by this server." }, +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + { HTTP_UNAUTHORIZED, "Unauthorized", "" }, +#endif + { HTTP_NOT_FOUND, "Not Found", + "The requested URL was not found on this server." }, + { HTTP_BAD_REQUEST, "Bad Request", "Unsupported method." }, + { HTTP_FORBIDDEN, "Forbidden", "" }, + { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error", + "Internal Server Error" }, +#if 0 /* not implemented */ + { HTTP_CREATED, "Created" }, + { HTTP_ACCEPTED, "Accepted" }, + { HTTP_NO_CONTENT, "No Content" }, + { HTTP_MULTIPLE_CHOICES, "Multiple Choices" }, + { HTTP_MOVED_PERMANENTLY, "Moved Permanently" }, + { HTTP_NOT_MODIFIED, "Not Modified" }, + { HTTP_BAD_GATEWAY, "Bad Gateway", "" }, + { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" }, +#endif +}; + + +static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT"; + + +#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1) + + +static int scan_ip(const char **ep, unsigned int *ip, unsigned char endc) +{ + const char *p = *ep; + int auto_mask = 8; + int j; + + *ip = 0; + for (j = 0; j < 4; j++) { + unsigned int octet; + + if ((*p < '0' || *p > '9') && (*p != '/' || j == 0) && *p != 0) + return -auto_mask; + octet = 0; + while (*p >= '0' && *p <= '9') { + octet *= 10; + octet += *p - '0'; + if (octet > 255) + return -auto_mask; + p++; + } + if (*p == '.') + p++; + if (*p != '/' && *p != 0) + auto_mask += 8; + *ip = ((*ip) << 8) | octet; + } + if (*p != 0) { + if (*p != endc) + return -auto_mask; + p++; + if (*p == 0) + return -auto_mask; + } + *ep = p; + return auto_mask; +} + +static int scan_ip_mask(const char *ipm, unsigned int *ip, unsigned int *mask) +{ + int i; + unsigned int msk; + + i = scan_ip(&ipm, ip, '/'); + if (i < 0) + return i; + if (*ipm) { + const char *p = ipm; + + i = 0; + while (*p) { + if (*p < '0' || *p > '9') { + if (*p == '.') { + i = scan_ip(&ipm, mask, 0); + return i != 32; + } + return -1; + } + i *= 10; + i += *p - '0'; + p++; + } + } + if (i > 32 || i < 0) + return -1; + msk = 0x80000000; + *mask = 0; + while (i > 0) { + *mask |= msk; + msk >>= 1; + i--; + } + return 0; +} + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES +static void free_config_lines(Htaccess **pprev) +{ + Htaccess *prev = *pprev; + + while (prev) { + Htaccess *cur = prev; + + prev = cur->next; + free(cur); + } + *pprev = NULL; +} +#endif + +/* flag */ +#define FIRST_PARSE 0 +#define SUBDIR_PARSE 1 +#define SIGNALED_PARSE 2 +#define FIND_FROM_HTTPD_ROOT 3 +/**************************************************************************** + * + > $Function: parse_conf() + * + * $Description: parse configuration file into in-memory linked list. + * + * The first non-white character is examined to determine if the config line + * is one of the following: + * .ext:mime/type # new mime type not compiled into httpd + * [adAD]:from # ip address allow/deny, * for wildcard + * /path:user:pass # username/password + * + * Any previous IP rules are discarded. + * If the flag argument is not SUBDIR_PARSE then all /path and mime rules + * are also discarded. That is, previous settings are retained if flag is + * SUBDIR_PARSE. + * + * $Parameters: + * (const char *) path . . null for ip address checks, path for password + * checks. + * (int) flag . . . . . . the source of the parse request. + * + * $Return: (None) + * + ****************************************************************************/ +static void parse_conf(const char *path, int flag) +{ + FILE *f; +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + Htaccess *prev, *cur; +#elif ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *cur; +#endif + + const char *cf = config->configFile; + char buf[160]; + char *p0 = NULL; + char *c, *p; + + /* free previous ip setup if present */ + Htaccess_IP *pip = config->ip_a_d; + + while (pip) { + Htaccess_IP *cur_ipl = pip; + + pip = cur_ipl->next; + free(cur_ipl); + } + config->ip_a_d = NULL; + + config->flg_deny_all = 0; + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + /* retain previous auth and mime config only for subdir parse */ + if (flag != SUBDIR_PARSE) { +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + free_config_lines(&config->auth); +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + free_config_lines(&config->mime_a); +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + free_config_lines(&config->script_i); +#endif + } +#endif + + if (flag == SUBDIR_PARSE || cf == NULL) { + cf = alloca(strlen(path) + sizeof(httpd_conf) + 2); + if (cf == NULL) { + if (flag == FIRST_PARSE) + bb_error_msg_and_die(bb_msg_memory_exhausted); + return; + } + sprintf((char *)cf, "%s/%s", path, httpd_conf); + } + + while ((f = fopen(cf, "r")) == NULL) { + if (flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) { + /* config file not found, no changes to config */ + return; + } + if (config->configFile && flag == FIRST_PARSE) /* if -c option given */ + bb_perror_msg_and_die("%s", cf); + flag = FIND_FROM_HTTPD_ROOT; + cf = httpd_conf; + } + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + prev = config->auth; +#endif + /* This could stand some work */ + while ((p0 = fgets(buf, sizeof(buf), f)) != NULL) { + c = NULL; + for (p = p0; *p0 != 0 && *p0 != '#'; p0++) { + if (!isspace(*p0)) { + *p++ = *p0; + if (*p0 == ':' && c == NULL) + c = p; + } + } + *p = 0; + + /* test for empty or strange line */ + if (c == NULL || *c == 0) + continue; + p0 = buf; + if (*p0 == 'd') + *p0 = 'D'; + if (*c == '*') { + if (*p0 == 'D') { + /* memorize deny all */ + config->flg_deny_all++; + } + /* skip default other "word:*" config lines */ + continue; + } + + if (*p0 == 'a') + *p0 = 'A'; + else if (*p0 != 'D' && *p0 != 'A' +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + && *p0 != '/' +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + && *p0 != '.' +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + && *p0 != '*' +#endif + ) + continue; + if (*p0 == 'A' || *p0 == 'D') { + /* storing current config IP line */ + pip = calloc(1, sizeof(Htaccess_IP)); + if (pip) { + if (scan_ip_mask(c, &(pip->ip), &(pip->mask))) { + /* syntax IP{/mask} error detected, protect all */ + *p0 = 'D'; + pip->mask = 0; + } + pip->allow_deny = *p0; + if (*p0 == 'D') { + /* Deny:form_IP move top */ + pip->next = config->ip_a_d; + config->ip_a_d = pip; + } else { + /* add to bottom A:form_IP config line */ + Htaccess_IP *prev_IP = config->ip_a_d; + + if (prev_IP == NULL) { + config->ip_a_d = pip; + } else { + while (prev_IP->next) + prev_IP = prev_IP->next; + prev_IP->next = pip; + } + } + } + continue; + } +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (*p0 == '/') { + /* make full path from httpd root / curent_path / config_line_path */ + cf = flag == SUBDIR_PARSE ? path : ""; + p0 = malloc(strlen(cf) + (c - buf) + 2 + strlen(c)); + if (p0 == NULL) + continue; + c[-1] = 0; + sprintf(p0, "/%s%s", cf, buf); + + /* another call bb_simplify_path */ + cf = p = p0; + + do { + if (*p == '/') { + if (*cf == '/') { /* skip duplicate (or initial) slash */ + continue; + } else if (*cf == '.') { + if (cf[1] == '/' || cf[1] == 0) { /* remove extra '.' */ + continue; + } else if ((cf[1] == '.') && (cf[2] == '/' || cf[2] == 0)) { + ++cf; + if (p > p0) { + while (*--p != '/') /* omit previous dir */; + } + continue; + } + } + } + *++p = *cf; + } while (*++cf); + + if ((p == p0) || (*p != '/')) { /* not a trailing slash */ + ++p; /* so keep last character */ + } + *p = 0; + sprintf(p0, "%s:%s", p0, c); + } +#endif + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + /* storing current config line */ + cur = calloc(1, sizeof(Htaccess) + strlen(p0)); + if (cur) { + cf = strcpy(cur->before_colon, p0); + c = strchr(cf, ':'); + *c++ = 0; + cur->after_colon = c; +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + if (*cf == '.') { + /* config .mime line move top for overwrite previous */ + cur->next = config->mime_a; + config->mime_a = cur; + continue; + } +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + if (*cf == '*' && cf[1] == '.') { + /* config script interpreter line move top for overwrite previous */ + cur->next = config->script_i; + config->script_i = cur; + continue; + } +#endif +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + free(p0); + if (prev == NULL) { + /* first line */ + config->auth = prev = cur; + } else { + /* sort path, if current lenght eq or bigger then move up */ + Htaccess *prev_hti = config->auth; + size_t l = strlen(cf); + Htaccess *hti; + + for (hti = prev_hti; hti; hti = hti->next) { + if (l >= strlen(hti->before_colon)) { + /* insert before hti */ + cur->next = hti; + if (prev_hti != hti) { + prev_hti->next = cur; + } else { + /* insert as top */ + config->auth = cur; + } + break; + } + if (prev_hti != hti) + prev_hti = prev_hti->next; + } + if (!hti) { /* not inserted, add to bottom */ + prev->next = cur; + prev = cur; + } + } +#endif + } +#endif + } + fclose(f); +} + +#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR +/**************************************************************************** + * + > $Function: encodeString() + * + * $Description: Given a string, html encode special characters. + * This is used for the -e command line option to provide an easy way + * for scripts to encode result data without confusing browsers. The + * returned string pointer is memory allocated by malloc(). + * + * $Parameters: + * (const char *) string . . The first string to encode. + * + * $Return: (char *) . . . .. . . A pointer to the encoded string. + * + * $Errors: Returns a null string ("") if memory is not available. + * + ****************************************************************************/ +static char *encodeString(const char *string) +{ + /* take the simple route and encode everything */ + /* could possibly scan once to get length. */ + int len = strlen(string); + char *out = malloc(len * 6 + 1); + char *p = out; + char ch; + + if (!out) return ""; + while ((ch = *string++)) { + // very simple check for what to encode + if (isalnum(ch)) *p++ = ch; + else p += sprintf(p, "&#%d;", (unsigned char) ch); + } + *p = 0; + return out; +} +#endif /* FEATURE_HTTPD_ENCODE_URL_STR */ + +/**************************************************************************** + * + > $Function: decodeString() + * + * $Description: Given a URL encoded string, convert it to plain ascii. + * Since decoding always makes strings smaller, the decode is done in-place. + * Thus, callers should strdup() the argument if they do not want the + * argument modified. The return is the original pointer, allowing this + * function to be easily used as arguments to other functions. + * + * $Parameters: + * (char *) string . . . The first string to decode. + * (int) option_d . . 1 if called for httpd -d + * + * $Return: (char *) . . . . A pointer to the decoded string (same as input). + * + * $Errors: None + * + ****************************************************************************/ +static char *decodeString(char *orig, int option_d) +{ + /* note that decoded string is always shorter than original */ + char *string = orig; + char *ptr = string; + char c; + + while ((c = *ptr++) != '\0') { + unsigned value1, value2; + + if (option_d && c == '+') { + *string++ = ' '; + continue; + } + if (c != '%') { + *string++ = c; + continue; + } + if (sscanf(ptr, "%1X", &value1) != 1 + || sscanf(ptr+1, "%1X", &value2) != 1 + ) { + if (!option_d) + return NULL; + *string++ = '%'; + continue; + } + value1 = value1 * 16 + value2; + if (!option_d && (value1 == '/' || value1 == '\0')) { + /* caller takes it as indication of invalid + * (dangerous wrt exploits) chars */ + return orig + 1; + } + *string++ = value1; + ptr += 2; + } + *string = '\0'; + return orig; +} + + +#if ENABLE_FEATURE_HTTPD_CGI +/**************************************************************************** + * setenv helpers + ****************************************************************************/ +static void setenv1(const char *name, const char *value) +{ + if (!value) + value = ""; + setenv(name, value, 1); +} +static void setenv_long(const char *name, long value) +{ + char buf[sizeof(value)*3 + 1]; + sprintf(buf, "%ld", value); + setenv(name, buf, 1); +} +#endif + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH +/**************************************************************************** + * + > $Function: decodeBase64() + * + > $Description: Decode a base 64 data stream as per rfc1521. + * Note that the rfc states that none base64 chars are to be ignored. + * Since the decode always results in a shorter size than the input, it is + * OK to pass the input arg as an output arg. + * + * $Parameter: + * (char *) Data . . . . A pointer to a base64 encoded string. + * Where to place the decoded data. + * + * $Return: void + * + * $Errors: None + * + ****************************************************************************/ +static void decodeBase64(char *Data) +{ + + const unsigned char *in = (const unsigned char *)Data; + // The decoded size will be at most 3/4 the size of the encoded + unsigned long ch = 0; + int i = 0; + + while (*in) { + int t = *in++; + + if (t >= '0' && t <= '9') + t = t - '0' + 52; + else if (t >= 'A' && t <= 'Z') + t = t - 'A'; + else if (t >= 'a' && t <= 'z') + t = t - 'a' + 26; + else if (t == '+') + t = 62; + else if (t == '/') + t = 63; + else if (t == '=') + t = 0; + else + continue; + + ch = (ch << 6) | t; + i++; + if (i == 4) { + *Data++ = (char) (ch >> 16); + *Data++ = (char) (ch >> 8); + *Data++ = (char) ch; + i = 0; + } + } + *Data = 0; +} +#endif + + +/**************************************************************************** + * + > $Function: openServer() + * + * $Description: create a listen server socket on the designated port. + * + * $Return: (int) . . . A connection socket. -1 for errors. + * + * $Errors: None + * + ****************************************************************************/ +static int openServer(void) +{ + struct sockaddr_in lsocket; + int fd; + + /* create the socket right now */ + /* inet_addr() returns a value that is already in network order */ + memset(&lsocket, 0, sizeof(lsocket)); + lsocket.sin_family = AF_INET; + lsocket.sin_addr.s_addr = INADDR_ANY; + lsocket.sin_port = htons(config->port); + fd = xsocket(AF_INET, SOCK_STREAM, 0); + /* tell the OS it's OK to reuse a previous address even though */ + /* it may still be in a close down state. Allows bind to succeed. */ +#ifdef SO_REUSEPORT + { + static const int on = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, + (void *)&on, sizeof(on)); + } +#else + setsockopt_reuseaddr(fd); +#endif + xbind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)); + xlisten(fd, 9); + signal(SIGCHLD, SIG_IGN); /* prevent zombie (defunct) processes */ + return fd; +} + +/**************************************************************************** + * + > $Function: sendHeaders() + * + * $Description: Create and send HTTP response headers. + * The arguments are combined and sent as one write operation. Note that + * IE will puke big-time if the headers are not sent in one packet and the + * second packet is delayed for any reason. + * + * $Parameter: + * (HttpResponseNum) responseNum . . . The result code to send. + * + * $Return: (int) . . . . writing errors + * + ****************************************************************************/ +static int sendHeaders(HttpResponseNum responseNum) +{ + char *buf = config->buf; + const char *responseString = ""; + const char *infoString = 0; + const char *mime_type; + unsigned int i; + time_t timer = time(0); + char timeStr[80]; + int len; + enum { + numNames = sizeof(httpResponseNames) / sizeof(httpResponseNames[0]) + }; + + for (i = 0; i < numNames; i++) { + if (httpResponseNames[i].type == responseNum) { + responseString = httpResponseNames[i].name; + infoString = httpResponseNames[i].info; + break; + } + } + /* error message is HTML */ + mime_type = responseNum == HTTP_OK ? + config->found_mime_type : "text/html"; + + /* emit the current date */ + strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer)); + len = sprintf(buf, + "HTTP/1.0 %d %s\r\nContent-type: %s\r\n" + "Date: %s\r\nConnection: close\r\n", + responseNum, responseString, mime_type, timeStr); + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (responseNum == HTTP_UNAUTHORIZED) { + len += sprintf(buf+len, "WWW-Authenticate: Basic realm=\"%s\"\r\n", + config->realm); + } +#endif + if (responseNum == HTTP_MOVED_TEMPORARILY) { + len += sprintf(buf+len, "Location: %s/%s%s\r\n", + config->found_moved_temporarily, + (config->query ? "?" : ""), + (config->query ? config->query : "")); + } + + if (config->ContentLength != -1) { /* file */ + strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&config->last_mod)); + len += sprintf(buf+len, "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n", + timeStr, "Content-length:", config->ContentLength); + } + strcat(buf, "\r\n"); + len += 2; + if (infoString) { + len += sprintf(buf+len, + "%d %s\n" + "

    %d %s

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

    Index of $QUERY_STRING

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

    "$'\r\n' +} | dd bs=4k diff --git a/networking/ifconfig.c b/networking/ifconfig.c new file mode 100644 index 000000000..ae5b468ce --- /dev/null +++ b/networking/ifconfig.c @@ -0,0 +1,548 @@ +/* vi: set sw=4 ts=4: */ +/* ifconfig + * + * Similar to the standard Unix ifconfig, but with only the necessary + * parts for AF_INET, and without any printing of if info (for now). + * + * Bjorn Wesen, Axis Communications AB + * + * + * Authors of the original ifconfig was: + * Fred N. van Kempen, + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + * Heavily modified by Manuel Novoa III Mar 6, 2001 + * + * From initial port to busybox, removed most of the redundancy by + * converting to a table-driven approach. Added several (optional) + * args missing from initial port. + * + * Still missing: media, tunnel. + * + * 2002-04-20 + * IPV6 support added by Bart Visscher + */ + +#include +#include +#include +#if __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1 +#include +#include +#else +#include +#include +#endif +#include "inet_common.h" +#include "busybox.h" + +#if ENABLE_FEATURE_IFCONFIG_SLIP +# include +#endif + +/* I don't know if this is needed for busybox or not. Anyone? */ +#define QUESTIONABLE_ALIAS_CASE + + +/* Defines for glibc2.0 users. */ +#ifndef SIOCSIFTXQLEN +# define SIOCSIFTXQLEN 0x8943 +# define SIOCGIFTXQLEN 0x8942 +#endif + +/* ifr_qlen is ifru_ivalue, but it isn't present in 2.0 kernel headers */ +#ifndef ifr_qlen +# define ifr_qlen ifr_ifru.ifru_mtu +#endif + +#ifndef IFF_DYNAMIC +# define IFF_DYNAMIC 0x8000 /* dialup device with changing addresses */ +#endif + +#if ENABLE_FEATURE_IPV6 +struct in6_ifreq { + struct in6_addr ifr6_addr; + uint32_t ifr6_prefixlen; + int ifr6_ifindex; +}; +#endif + +/* + * Here are the bit masks for the "flags" member of struct options below. + * N_ signifies no arg prefix; M_ signifies arg prefixed by '-'. + * CLR clears the flag; SET sets the flag; ARG signifies (optional) arg. + */ +#define N_CLR 0x01 +#define M_CLR 0x02 +#define N_SET 0x04 +#define M_SET 0x08 +#define N_ARG 0x10 +#define M_ARG 0x20 + +#define M_MASK (M_CLR | M_SET | M_ARG) +#define N_MASK (N_CLR | N_SET | N_ARG) +#define SET_MASK (N_SET | M_SET) +#define CLR_MASK (N_CLR | M_CLR) +#define SET_CLR_MASK (SET_MASK | CLR_MASK) +#define ARG_MASK (M_ARG | N_ARG) + +/* + * Here are the bit masks for the "arg_flags" member of struct options below. + */ + +/* + * cast type: + * 00 int + * 01 char * + * 02 HOST_COPY in_ether + * 03 HOST_COPY INET_resolve + */ +#define A_CAST_TYPE 0x03 +/* + * map type: + * 00 not a map type (mem_start, io_addr, irq) + * 04 memstart (unsigned long) + * 08 io_addr (unsigned short) + * 0C irq (unsigned char) + */ +#define A_MAP_TYPE 0x0C +#define A_ARG_REQ 0x10 /* Set if an arg is required. */ +#define A_NETMASK 0x20 /* Set if netmask (check for multiple sets). */ +#define A_SET_AFTER 0x40 /* Set a flag at the end. */ +#define A_COLON_CHK 0x80 /* Is this needed? See below. */ +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS +#define A_HOSTNAME 0x100 /* Set if it is ip addr. */ +#define A_BROADCAST 0x200 /* Set if it is broadcast addr. */ +#else +#define A_HOSTNAME 0 +#define A_BROADCAST 0 +#endif + +/* + * These defines are for dealing with the A_CAST_TYPE field. + */ +#define A_CAST_CHAR_PTR 0x01 +#define A_CAST_RESOLVE 0x01 +#define A_CAST_HOST_COPY 0x02 +#define A_CAST_HOST_COPY_IN_ETHER A_CAST_HOST_COPY +#define A_CAST_HOST_COPY_RESOLVE (A_CAST_HOST_COPY | A_CAST_RESOLVE) + +/* + * These defines are for dealing with the A_MAP_TYPE field. + */ +#define A_MAP_ULONG 0x04 /* memstart */ +#define A_MAP_USHORT 0x08 /* io_addr */ +#define A_MAP_UCHAR 0x0C /* irq */ + +/* + * Define the bit masks signifying which operations to perform for each arg. + */ + +#define ARG_METRIC (A_ARG_REQ /*| A_CAST_INT*/) +#define ARG_MTU (A_ARG_REQ /*| A_CAST_INT*/) +#define ARG_TXQUEUELEN (A_ARG_REQ /*| A_CAST_INT*/) +#define ARG_MEM_START (A_ARG_REQ | A_MAP_ULONG) +#define ARG_IO_ADDR (A_ARG_REQ | A_MAP_ULONG) +#define ARG_IRQ (A_ARG_REQ | A_MAP_UCHAR) +#define ARG_DSTADDR (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE) +#define ARG_NETMASK (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_NETMASK) +#define ARG_BROADCAST (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER | A_BROADCAST) +#define ARG_HW (A_ARG_REQ | A_CAST_HOST_COPY_IN_ETHER) +#define ARG_POINTOPOINT (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER) +#define ARG_KEEPALIVE (A_ARG_REQ | A_CAST_CHAR_PTR) +#define ARG_OUTFILL (A_ARG_REQ | A_CAST_CHAR_PTR) +#define ARG_HOSTNAME (A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER | A_COLON_CHK | A_HOSTNAME) +#define ARG_ADD_DEL (A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER) + + +/* + * Set up the tables. Warning! They must have corresponding order! + */ + +struct arg1opt { + const char *name; + int selector; + unsigned short ifr_offset; +}; + +struct options { + const char *name; +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + const unsigned int flags:6; + const unsigned int arg_flags:10; +#else + const unsigned char flags; + const unsigned char arg_flags; +#endif + const unsigned short selector; +}; + +#define ifreq_offsetof(x) offsetof(struct ifreq, x) + +static const struct arg1opt Arg1Opt[] = { + {"SIOCSIFMETRIC", SIOCSIFMETRIC, ifreq_offsetof(ifr_metric)}, + {"SIOCSIFMTU", SIOCSIFMTU, ifreq_offsetof(ifr_mtu)}, + {"SIOCSIFTXQLEN", SIOCSIFTXQLEN, ifreq_offsetof(ifr_qlen)}, + {"SIOCSIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr)}, + {"SIOCSIFNETMASK", SIOCSIFNETMASK, ifreq_offsetof(ifr_netmask)}, + {"SIOCSIFBRDADDR", SIOCSIFBRDADDR, ifreq_offsetof(ifr_broadaddr)}, +#if ENABLE_FEATURE_IFCONFIG_HW + {"SIOCSIFHWADDR", SIOCSIFHWADDR, ifreq_offsetof(ifr_hwaddr)}, +#endif + {"SIOCSIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr)}, +#ifdef SIOCSKEEPALIVE + {"SIOCSKEEPALIVE", SIOCSKEEPALIVE, ifreq_offsetof(ifr_data)}, +#endif +#ifdef SIOCSOUTFILL + {"SIOCSOUTFILL", SIOCSOUTFILL, ifreq_offsetof(ifr_data)}, +#endif +#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + {"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.mem_start)}, + {"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.base_addr)}, + {"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.irq)}, +#endif + /* Last entry if for unmatched (possibly hostname) arg. */ +#if ENABLE_FEATURE_IPV6 + {"SIOCSIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr)}, /* IPv6 version ignores the offset */ + {"SIOCDIFADDR", SIOCDIFADDR, ifreq_offsetof(ifr_addr)}, /* IPv6 version ignores the offset */ +#endif + {"SIOCSIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr)}, +}; + +static const struct options OptArray[] = { + {"metric", N_ARG, ARG_METRIC, 0}, + {"mtu", N_ARG, ARG_MTU, 0}, + {"txqueuelen", N_ARG, ARG_TXQUEUELEN, 0}, + {"dstaddr", N_ARG, ARG_DSTADDR, 0}, + {"netmask", N_ARG, ARG_NETMASK, 0}, + {"broadcast", N_ARG | M_CLR, ARG_BROADCAST, IFF_BROADCAST}, +#if ENABLE_FEATURE_IFCONFIG_HW + {"hw", N_ARG, ARG_HW, 0}, +#endif + {"pointopoint", N_ARG | M_CLR, ARG_POINTOPOINT, IFF_POINTOPOINT}, +#ifdef SIOCSKEEPALIVE + {"keepalive", N_ARG, ARG_KEEPALIVE, 0}, +#endif +#ifdef SIOCSOUTFILL + {"outfill", N_ARG, ARG_OUTFILL, 0}, +#endif +#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + {"mem_start", N_ARG, ARG_MEM_START, 0}, + {"io_addr", N_ARG, ARG_IO_ADDR, 0}, + {"irq", N_ARG, ARG_IRQ, 0}, +#endif +#if ENABLE_FEATURE_IPV6 + {"add", N_ARG, ARG_ADD_DEL, 0}, + {"del", N_ARG, ARG_ADD_DEL, 0}, +#endif + {"arp", N_CLR | M_SET, 0, IFF_NOARP}, + {"trailers", N_CLR | M_SET, 0, IFF_NOTRAILERS}, + {"promisc", N_SET | M_CLR, 0, IFF_PROMISC}, + {"multicast", N_SET | M_CLR, 0, IFF_MULTICAST}, + {"allmulti", N_SET | M_CLR, 0, IFF_ALLMULTI}, + {"dynamic", N_SET | M_CLR, 0, IFF_DYNAMIC}, + {"up", N_SET, 0, (IFF_UP | IFF_RUNNING)}, + {"down", N_CLR, 0, IFF_UP}, + {NULL, 0, ARG_HOSTNAME, (IFF_UP | IFF_RUNNING)} +}; + +/* + * A couple of prototypes. + */ + +#if ENABLE_FEATURE_IFCONFIG_HW +static int in_ether(const char *bufp, struct sockaddr *sap); +#endif + +#if ENABLE_FEATURE_IFCONFIG_STATUS +extern int interface_opt_a; +extern int display_interfaces(char *ifname); +#endif + +/* + * Our main function. + */ + +int ifconfig_main(int argc, char **argv) +{ + struct ifreq ifr; + struct sockaddr_in sai; +#if ENABLE_FEATURE_IPV6 + struct sockaddr_in6 sai6; +#endif +#if ENABLE_FEATURE_IFCONFIG_HW + struct sockaddr sa; +#endif + const struct arg1opt *a1op; + const struct options *op; + int sockfd; /* socket fd we use to manipulate stuff with */ + int selector; +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + unsigned int mask; + unsigned int did_flags; + unsigned int sai_hostname, sai_netmask; +#else + unsigned char mask; + unsigned char did_flags; +#endif + char *p; + /*char host[128];*/ + const char *host = NULL; /* make gcc happy */ + + did_flags = 0; +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + sai_hostname = 0; + sai_netmask = 0; +#endif + + /* skip argv[0] */ + ++argv; + --argc; + +#if ENABLE_FEATURE_IFCONFIG_STATUS + if (argc > 0 && (argv[0][0] == '-' && argv[0][1] == 'a' && !argv[0][2])) { + interface_opt_a = 1; + --argc; + ++argv; + } +#endif + + if (argc <= 1) { +#if ENABLE_FEATURE_IFCONFIG_STATUS + return display_interfaces(argc ? *argv : NULL); +#else + bb_error_msg_and_die("no support for status display"); +#endif + } + + /* Create a channel to the NET kernel. */ + sockfd = xsocket(AF_INET, SOCK_DGRAM, 0); + + /* get interface name */ + safe_strncpy(ifr.ifr_name, *argv, IFNAMSIZ); + + /* Process the remaining arguments. */ + while (*++argv != (char *) NULL) { + p = *argv; + mask = N_MASK; + if (*p == '-') { /* If the arg starts with '-'... */ + ++p; /* advance past it and */ + mask = M_MASK; /* set the appropriate mask. */ + } + for (op = OptArray; op->name; op++) { /* Find table entry. */ + if (strcmp(p, op->name) == 0) { /* If name matches... */ + mask &= op->flags; + if (mask) /* set the mask and go. */ + goto FOUND_ARG; + /* If we get here, there was a valid arg with an */ + /* invalid '-' prefix. */ + bb_error_msg_and_die("bad: '%s'", p-1); + } + } + + /* We fell through, so treat as possible hostname. */ + a1op = Arg1Opt + (sizeof(Arg1Opt) / sizeof(Arg1Opt[0])) - 1; + mask = op->arg_flags; + goto HOSTNAME; + + FOUND_ARG: + if (mask & ARG_MASK) { + mask = op->arg_flags; + a1op = Arg1Opt + (op - OptArray); + if (mask & A_NETMASK & did_flags) + bb_show_usage(); + if (*++argv == NULL) { + if (mask & A_ARG_REQ) + bb_show_usage(); + --argv; + mask &= A_SET_AFTER; /* just for broadcast */ + } else { /* got an arg so process it */ + HOSTNAME: + did_flags |= (mask & (A_NETMASK|A_HOSTNAME)); + if (mask & A_CAST_HOST_COPY) { +#if ENABLE_FEATURE_IFCONFIG_HW + if (mask & A_CAST_RESOLVE) { +#endif +#if ENABLE_FEATURE_IPV6 + char *prefix; + int prefix_len = 0; +#endif + /*safe_strncpy(host, *argv, (sizeof host));*/ + host = *argv; +#if ENABLE_FEATURE_IPV6 + prefix = strchr(host, '/'); + if (prefix) { + prefix_len = xatou_range(prefix + 1, 0, 128); + *prefix = '\0'; + } +#endif + sai.sin_family = AF_INET; + sai.sin_port = 0; + if (!strcmp(host, bb_str_default)) { + /* Default is special, meaning 0.0.0.0. */ + sai.sin_addr.s_addr = INADDR_ANY; + } +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + else if ((host[0] == '+' && !host[1]) && (mask & A_BROADCAST) + && (did_flags & (A_NETMASK|A_HOSTNAME)) == (A_NETMASK|A_HOSTNAME) + ) { + /* + is special, meaning broadcast is derived. */ + sai.sin_addr.s_addr = (~sai_netmask) | (sai_hostname & sai_netmask); + } +#endif +#if ENABLE_FEATURE_IPV6 + else if (inet_pton(AF_INET6, host, &sai6.sin6_addr) > 0) { + int sockfd6; + struct in6_ifreq ifr6; + + memcpy((char *) &ifr6.ifr6_addr, + (char *) &sai6.sin6_addr, + sizeof(struct in6_addr)); + + /* Create a channel to the NET kernel. */ + sockfd6 = xsocket(AF_INET6, SOCK_DGRAM, 0); + if (ioctl(sockfd6, SIOGIFINDEX, &ifr) < 0) + bb_perror_msg_and_die("SIOGIFINDEX"); + ifr6.ifr6_ifindex = ifr.ifr_ifindex; + ifr6.ifr6_prefixlen = prefix_len; + if (ioctl(sockfd6, a1op->selector, &ifr6) < 0) + bb_perror_msg_and_die(a1op->name); + continue; + } +#endif + else if (inet_aton(host, &sai.sin_addr) == 0) { + /* It's not a dotted quad. */ + struct hostent *hp = xgethostbyname(host); + memcpy((char *) &sai.sin_addr, (char *) hp->h_addr_list[0], + sizeof(struct in_addr)); + } +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + if (mask & A_HOSTNAME) + sai_hostname = sai.sin_addr.s_addr; + if (mask & A_NETMASK) + sai_netmask = sai.sin_addr.s_addr; +#endif + p = (char *) &sai; +#if ENABLE_FEATURE_IFCONFIG_HW + } else { /* A_CAST_HOST_COPY_IN_ETHER */ + /* This is the "hw" arg case. */ + if (strcmp("ether", *argv) || !*++argv) + bb_show_usage(); + /*safe_strncpy(host, *argv, sizeof(host));*/ + host = *argv; + if (in_ether(host, &sa)) + bb_error_msg_and_die("invalid hw-addr %s", host); + p = (char *) &sa; + } +#endif + memcpy( (((char *)&ifr) + a1op->ifr_offset), + p, sizeof(struct sockaddr)); + } else { + /* FIXME: error check?? */ + unsigned long i = strtoul(*argv, NULL, 0); + p = ((char *)&ifr) + a1op->ifr_offset; +#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + if (mask & A_MAP_TYPE) { + if (ioctl(sockfd, SIOCGIFMAP, &ifr) < 0) + bb_perror_msg_and_die("SIOCGIFMAP"); + if ((mask & A_MAP_UCHAR) == A_MAP_UCHAR) + *((unsigned char *) p) = i; + else if (mask & A_MAP_USHORT) + *((unsigned short *) p) = i; + else + *((unsigned long *) p) = i; + } else +#endif + if (mask & A_CAST_CHAR_PTR) + *((caddr_t *) p) = (caddr_t) i; + else /* A_CAST_INT */ + *((int *) p) = i; + } + + if (ioctl(sockfd, a1op->selector, &ifr) < 0) + bb_perror_msg_and_die(a1op->name); +#ifdef QUESTIONABLE_ALIAS_CASE + if (mask & A_COLON_CHK) { + /* + * Don't do the set_flag() if the address is an alias with + * a '-' at the end, since it's deleted already! - Roman + * + * Should really use regex.h here, not sure though how well + * it'll go with the cross-platform support etc. + */ + char *ptr; + short int found_colon = 0; + for (ptr = ifr.ifr_name; *ptr; ptr++) + if (*ptr == ':') + found_colon++; + if (found_colon && ptr[-1] == '-') + continue; + } +#endif + } + if (!(mask & A_SET_AFTER)) + continue; + mask = N_SET; + } + + if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) + bb_perror_msg_and_die("SIOCGIFFLAGS"); + selector = op->selector; + if (mask & SET_MASK) + ifr.ifr_flags |= selector; + else + ifr.ifr_flags &= ~selector; + if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) + bb_perror_msg_and_die("SIOCSIFFLAGS"); + } /* while() */ + + if (ENABLE_FEATURE_CLEAN_UP) + close(sockfd); + return 0; +} + +#if ENABLE_FEATURE_IFCONFIG_HW +/* Input an Ethernet address and convert to binary. */ +static int in_ether(const char *bufp, struct sockaddr *sap) +{ + char *ptr; + int i, j; + unsigned char val; + unsigned char c; + + sap->sa_family = ARPHRD_ETHER; + ptr = sap->sa_data; + + i = 0; + do { + j = val = 0; + + /* We might get a semicolon here - not required. */ + if (i && (*bufp == ':')) { + bufp++; + } + + do { + c = *bufp; + if (((unsigned char)(c - '0')) <= 9) { + c -= '0'; + } else if (((unsigned char)((c|0x20) - 'a')) <= 5) { + c = (c|0x20) - ('a'-10); + } else if (j && (c == ':' || c == 0)) { + break; + } else { + return -1; + } + ++bufp; + val <<= 4; + val += c; + } while (++j < 2); + *ptr++ = val; + } while (++i < ETH_ALEN); + + return *bufp; /* Error if we don't end at end of string. */ +} +#endif diff --git a/networking/ifupdown.c b/networking/ifupdown.c new file mode 100644 index 000000000..76ff2a830 --- /dev/null +++ b/networking/ifupdown.c @@ -0,0 +1,1270 @@ +/* vi: set sw=4 ts=4: */ +/* + * ifupdown for busybox + * Copyright (c) 2002 Glenn McGrath + * Copyright (c) 2003-2004 Erik Andersen + * + * Based on ifupdown v 0.6.4 by Anthony Towns + * Copyright (c) 1999 Anthony Towns + * + * Changes to upstream version + * Remove checks for kernel version, assume kernel version 2.2.0 or better. + * Lines in the interfaces file cannot wrap. + * To adhere to the FHS, the default state file is /var/run/ifstate. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include +#include +#include + +#define MAX_OPT_DEPTH 10 +#define EUNBALBRACK 10001 +#define EUNDEFVAR 10002 +#define EUNBALPER 10000 + +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING +#define MAX_INTERFACE_LENGTH 10 +#endif + +#define debug_noise(fmt, args...) + +/* Forward declaration */ +struct interface_defn_t; + +typedef int execfn(char *command); + +struct method_t +{ + char *name; + int (*up)(struct interface_defn_t *ifd, execfn *e); + int (*down)(struct interface_defn_t *ifd, execfn *e); +}; + +struct address_family_t +{ + char *name; + int n_methods; + const struct method_t *method; +}; + +struct mapping_defn_t +{ + struct mapping_defn_t *next; + + int max_matches; + int n_matches; + char **match; + + char *script; + + int max_mappings; + int n_mappings; + char **mapping; +}; + +struct variable_t +{ + char *name; + char *value; +}; + +struct interface_defn_t +{ + const struct address_family_t *address_family; + const struct method_t *method; + + char *iface; + int max_options; + int n_options; + struct variable_t *option; +}; + +struct interfaces_file_t +{ + llist_t *autointerfaces; + llist_t *ifaces; + struct mapping_defn_t *mappings; +}; + +#define OPTION_STR "anvf" USE_FEATURE_IFUPDOWN_MAPPING("m") "i:" +enum { + OPT_do_all = 0x1, + OPT_no_act = 0x2, + OPT_verbose = 0x4, + OPT_force = 0x8, + OPT_no_mappings = 0x10, +}; +#define DO_ALL (option_mask32 & OPT_do_all) +#define NO_ACT (option_mask32 & OPT_no_act) +#define VERBOSE (option_mask32 & OPT_verbose) +#define FORCE (option_mask32 & OPT_force) +#define NO_MAPPINGS (option_mask32 & OPT_no_mappings) + +static char **__myenviron; + +static char *startup_PATH; + +#if ENABLE_FEATURE_IFUPDOWN_IPV4 || ENABLE_FEATURE_IFUPDOWN_IPV6 + +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + +static unsigned count_bits(unsigned a) +{ + unsigned result; + result = (a & 0x55) + ((a >> 1) & 0x55); + result = (result & 0x33) + ((result >> 2) & 0x33); + return (result & 0x0F) + ((result >> 4) & 0x0F); +} + +static int count_netmask_bits(char *dotted_quad) +{ + unsigned result, a, b, c, d; + /* Found a netmask... Check if it is dotted quad */ + if (sscanf(dotted_quad, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) + return -1; + // FIXME: will be confused by e.g. 255.0.255.0 + result = count_bits(a); + result += count_bits(b); + result += count_bits(c); + result += count_bits(d); + return (int)result; +} +#endif + +static void addstr(char **bufp, const char *str, size_t str_length) +{ + /* xasprintf trick will be smaller, but we are often + * called with str_length == 1 - don't want to have + * THAT much of malloc/freeing! */ + char *buf = *bufp; + int len = (buf ? strlen(buf) : 0); + str_length++; + buf = xrealloc(buf, len + str_length); + /* copies at most str_length-1 chars! */ + safe_strncpy(buf + len, str, str_length); + *bufp = buf; +} + +static int strncmpz(const char *l, const char *r, size_t llen) +{ + int i = strncmp(l, r, llen); + + if (i == 0) + return -r[llen]; + return i; +} + +static char *get_var(const char *id, size_t idlen, struct interface_defn_t *ifd) +{ + int i; + + if (strncmpz(id, "iface", idlen) == 0) { + char *result; + static char label_buf[20]; + safe_strncpy(label_buf, ifd->iface, sizeof(label_buf)); + result = strchr(label_buf, ':'); + if (result) { + *result = '\0'; + } + return label_buf; + } + if (strncmpz(id, "label", idlen) == 0) { + return ifd->iface; + } + for (i = 0; i < ifd->n_options; i++) { + if (strncmpz(id, ifd->option[i].name, idlen) == 0) { + return ifd->option[i].value; + } + } + return NULL; +} + +static char *parse(const char *command, struct interface_defn_t *ifd) +{ + char *result = NULL; + size_t old_pos[MAX_OPT_DEPTH] = { 0 }; + int okay[MAX_OPT_DEPTH] = { 1 }; + int opt_depth = 1; + + while (*command) { + switch (*command) { + default: + addstr(&result, command, 1); + command++; + break; + case '\\': + if (command[1]) { + addstr(&result, command + 1, 1); + command += 2; + } else { + addstr(&result, command, 1); + command++; + } + break; + case '[': + if (command[1] == '[' && opt_depth < MAX_OPT_DEPTH) { + old_pos[opt_depth] = strlen(result); + okay[opt_depth] = 1; + opt_depth++; + command += 2; + } else { + addstr(&result, "[", 1); + command++; + } + break; + case ']': + if (command[1] == ']' && opt_depth > 1) { + opt_depth--; + if (!okay[opt_depth]) { + result[old_pos[opt_depth]] = '\0'; + } + command += 2; + } else { + addstr(&result, "]", 1); + command++; + } + break; + case '%': + { + char *nextpercent; + char *varvalue; + + command++; + nextpercent = strchr(command, '%'); + if (!nextpercent) { + errno = EUNBALPER; + free(result); + return NULL; + } + + varvalue = get_var(command, nextpercent - command, ifd); + + if (varvalue) { + addstr(&result, varvalue, strlen(varvalue)); + } else { +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + /* Sigh... Add a special case for 'ip' to convert from + * dotted quad to bit count style netmasks. */ + if (strncmp(command, "bnmask", 6)==0) { + unsigned res; + varvalue = get_var("netmask", 7, ifd); + if (varvalue && (res = count_netmask_bits(varvalue)) > 0) { + const char *argument = utoa(res); + addstr(&result, argument, strlen(argument)); + command = nextpercent + 1; + break; + } + } +#endif + okay[opt_depth - 1] = 0; + } + + command = nextpercent + 1; + } + break; + } + } + + if (opt_depth > 1) { + errno = EUNBALBRACK; + free(result); + return NULL; + } + + if (!okay[0]) { + errno = EUNDEFVAR; + free(result); + return NULL; + } + + return result; +} + +/* execute() returns 1 for success and 0 for failure */ +static int execute(const char *command, struct interface_defn_t *ifd, execfn *exec) +{ + char *out; + int ret; + + out = parse(command, ifd); + if (!out) { + return 0; + } + ret = (*exec)(out); + + free(out); + if (ret != 1) { + return 0; + } + return 1; +} +#endif + +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV6 +static int loopback_up6(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + int result; + result = execute("ip addr add ::1 dev %iface%", ifd, exec); + result += execute("ip link set %iface% up", ifd, exec); + return ((result == 2) ? 2 : 0); +#else + return execute("ifconfig %iface% add ::1", ifd, exec); +#endif +} + +static int loopback_down6(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + return execute("ip link set %iface% down", ifd, exec); +#else + return execute("ifconfig %iface% del ::1", ifd, exec); +#endif +} + +static int static_up6(struct interface_defn_t *ifd, execfn *exec) +{ + int result; +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + result = execute("ip addr add %address%/%netmask% dev %iface% [[label %label%]]", ifd, exec); + result += execute("ip link set [[mtu %mtu%]] [[address %hwaddress%]] %iface% up", ifd, exec); + result += execute("[[ ip route add ::/0 via %gateway% ]]", ifd, exec); +#else + result = execute("ifconfig %iface% [[media %media%]] [[hw %hwaddress%]] [[mtu %mtu%]] up", ifd, exec); + result += execute("ifconfig %iface% add %address%/%netmask%", ifd, exec); + result += execute("[[ route -A inet6 add ::/0 gw %gateway% ]]", ifd, exec); +#endif + return ((result == 3) ? 3 : 0); +} + +static int static_down6(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + return execute("ip link set %iface% down", ifd, exec); +#else + return execute("ifconfig %iface% down", ifd, exec); +#endif +} + +#ifdef CONFIG_FEATURE_IFUPDOWN_IP +static int v4tunnel_up(struct interface_defn_t *ifd, execfn *exec) +{ + int result; + result = execute("ip tunnel add %iface% mode sit remote " + "%endpoint% [[local %local%]] [[ttl %ttl%]]", ifd, exec); + result += execute("ip link set %iface% up", ifd, exec); + result += execute("ip addr add %address%/%netmask% dev %iface%", ifd, exec); + result += execute("[[ ip route add ::/0 via %gateway% ]]", ifd, exec); + return ((result == 4) ? 4 : 0); +} + +static int v4tunnel_down(struct interface_defn_t * ifd, execfn * exec) +{ + return execute("ip tunnel del %iface%", ifd, exec); +} +#endif + +static const struct method_t methods6[] = { +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + { "v4tunnel", v4tunnel_up, v4tunnel_down, }, +#endif + { "static", static_up6, static_down6, }, + { "loopback", loopback_up6, loopback_down6, }, +}; + +static const struct address_family_t addr_inet6 = { + "inet6", + sizeof(methods6) / sizeof(struct method_t), + methods6 +}; +#endif /* CONFIG_FEATURE_IFUPDOWN_IPV6 */ + +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV4 +static int loopback_up(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + int result; + result = execute("ip addr add 127.0.0.1/8 dev %iface%", ifd, exec); + result += execute("ip link set %iface% up", ifd, exec); + return ((result == 2) ? 2 : 0); +#else + return execute("ifconfig %iface% 127.0.0.1 up", ifd, exec); +#endif +} + +static int loopback_down(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + int result; + result = execute("ip addr flush dev %iface%", ifd, exec); + result += execute("ip link set %iface% down", ifd, exec); + return ((result == 2) ? 2 : 0); +#else + return execute("ifconfig %iface% 127.0.0.1 down", ifd, exec); +#endif +} + +static int static_up(struct interface_defn_t *ifd, execfn *exec) +{ + int result; +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + result = execute("ip addr add %address%/%bnmask% [[broadcast %broadcast%]] " + "dev %iface% [[peer %pointopoint%]] [[label %label%]]", ifd, exec); + result += execute("ip link set [[mtu %mtu%]] [[address %hwaddress%]] %iface% up", ifd, exec); + result += execute("[[ ip route add default via %gateway% dev %iface% ]]", ifd, exec); + return ((result == 3) ? 3 : 0); +#else + /* ifconfig said to set iface up before it processes hw %hwaddress%, + * which then of course fails. Thus we run two separate ifconfig */ + result = execute("ifconfig %iface% [[hw %hwaddress%]] [[media %media%]] [[mtu %mtu%]] up", + ifd, exec); + result += execute("ifconfig %iface% %address% netmask %netmask% " + "[[broadcast %broadcast%]] [[pointopoint %pointopoint%]] ", + ifd, exec); + result += execute("[[ route add default gw %gateway% %iface% ]]", ifd, exec); + return ((result == 3) ? 3 : 0); +#endif +} + +static int static_down(struct interface_defn_t *ifd, execfn *exec) +{ + int result; +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + result = execute("ip addr flush dev %iface%", ifd, exec); + result += execute("ip link set %iface% down", ifd, exec); +#else + result = execute("[[ route del default gw %gateway% %iface% ]]", ifd, exec); + result += execute("ifconfig %iface% down", ifd, exec); +#endif + return ((result == 2) ? 2 : 0); +} + +#ifndef CONFIG_APP_UDHCPC +struct dhcp_client_t +{ + const char *name; + const char *startcmd; + const char *stopcmd; +}; + +static const struct dhcp_client_t ext_dhcp_clients[] = { + { "udhcpc", + "udhcpc -R -n -p /var/run/udhcpc.%iface%.pid -i %iface% [[-H %hostname%]] [[-c %clientid%]] [[-s %script%]]", + "kill -TERM `cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", + }, + { "pump", + "pump -i %iface% [[-h %hostname%]] [[-l %leasehours%]]", + "pump -i %iface% -k", + }, + { "dhclient", + "dhclient -pf /var/run/dhclient.%iface%.pid %iface%", + "kill -9 `cat /var/run/dhclient.%iface%.pid` 2>/dev/null", + }, + { "dhcpcd", + "dhcpcd [[-h %hostname%]] [[-i %vendor%]] [[-I %clientid%]] [[-l %leasetime%]] %iface%", + "dhcpcd -k %iface%", + }, +}; +#endif + +static int dhcp_up(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_APP_UDHCPC + return execute("udhcpc -R -n -p /var/run/udhcpc.%iface%.pid " + "-i %iface% [[-H %hostname%]] [[-c %clientid%]] [[-s %script%]]", + ifd, exec); +#else + int i, nclients = sizeof(ext_dhcp_clients) / sizeof(ext_dhcp_clients[0]); + for (i = 0; i < nclients; i++) { + if (exists_execable(ext_dhcp_clients[i].name)) + return execute(ext_dhcp_clients[i].startcmd, ifd, exec); + } + bb_error_msg("no dhcp clients found"); + return 0; +#endif +} + +static int dhcp_down(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_APP_UDHCPC + return execute("kill -TERM " + "`cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", ifd, exec); +#else + int i, nclients = sizeof(ext_dhcp_clients) / sizeof(ext_dhcp_clients[0]); + for (i = 0; i < nclients; i++) { + if (exists_execable(ext_dhcp_clients[i].name)) + return execute(ext_dhcp_clients[i].stopcmd, ifd, exec); + } + bb_error_msg("no dhcp clients found, using static interface shutdown"); + return static_down(ifd, exec); +#endif +} + +static int manual_up_down(struct interface_defn_t *ifd, execfn *exec) +{ + return 1; +} + +static int bootp_up(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("bootpc [[--bootfile %bootfile%]] --dev %iface% " + "[[--server %server%]] [[--hwaddr %hwaddr%]] " + "--returniffail --serverbcast", ifd, exec); +} + +static int ppp_up(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("pon [[%provider%]]", ifd, exec); +} + +static int ppp_down(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("poff [[%provider%]]", ifd, exec); +} + +static int wvdial_up(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("/sbin/start-stop-daemon --start -x /usr/bin/wvdial " + "-p /var/run/wvdial.%iface% -b -m -- [[ %provider% ]]", ifd, exec); +} + +static int wvdial_down(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("/sbin/start-stop-daemon --stop -x /usr/bin/wvdial " + "-p /var/run/wvdial.%iface% -s 2", ifd, exec); +} + +static const struct method_t methods[] = { + { "manual", manual_up_down, manual_up_down, }, + { "wvdial", wvdial_up, wvdial_down, }, + { "ppp", ppp_up, ppp_down, }, + { "static", static_up, static_down, }, + { "bootp", bootp_up, static_down, }, + { "dhcp", dhcp_up, dhcp_down, }, + { "loopback", loopback_up, loopback_down, }, +}; + +static const struct address_family_t addr_inet = { + "inet", + sizeof(methods) / sizeof(struct method_t), + methods +}; + +#endif /* ifdef CONFIG_FEATURE_IFUPDOWN_IPV4 */ + +static char *next_word(char **buf) +{ + unsigned short length; + char *word; + + if (!buf || !*buf || !**buf) { + return NULL; + } + + /* Skip over leading whitespace */ + word = skip_whitespace(*buf); + + /* Skip over comments */ + if (*word == '#') { + return NULL; + } + + /* Find the length of this word */ + length = strcspn(word, " \t\n"); + if (length == 0) { + return NULL; + } + *buf = word + length; + /*DBU:[dave@cray.com] if we are already at EOL dont't increment beyond it */ + if (**buf) { + **buf = '\0'; + (*buf)++; + } + + return word; +} + +static const struct address_family_t *get_address_family(const struct address_family_t *const af[], char *name) +{ + int i; + + if (!name) + return NULL; + + for (i = 0; af[i]; i++) { + if (strcmp(af[i]->name, name) == 0) { + return af[i]; + } + } + return NULL; +} + +static const struct method_t *get_method(const struct address_family_t *af, char *name) +{ + int i; + + if (!name) + return NULL; + + for (i = 0; i < af->n_methods; i++) { + if (strcmp(af->method[i].name, name) == 0) { + return &af->method[i]; + } + } + return NULL; +} + +static const llist_t *find_list_string(const llist_t *list, const char *string) +{ + if (string == NULL) + return NULL; + + while (list) { + if (strcmp(list->data, string) == 0) { + return list; + } + list = list->link; + } + return NULL; +} + +static struct interfaces_file_t *read_interfaces(const char *filename) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + struct mapping_defn_t *currmap = NULL; +#endif + struct interface_defn_t *currif = NULL; + struct interfaces_file_t *defn; + FILE *f; + char *firstword; + char *buf; + + enum { NONE, IFACE, MAPPING } currently_processing = NONE; + + defn = xzalloc(sizeof(struct interfaces_file_t)); + + f = xfopen(filename, "r"); + + while ((buf = xmalloc_getline(f)) != NULL) { + char *buf_ptr = buf; + + firstword = next_word(&buf_ptr); + if (firstword == NULL) { + free(buf); + continue; /* blank line */ + } + + if (strcmp(firstword, "mapping") == 0) { +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + currmap = xzalloc(sizeof(struct mapping_defn_t)); + + while ((firstword = next_word(&buf_ptr)) != NULL) { + if (currmap->max_matches == currmap->n_matches) { + currmap->max_matches = currmap->max_matches * 2 + 1; + currmap->match = xrealloc(currmap->match, sizeof(currmap->match) * currmap->max_matches); + } + + currmap->match[currmap->n_matches++] = xstrdup(firstword); + } + currmap->max_mappings = 0; + currmap->n_mappings = 0; + currmap->mapping = NULL; + currmap->script = NULL; + { + struct mapping_defn_t **where = &defn->mappings; + while (*where != NULL) { + where = &(*where)->next; + } + *where = currmap; + currmap->next = NULL; + } + debug_noise("Added mapping\n"); +#endif + currently_processing = MAPPING; + } else if (strcmp(firstword, "iface") == 0) { + static const struct address_family_t *const addr_fams[] = { +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV4 + &addr_inet, +#endif +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV6 + &addr_inet6, +#endif + NULL + }; + + char *iface_name; + char *address_family_name; + char *method_name; + llist_t *iface_list; + + currif = xzalloc(sizeof(struct interface_defn_t)); + iface_name = next_word(&buf_ptr); + address_family_name = next_word(&buf_ptr); + method_name = next_word(&buf_ptr); + + if (buf_ptr == NULL) { + bb_error_msg("too few parameters for line \"%s\"", buf); + return NULL; + } + + /* ship any trailing whitespace */ + buf_ptr = skip_whitespace(buf_ptr); + + if (buf_ptr[0] != '\0') { + bb_error_msg("too many parameters \"%s\"", buf); + return NULL; + } + + currif->iface = xstrdup(iface_name); + + currif->address_family = get_address_family(addr_fams, address_family_name); + if (!currif->address_family) { + bb_error_msg("unknown address type \"%s\"", address_family_name); + return NULL; + } + + currif->method = get_method(currif->address_family, method_name); + if (!currif->method) { + bb_error_msg("unknown method \"%s\"", method_name); + return NULL; + } + + for (iface_list = defn->ifaces; iface_list; iface_list = iface_list->link) { + struct interface_defn_t *tmp = (struct interface_defn_t *) iface_list->data; + if ((strcmp(tmp->iface, currif->iface) == 0) && + (tmp->address_family == currif->address_family)) { + bb_error_msg("duplicate interface \"%s\"", tmp->iface); + return NULL; + } + } + llist_add_to_end(&(defn->ifaces), (char*)currif); + + debug_noise("iface %s %s %s\n", currif->iface, address_family_name, method_name); + currently_processing = IFACE; + } else if (strcmp(firstword, "auto") == 0) { + while ((firstword = next_word(&buf_ptr)) != NULL) { + + /* Check the interface isnt already listed */ + if (find_list_string(defn->autointerfaces, firstword)) { + bb_perror_msg_and_die("interface declared auto twice \"%s\"", buf); + } + + /* Add the interface to the list */ + llist_add_to_end(&(defn->autointerfaces), xstrdup(firstword)); + debug_noise("\nauto %s\n", firstword); + } + currently_processing = NONE; + } else { + switch (currently_processing) { + case IFACE: + { + int i; + + if (strlen(buf_ptr) == 0) { + bb_error_msg("option with empty value \"%s\"", buf); + return NULL; + } + + if (strcmp(firstword, "up") != 0 + && strcmp(firstword, "down") != 0 + && strcmp(firstword, "pre-up") != 0 + && strcmp(firstword, "post-down") != 0) { + for (i = 0; i < currif->n_options; i++) { + if (strcmp(currif->option[i].name, firstword) == 0) { + bb_error_msg("duplicate option \"%s\"", buf); + return NULL; + } + } + } + } + if (currif->n_options >= currif->max_options) { + struct variable_t *opt; + + currif->max_options = currif->max_options + 10; + opt = xrealloc(currif->option, sizeof(*opt) * currif->max_options); + currif->option = opt; + } + currif->option[currif->n_options].name = xstrdup(firstword); + currif->option[currif->n_options].value = xstrdup(buf_ptr); + if (!currif->option[currif->n_options].name) { + perror(filename); + return NULL; + } + if (!currif->option[currif->n_options].value) { + perror(filename); + return NULL; + } + debug_noise("\t%s=%s\n", currif->option[currif->n_options].name, + currif->option[currif->n_options].value); + currif->n_options++; + break; + case MAPPING: +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + if (strcmp(firstword, "script") == 0) { + if (currmap->script != NULL) { + bb_error_msg("duplicate script in mapping \"%s\"", buf); + return NULL; + } else { + currmap->script = xstrdup(next_word(&buf_ptr)); + } + } else if (strcmp(firstword, "map") == 0) { + if (currmap->max_mappings == currmap->n_mappings) { + currmap->max_mappings = currmap->max_mappings * 2 + 1; + currmap->mapping = xrealloc(currmap->mapping, sizeof(char *) * currmap->max_mappings); + } + currmap->mapping[currmap->n_mappings] = xstrdup(next_word(&buf_ptr)); + currmap->n_mappings++; + } else { + bb_error_msg("misplaced option \"%s\"", buf); + return NULL; + } +#endif + break; + case NONE: + default: + bb_error_msg("misplaced option \"%s\"", buf); + return NULL; + } + } + free(buf); + } + if (ferror(f) != 0) { + bb_perror_msg_and_die("%s", filename); + } + fclose(f); + + return defn; +} + +static char *setlocalenv(char *format, const char *name, const char *value) +{ + char *result; + char *here; + char *there; + + result = xasprintf(format, name, value); + + for (here = there = result; *there != '=' && *there; there++) { + if (*there == '-') + *there = '_'; + if (isalpha(*there)) + *there = toupper(*there); + + if (isalnum(*there) || *there == '_') { + *here = *there; + here++; + } + } + memmove(here, there, strlen(there) + 1); + + return result; +} + +static void set_environ(struct interface_defn_t *iface, const char *mode) +{ + char **environend; + int i; + const int n_env_entries = iface->n_options + 5; + char **ppch; + + if (__myenviron != NULL) { + for (ppch = __myenviron; *ppch; ppch++) { + free(*ppch); + *ppch = NULL; + } + free(__myenviron); + } + __myenviron = xzalloc(sizeof(char *) * (n_env_entries + 1 /* for final NULL */ )); + environend = __myenviron; + + for (i = 0; i < iface->n_options; i++) { + if (strcmp(iface->option[i].name, "up") == 0 + || strcmp(iface->option[i].name, "down") == 0 + || strcmp(iface->option[i].name, "pre-up") == 0 + || strcmp(iface->option[i].name, "post-down") == 0) { + continue; + } + *(environend++) = setlocalenv("IF_%s=%s", iface->option[i].name, iface->option[i].value); + } + + *(environend++) = setlocalenv("%s=%s", "IFACE", iface->iface); + *(environend++) = setlocalenv("%s=%s", "ADDRFAM", iface->address_family->name); + *(environend++) = setlocalenv("%s=%s", "METHOD", iface->method->name); + *(environend++) = setlocalenv("%s=%s", "MODE", mode); + *(environend++) = setlocalenv("%s=%s", "PATH", startup_PATH); +} + +static int doit(char *str) +{ + if (option_mask32 & (OPT_no_act|OPT_verbose)) { + puts(str); + } + if (!(option_mask32 & OPT_no_act)) { + pid_t child; + int status; + + fflush(NULL); + switch (child = fork()) { + case -1: /* failure */ + return 0; + case 0: /* child */ + execle(DEFAULT_SHELL, DEFAULT_SHELL, "-c", str, NULL, __myenviron); + exit(127); + } + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + return 0; + } + } + return 1; +} + +static int execute_all(struct interface_defn_t *ifd, const char *opt) +{ + int i; + char *buf; + for (i = 0; i < ifd->n_options; i++) { + if (strcmp(ifd->option[i].name, opt) == 0) { + if (!doit(ifd->option[i].value)) { + return 0; + } + } + } + + buf = xasprintf("run-parts /etc/network/if-%s.d", opt); + if (doit(buf) != 1) { + return 0; + } + return 1; +} + +static int check(char *str) { + return str != NULL; +} + +static int iface_up(struct interface_defn_t *iface) +{ + if (!iface->method->up(iface,check)) return -1; + set_environ(iface, "start"); + if (!execute_all(iface, "pre-up")) return 0; + if (!iface->method->up(iface, doit)) return 0; + if (!execute_all(iface, "up")) return 0; + return 1; +} + +static int iface_down(struct interface_defn_t *iface) +{ + if (!iface->method->down(iface,check)) return -1; + set_environ(iface, "stop"); + if (!execute_all(iface, "down")) return 0; + if (!iface->method->down(iface, doit)) return 0; + if (!execute_all(iface, "post-down")) return 0; + return 1; +} + +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING +static int popen2(FILE **in, FILE **out, char *command, ...) +{ + va_list ap; + char *argv[11] = { command }; + int argc; + int infd[2], outfd[2]; + pid_t pid; + + argc = 1; + va_start(ap, command); + while ((argc < 10) && (argv[argc] = va_arg(ap, char *))) { + argc++; + } + argv[argc] = NULL; /* make sure */ + va_end(ap); + + if (pipe(infd) != 0) { + return 0; + } + + if (pipe(outfd) != 0) { + close(infd[0]); + close(infd[1]); + return 0; + } + + fflush(NULL); + switch (pid = fork()) { + case -1: /* failure */ + close(infd[0]); + close(infd[1]); + close(outfd[0]); + close(outfd[1]); + return 0; + case 0: /* child */ + dup2(infd[0], 0); + dup2(outfd[1], 1); + close(infd[0]); + close(infd[1]); + close(outfd[0]); + close(outfd[1]); + execvp(command, argv); + exit(127); + default: /* parent */ + *in = fdopen(infd[1], "w"); + *out = fdopen(outfd[0], "r"); + close(infd[0]); + close(outfd[1]); + return pid; + } + /* unreached */ +} + +static char *run_mapping(char *physical, struct mapping_defn_t * map) +{ + FILE *in, *out; + int i, status; + pid_t pid; + + char *logical = xstrdup(physical); + + /* Run the mapping script. */ + pid = popen2(&in, &out, map->script, physical, NULL); + + /* popen2() returns 0 on failure. */ + if (pid == 0) + return logical; + + /* Write mappings to stdin of mapping script. */ + for (i = 0; i < map->n_mappings; i++) { + fprintf(in, "%s\n", map->mapping[i]); + } + fclose(in); + waitpid(pid, &status, 0); + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + /* If the mapping script exited successfully, try to + * grab a line of output and use that as the name of the + * logical interface. */ + char *new_logical = (char *)xmalloc(MAX_INTERFACE_LENGTH); + + if (fgets(new_logical, MAX_INTERFACE_LENGTH, out)) { + /* If we are able to read a line of output from the script, + * remove any trailing whitespace and use this value + * as the name of the logical interface. */ + char *pch = new_logical + strlen(new_logical) - 1; + + while (pch >= new_logical && isspace(*pch)) + *(pch--) = '\0'; + + free(logical); + logical = new_logical; + } else { + /* If we are UNABLE to read a line of output, discard our + * freshly allocated memory. */ + free(new_logical); + } + } + + fclose(out); + + return logical; +} +#endif /* CONFIG_FEATURE_IFUPDOWN_MAPPING */ + +static llist_t *find_iface_state(llist_t *state_list, const char *iface) +{ + unsigned short iface_len = strlen(iface); + llist_t *search = state_list; + + while (search) { + if ((strncmp(search->data, iface, iface_len) == 0) && + (search->data[iface_len] == '=')) { + return search; + } + search = search->link; + } + return NULL; +} + +int ifupdown_main(int argc, char **argv) +{ + static const char statefile[] = "/var/run/ifstate"; + + int (*cmds)(struct interface_defn_t *) = NULL; + struct interfaces_file_t *defn; + llist_t *state_list = NULL; + llist_t *target_list = NULL; + const char *interfaces = "/etc/network/interfaces"; + int any_failures = 0; + + if (applet_name[2] == 'u') { + /* ifup command */ + cmds = iface_up; + } else { + /* ifdown command */ + cmds = iface_down; + } + + getopt32(argc, argv, OPTION_STR, &interfaces); + if (argc - optind > 0) { + if (DO_ALL) bb_show_usage(); + } else + if (!DO_ALL) bb_show_usage(); + + debug_noise("reading %s file:\n", interfaces); + defn = read_interfaces(interfaces); + debug_noise("\ndone reading %s\n\n", interfaces); + + if (!defn) { + exit(EXIT_FAILURE); + } + + startup_PATH = getenv("PATH"); + if (!startup_PATH) startup_PATH = ""; + + /* Create a list of interfaces to work on */ + if (DO_ALL) { + if (cmds == iface_up) { + target_list = defn->autointerfaces; + } else { + /* iface_down */ + const llist_t *list = state_list; + while (list) { + llist_add_to_end(&target_list, xstrdup(list->data)); + list = list->link; + } + target_list = defn->autointerfaces; + } + } else { + llist_add_to_end(&target_list, argv[optind]); + } + + + /* Update the interfaces */ + while (target_list) { + llist_t *iface_list; + struct interface_defn_t *currif; + char *iface; + char *liface; + char *pch; + int okay = 0; + int cmds_ret; + + iface = xstrdup(target_list->data); + target_list = target_list->link; + + pch = strchr(iface, '='); + if (pch) { + *pch = '\0'; + liface = xstrdup(pch + 1); + } else { + liface = xstrdup(iface); + } + + if (!FORCE) { + const llist_t *iface_state = find_iface_state(state_list, iface); + + if (cmds == iface_up) { + /* ifup */ + if (iface_state) { + bb_error_msg("interface %s already configured", iface); + continue; + } + } else { + /* ifdown */ + if (iface_state) { + bb_error_msg("interface %s not configured", iface); + continue; + } + } + } + +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + if ((cmds == iface_up) && !NO_MAPPINGS) { + struct mapping_defn_t *currmap; + + for (currmap = defn->mappings; currmap; currmap = currmap->next) { + int i; + for (i = 0; i < currmap->n_matches; i++) { + if (fnmatch(currmap->match[i], liface, 0) != 0) + continue; + if (VERBOSE) { + printf("Running mapping script %s on %s\n", currmap->script, liface); + } + liface = run_mapping(iface, currmap); + break; + } + } + } +#endif + + + iface_list = defn->ifaces; + while (iface_list) { + currif = (struct interface_defn_t *) iface_list->data; + if (strcmp(liface, currif->iface) == 0) { + char *oldiface = currif->iface; + + okay = 1; + currif->iface = iface; + + debug_noise("\nConfiguring interface %s (%s)\n", liface, currif->address_family->name); + + /* Call the cmds function pointer, does either iface_up() or iface_down() */ + cmds_ret = cmds(currif); + if (cmds_ret == -1) { + bb_error_msg("don't seem to have all the variables for %s/%s", + liface, currif->address_family->name); + any_failures += 1; + } else if (cmds_ret == 0) { + any_failures += 1; + } + + currif->iface = oldiface; + } + iface_list = iface_list->link; + } + if (VERBOSE) { + puts(""); + } + + if (!okay && !FORCE) { + bb_error_msg("ignoring unknown interface %s", liface); + any_failures += 1; + } else { + llist_t *iface_state = find_iface_state(state_list, iface); + + if (cmds == iface_up) { + char *newiface = xasprintf("%s=%s", iface, liface); + if (iface_state == NULL) { + llist_add_to_end(&state_list, newiface); + } else { + free(iface_state->data); + iface_state->data = newiface; + } + } else { + /* Remove an interface from the linked list */ + free(llist_pop(&iface_state)); + } + } + } + + /* Actually write the new state */ + if (!NO_ACT) { + FILE *state_fp = NULL; + + state_fp = xfopen(statefile, "w"); + while (state_list) { + if (state_list->data) { + fputs(state_list->data, state_fp); + fputc('\n', state_fp); + } + state_list = state_list->link; + } + fclose(state_fp); + } + + if (any_failures) + return 1; + return 0; +} diff --git a/networking/inetd.c b/networking/inetd.c new file mode 100644 index 000000000..ec7b2e8f7 --- /dev/null +++ b/networking/inetd.c @@ -0,0 +1,1767 @@ +/* vi: set sw=4 ts=4: */ +/* $Slackware: inetd.c 1.79s 2001/02/06 13:18:00 volkerdi Exp $ */ +/* $OpenBSD: inetd.c,v 1.79 2001/01/30 08:30:57 deraadt Exp $ */ +/* $NetBSD: inetd.c,v 1.11 1996/02/22 11:14:41 mycroft Exp $ */ +/* Busybox port by Vladimir Oleynik (C) 2001-2005 */ +/* + * Copyright (c) 1983,1991 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Inetd - Internet super-server + * + * This program invokes all internet services as needed. + * connection-oriented services are invoked each time a + * connection is made, by creating a process. This process + * is passed the connection as file descriptor 0 and is + * expected to do a getpeername to find out the source host + * and port. + * + * Datagram oriented services are invoked when a datagram + * arrives; a process is created and passed a pending message + * on file descriptor 0. Datagram servers may either connect + * to their peer, freeing up the original socket for inetd + * to receive further messages on, or ``take over the socket'', + * processing all arriving datagrams and, eventually, timing + * out. The first type of server is said to be ``multi-threaded''; + * the second type of server ``single-threaded''. + * + * Inetd uses a configuration file which is read at startup + * and, possibly, at some later time in response to a hangup signal. + * The configuration file is ``free format'' with fields given in the + * order shown below. Continuation lines for an entry must begin with + * a space or tab. All fields must be present in each entry. + * + * service name must be in /etc/services + * socket type stream/dgram/raw/rdm/seqpacket + * protocol must be in /etc/protocols + * wait/nowait[.max] single-threaded/multi-threaded, max # + * user[.group] or user[:group] user/group to run daemon as + * server program full path name + * server program arguments maximum of MAXARGS (20) + * + * For RPC services + * service name/version must be in /etc/rpc + * socket type stream/dgram/raw/rdm/seqpacket + * protocol must be in /etc/protocols + * wait/nowait[.max] single-threaded/multi-threaded + * user[.group] or user[:group] user to run daemon as + * server program full path name + * server program arguments maximum of MAXARGS (20) + * + * For non-RPC services, the "service name" can be of the form + * hostaddress:servicename, in which case the hostaddress is used + * as the host portion of the address to listen on. If hostaddress + * consists of a single `*' character, INADDR_ANY is used. + * + * A line can also consist of just + * hostaddress: + * where hostaddress is as in the preceding paragraph. Such a line must + * have no further fields; the specified hostaddress is remembered and + * used for all further lines that have no hostaddress specified, + * until the next such line (or EOF). (This is why * is provided to + * allow explicit specification of INADDR_ANY.) A line + * *: + * is implicitly in effect at the beginning of the file. + * + * The hostaddress specifier may (and often will) contain dots; + * the service name must not. + * + * For RPC services, host-address specifiers are accepted and will + * work to some extent; however, because of limitations in the + * portmapper interface, it will not work to try to give more than + * one line for any given RPC service, even if the host-address + * specifiers are different. + * + * Comment lines are indicated by a `#' in column 1. + */ + +/* + * Here's the scoop concerning the user[.:]group feature: + * + * 1) set-group-option off. + * + * a) user = root: NO setuid() or setgid() is done + * + * b) other: setgid(primary group as found in passwd) + * initgroups(name, primary group) + * setuid() + * + * 2) set-group-option on. + * + * a) user = root: setgid(specified group) + * NO initgroups() + * NO setuid() + * + * b) other: setgid(specified group) + * initgroups(name, specified group) + * setuid() + * + */ + +#include "busybox.h" +#include +#include + +//#define CONFIG_FEATURE_INETD_RPC +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +//#define CONFIG_FEATURE_IPV6 + +#ifdef CONFIG_FEATURE_INETD_RPC +#include +#include +#endif + +#define _PATH_INETDCONF "/etc/inetd.conf" +#define _PATH_INETDPID "/var/run/inetd.pid" + + +#define CNT_INTVL 60 /* servers in CNT_INTVL sec. */ +#define RETRYTIME (60*10) /* retry after bind or server fail */ + +#ifndef RLIMIT_NOFILE +#define RLIMIT_NOFILE RLIMIT_OFILE +#endif + +#ifndef OPEN_MAX +#define OPEN_MAX 64 +#endif + +/* Reserve some descriptors, 3 stdio + at least: 1 log, 1 conf. file */ +#define FD_MARGIN (8) +static rlim_t rlim_ofile_cur = OPEN_MAX; +static struct rlimit rlim_ofile; + + +/* Check unsupporting builtin */ +#if defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +# define INETD_FEATURE_ENABLED +#endif + +#if defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +# define INETD_SETPROCTITLE +#endif + +typedef struct servtab { + char *se_hostaddr; /* host address to listen on */ + char *se_service; /* name of service */ + int se_socktype; /* type of socket to use */ + int se_family; /* address family */ + char *se_proto; /* protocol used */ +#ifdef CONFIG_FEATURE_INETD_RPC + int se_rpcprog; /* rpc program number */ + int se_rpcversl; /* rpc program lowest version */ + int se_rpcversh; /* rpc program highest version */ +#define isrpcservice(sep) ((sep)->se_rpcversl != 0) +#else +#define isrpcservice(sep) 0 +#endif + pid_t se_wait; /* single threaded server */ + short se_checked; /* looked at during merge */ + char *se_user; /* user name to run as */ + char *se_group; /* group name to run as */ +#ifdef INETD_FEATURE_ENABLED + const struct builtin *se_bi; /* if built-in, description */ +#endif + char *se_server; /* server program */ +#define MAXARGV 20 + char *se_argv[MAXARGV + 1]; /* program arguments */ + int se_fd; /* open descriptor */ + union { + struct sockaddr se_un_ctrladdr; + struct sockaddr_in se_un_ctrladdr_in; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 se_un_ctrladdr_in6; +#endif + struct sockaddr_un se_un_ctrladdr_un; + } se_un; /* bound address */ +#define se_ctrladdr se_un.se_un_ctrladdr +#define se_ctrladdr_in se_un.se_un_ctrladdr_in +#define se_ctrladdr_in6 se_un.se_un_ctrladdr_in6 +#define se_ctrladdr_un se_un.se_un_ctrladdr_un + int se_ctrladdr_size; + int se_max; /* max # of instances of this service */ + int se_count; /* number started since se_time */ + struct timeval se_time; /* start of se_count */ + struct servtab *se_next; +} servtab_t; + +static servtab_t *servtab; + +#ifdef INETD_FEATURE_ENABLED +struct builtin { + const char *bi_service; /* internally provided service name */ + int bi_socktype; /* type of socket supported */ + short bi_fork; /* 1 if should fork before call */ + short bi_wait; /* 1 if should wait for child */ + void (*bi_fn) (int, servtab_t *); +}; + + /* Echo received data */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +static void echo_stream(int, servtab_t *); +static void echo_dg(int, servtab_t *); +#endif + /* Internet /dev/null */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +static void discard_stream(int, servtab_t *); +static void discard_dg(int, servtab_t *); +#endif + /* Return 32 bit time since 1900 */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME +static void machtime_stream(int, servtab_t *); +static void machtime_dg(int, servtab_t *); +#endif + /* Return human-readable time */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +static void daytime_stream(int, servtab_t *); +static void daytime_dg(int, servtab_t *); +#endif + /* Familiar character generator */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +static void chargen_stream(int, servtab_t *); +static void chargen_dg(int, servtab_t *); +#endif + +static const struct builtin builtins[] = { +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO + /* Echo received data */ + {"echo", SOCK_STREAM, 1, 0, echo_stream,}, + {"echo", SOCK_DGRAM, 0, 0, echo_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD + /* Internet /dev/null */ + {"discard", SOCK_STREAM, 1, 0, discard_stream,}, + {"discard", SOCK_DGRAM, 0, 0, discard_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME + /* Return 32 bit time since 1900 */ + {"time", SOCK_STREAM, 0, 0, machtime_stream,}, + {"time", SOCK_DGRAM, 0, 0, machtime_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME + /* Return human-readable time */ + {"daytime", SOCK_STREAM, 0, 0, daytime_stream,}, + {"daytime", SOCK_DGRAM, 0, 0, daytime_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN + /* Familiar character generator */ + {"chargen", SOCK_STREAM, 1, 0, chargen_stream,}, + {"chargen", SOCK_DGRAM, 0, 0, chargen_dg,}, +#endif + {NULL, 0, 0, 0, NULL} +}; +#endif /* INETD_FEATURE_ENABLED */ + +static int global_queuelen = 128; +static int nsock, maxsock; +static fd_set allsock; +static int toomany; +static int timingout; +static struct servent *sp; +static uid_t uid; + +static char *CONFIG = _PATH_INETDCONF; + +static FILE *fconfig; +static char line[1024]; +static char *defhost; + +/* xstrdup(NULL) returns NULL, but this one + * will return newly-allocated "" if called with NULL arg + * TODO: audit whether this makes any real difference + */ +static char *xxstrdup(char *cp) +{ + return xstrdup(cp ? cp : ""); +} + +static int setconfig(void) +{ + free(defhost); + defhost = xstrdup("*"); + if (fconfig != NULL) { + fseek(fconfig, 0L, SEEK_SET); + return 1; + } + fconfig = fopen(CONFIG, "r"); + return (fconfig != NULL); +} + +static void endconfig(void) +{ + if (fconfig) { + (void) fclose(fconfig); + fconfig = NULL; + } + free(defhost); + defhost = 0; +} + +#ifdef CONFIG_FEATURE_INETD_RPC +static void register_rpc(servtab_t *sep) +{ + int n; + struct sockaddr_in ir_sin; + struct protoent *pp; + socklen_t size; + + if ((pp = getprotobyname(sep->se_proto + 4)) == NULL) { + bb_perror_msg("%s: getproto", sep->se_proto); + return; + } + size = sizeof ir_sin; + if (getsockname(sep->se_fd, (struct sockaddr *) &ir_sin, &size) < 0) { + bb_perror_msg("%s/%s: getsockname", + sep->se_service, sep->se_proto); + return; + } + + for (n = sep->se_rpcversl; n <= sep->se_rpcversh; n++) { + (void) pmap_unset(sep->se_rpcprog, n); + if (!pmap_set(sep->se_rpcprog, n, pp->p_proto, ntohs(ir_sin.sin_port))) + bb_perror_msg("%s %s: pmap_set: %u %u %u %u", + sep->se_service, sep->se_proto, + sep->se_rpcprog, n, pp->p_proto, ntohs(ir_sin.sin_port)); + } +} + +static void unregister_rpc(servtab_t *sep) +{ + int n; + + for (n = sep->se_rpcversl; n <= sep->se_rpcversh; n++) { + if (!pmap_unset(sep->se_rpcprog, n)) + bb_error_msg("pmap_unset(%u, %u)", sep->se_rpcprog, n); + } +} +#endif /* CONFIG_FEATURE_INETD_RPC */ + +static void freeconfig(servtab_t *cp) +{ + int i; + + free(cp->se_hostaddr); + free(cp->se_service); + free(cp->se_proto); + free(cp->se_user); + free(cp->se_group); + free(cp->se_server); + for (i = 0; i < MAXARGV; i++) + free(cp->se_argv[i]); +} + +static int bump_nofile (void) +{ +#define FD_CHUNK 32 + + struct rlimit rl; + + if (getrlimit (RLIMIT_NOFILE, &rl) < 0) { + bb_perror_msg("getrlimit"); + return -1; + } + rl.rlim_cur = MIN(rl.rlim_max, rl.rlim_cur + FD_CHUNK); + rl.rlim_cur = MIN(FD_SETSIZE, rl.rlim_cur + FD_CHUNK); + if (rl.rlim_cur <= rlim_ofile_cur) { + bb_error_msg("bump_nofile: cannot extend file limit, max = %d", + (int) rl.rlim_cur); + return -1; + } + + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) { + bb_perror_msg("setrlimit"); + return -1; + } + + rlim_ofile_cur = rl.rlim_cur; + return 0; +} + +static void setup(servtab_t *sep) +{ + int r; + + sep->se_fd = socket(sep->se_family, sep->se_socktype, 0); + if (sep->se_fd < 0) { + bb_perror_msg("%s/%s: socket", sep->se_service, sep->se_proto); + return; + } + if (setsockopt_reuseaddr(sep->se_fd) < 0) + bb_perror_msg("setsockopt(SO_REUSEADDR)"); + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) { + struct passwd *pwd; + + /* + * for RPC services, attempt to use a reserved port + * if they are going to be running as root. + * + * Also, zero out the port for all RPC services; let bind() + * find one. + */ + sep->se_ctrladdr_in.sin_port = 0; + if (sep->se_user && (pwd = getpwnam(sep->se_user)) && + pwd->pw_uid == 0 && uid == 0) + r = bindresvport(sep->se_fd, &sep->se_ctrladdr_in); + else { + r = bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size); + if (r == 0) { + socklen_t len = sep->se_ctrladdr_size; + int saveerrno = errno; + + /* update se_ctrladdr_in.sin_port */ + r = getsockname(sep->se_fd, &sep->se_ctrladdr, &len); + if (r <= 0) + errno = saveerrno; + } + } + } else +#endif + r = bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size); + if (r < 0) { + bb_perror_msg("%s/%s (%d): bind", + sep->se_service, sep->se_proto, sep->se_ctrladdr.sa_family); + close(sep->se_fd); + sep->se_fd = -1; + if (!timingout) { + timingout = 1; + alarm(RETRYTIME); + } + return; + } + if (sep->se_socktype == SOCK_STREAM) + listen(sep->se_fd, global_queuelen); + + FD_SET(sep->se_fd, &allsock); + nsock++; + if (sep->se_fd > maxsock) { + maxsock = sep->se_fd; + if ((rlim_t)maxsock > rlim_ofile_cur - FD_MARGIN) + bump_nofile(); + } +} + +static char *nextline(void) +{ + char *cp; + FILE *fd = fconfig; + + if (fgets(line, sizeof(line), fd) == NULL) + return NULL; + cp = strchr(line, '\n'); + if (cp) + *cp = '\0'; + return line; +} + +static char *skip(char **cpp) /* int report; */ +{ + char *cp = *cpp; + char *start; + +/* erp: */ + if (*cpp == NULL) { + /* if (report) */ + /* bb_error_msg("syntax error in inetd config file"); */ + return NULL; + } + +again: + while (*cp == ' ' || *cp == '\t') + cp++; + if (*cp == '\0') { + int c; + + c = getc(fconfig); + (void) ungetc(c, fconfig); + if (c == ' ' || c == '\t') + if ((cp = nextline())) + goto again; + *cpp = NULL; + /* goto erp; */ + return NULL; + } + start = cp; + while (*cp && *cp != ' ' && *cp != '\t') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + /* if ((*cpp = cp) == NULL) */ + /* goto erp; */ + + *cpp = cp; + return start; +} + +static servtab_t *new_servtab(void) +{ + return xmalloc(sizeof(servtab_t)); +} + +static servtab_t *dupconfig(servtab_t *sep) +{ + servtab_t *newtab; + int argc; + + newtab = new_servtab(); + memset(newtab, 0, sizeof(servtab_t)); + newtab->se_service = xstrdup(sep->se_service); + newtab->se_socktype = sep->se_socktype; + newtab->se_family = sep->se_family; + newtab->se_proto = xstrdup(sep->se_proto); +#ifdef CONFIG_FEATURE_INETD_RPC + newtab->se_rpcprog = sep->se_rpcprog; + newtab->se_rpcversl = sep->se_rpcversl; + newtab->se_rpcversh = sep->se_rpcversh; +#endif + newtab->se_wait = sep->se_wait; + newtab->se_user = xstrdup(sep->se_user); + newtab->se_group = xstrdup(sep->se_group); +#ifdef INETD_FEATURE_ENABLED + newtab->se_bi = sep->se_bi; +#endif + newtab->se_server = xstrdup(sep->se_server); + + for (argc = 0; argc <= MAXARGV; argc++) + newtab->se_argv[argc] = xstrdup(sep->se_argv[argc]); + newtab->se_max = sep->se_max; + + return newtab; +} + +static servtab_t *getconfigent(void) +{ + servtab_t *sep; + int argc; + char *cp, *arg; + char *hostdelim; + servtab_t *nsep; + servtab_t *psep; + + sep = new_servtab(); + + /* memset(sep, 0, sizeof *sep); */ + more: + /* freeconfig(sep); */ + + while ((cp = nextline()) && *cp == '#') /* skip comment line */; + if (cp == NULL) { + /* free(sep); */ + return NULL; + } + + memset((char *) sep, 0, sizeof *sep); + arg = skip(&cp); + if (arg == NULL) { + /* A blank line. */ + goto more; + } + + /* Check for a host name. */ + hostdelim = strrchr(arg, ':'); + if (hostdelim) { + *hostdelim = '\0'; + sep->se_hostaddr = xstrdup(arg); + arg = hostdelim + 1; + /* + * If the line is of the form `host:', then just change the + * default host for the following lines. + */ + if (*arg == '\0') { + arg = skip(&cp); + if (cp == NULL) { + free(defhost); + defhost = sep->se_hostaddr; + goto more; + } + } + } else + sep->se_hostaddr = xxstrdup(defhost); + + sep->se_service = xxstrdup(arg); + arg = skip(&cp); + + if (strcmp(arg, "stream") == 0) + sep->se_socktype = SOCK_STREAM; + else if (strcmp(arg, "dgram") == 0) + sep->se_socktype = SOCK_DGRAM; + else if (strcmp(arg, "rdm") == 0) + sep->se_socktype = SOCK_RDM; + else if (strcmp(arg, "seqpacket") == 0) + sep->se_socktype = SOCK_SEQPACKET; + else if (strcmp(arg, "raw") == 0) + sep->se_socktype = SOCK_RAW; + else + sep->se_socktype = -1; + + sep->se_proto = xxstrdup(skip(&cp)); + + if (strcmp(sep->se_proto, "unix") == 0) { + sep->se_family = AF_UNIX; + } else { + sep->se_family = AF_INET; + if (sep->se_proto[strlen(sep->se_proto) - 1] == '6') +#ifdef CONFIG_FEATURE_IPV6 + sep->se_family = AF_INET6; +#else + bb_error_msg("%s: IPV6 not supported", sep->se_proto); +#endif + if (strncmp(sep->se_proto, "rpc/", 4) == 0) { +#ifdef CONFIG_FEATURE_INETD_RPC + char *p, *ccp; + long l; + + p = strchr(sep->se_service, '/'); + if (p == 0) { + bb_error_msg("%s: no rpc version", sep->se_service); + goto more; + } + *p++ = '\0'; + l = strtol(p, &ccp, 0); + if (ccp == p || l < 0 || l > INT_MAX) { + badafterall: + bb_error_msg("%s/%s: bad rpc version", sep->se_service, p); + goto more; + } + sep->se_rpcversl = sep->se_rpcversh = l; + if (*ccp == '-') { + p = ccp + 1; + l = strtol(p, &ccp, 0); + if (ccp == p || l < 0 || l > INT_MAX || l < sep->se_rpcversl || *ccp) + goto badafterall; + sep->se_rpcversh = l; + } else if (*ccp != '\0') + goto badafterall; +#else + bb_error_msg("%s: rpc services not supported", sep->se_service); +#endif + } + } + arg = skip(&cp); + if (arg == NULL) + goto more; + + { + char *s = strchr(arg, '.'); + if (s) { + *s++ = '\0'; + sep->se_max = xatoi(s); + } else + sep->se_max = toomany; + } + sep->se_wait = strcmp(arg, "wait") == 0; + /* if ((arg = skip(&cp, 1)) == NULL) */ + /* goto more; */ + sep->se_user = xxstrdup(skip(&cp)); + arg = strchr(sep->se_user, '.'); + if (arg == NULL) + arg = strchr(sep->se_user, ':'); + if (arg) { + *arg++ = '\0'; + sep->se_group = xstrdup(arg); + } + /* if ((arg = skip(&cp, 1)) == NULL) */ + /* goto more; */ + + sep->se_server = xxstrdup(skip(&cp)); + if (strcmp(sep->se_server, "internal") == 0) { +#ifdef INETD_FEATURE_ENABLED + const struct builtin *bi; + + for (bi = builtins; bi->bi_service; bi++) + if (bi->bi_socktype == sep->se_socktype && + strcmp(bi->bi_service, sep->se_service) == 0) + break; + if (bi->bi_service == 0) { + bb_error_msg("internal service %s unknown", sep->se_service); + goto more; + } + sep->se_bi = bi; + sep->se_wait = bi->bi_wait; +#else + bb_perror_msg("internal service %s unknown", sep->se_service); + goto more; +#endif + } +#ifdef INETD_FEATURE_ENABLED + else + sep->se_bi = NULL; +#endif + argc = 0; + for (arg = skip(&cp); cp; arg = skip(&cp)) { + if (argc < MAXARGV) + sep->se_argv[argc++] = xxstrdup(arg); + } + while (argc <= MAXARGV) + sep->se_argv[argc++] = NULL; + + /* + * Now that we've processed the entire line, check if the hostname + * specifier was a comma separated list of hostnames. If so + * we'll make new entries for each address. + */ + while ((hostdelim = strrchr(sep->se_hostaddr, ',')) != NULL) { + nsep = dupconfig(sep); + + /* + * NULL terminate the hostname field of the existing entry, + * and make a dup for the new entry. + */ + *hostdelim++ = '\0'; + nsep->se_hostaddr = xstrdup(hostdelim); + + nsep->se_next = sep->se_next; + sep->se_next = nsep; + } + + nsep = sep; + while (nsep != NULL) { + nsep->se_checked = 1; + if (nsep->se_family == AF_INET) { + if (!strcmp(nsep->se_hostaddr, "*")) + nsep->se_ctrladdr_in.sin_addr.s_addr = INADDR_ANY; + else if (!inet_aton(nsep->se_hostaddr, &nsep->se_ctrladdr_in.sin_addr)) { + struct hostent *hp; + + hp = gethostbyname(nsep->se_hostaddr); + if (hp == 0) { + bb_error_msg("%s: unknown host", nsep->se_hostaddr); + nsep->se_checked = 0; + goto skip; + } else if (hp->h_addrtype != AF_INET) { + bb_error_msg("%s: address isn't an Internet " + "address", nsep->se_hostaddr); + nsep->se_checked = 0; + goto skip; + } else { + int i = 1; + + memmove(&nsep->se_ctrladdr_in.sin_addr, + hp->h_addr_list[0], sizeof(struct in_addr)); + while (hp->h_addr_list[i] != NULL) { + psep = dupconfig(nsep); + psep->se_hostaddr = xxstrdup(nsep->se_hostaddr); + psep->se_checked = 1; + memmove(&psep->se_ctrladdr_in.sin_addr, + hp->h_addr_list[i], sizeof(struct in_addr)); + psep->se_ctrladdr_size = sizeof(psep->se_ctrladdr_in); + i++; + /* Prepend to list, don't want to look up */ + /* its hostname again. */ + psep->se_next = sep; + sep = psep; + } + } + } + } +/* XXX BUG?: is this skip: label supposed to remain? */ + skip: + nsep = nsep->se_next; + } + + /* + * Finally, free any entries which failed the gethostbyname + * check. + */ + psep = NULL; + nsep = sep; + while (nsep != NULL) { + servtab_t *tsep; + + if (nsep->se_checked == 0) { + tsep = nsep; + if (psep == NULL) { + sep = nsep->se_next; + nsep = sep; + } else { + nsep = nsep->se_next; + psep->se_next = nsep; + } + freeconfig(tsep); + } else { + nsep->se_checked = 0; + psep = nsep; + nsep = nsep->se_next; + } + } + + return sep; +} + +#define Block_Using_Signals(m) do { \ + sigemptyset(&m); \ + sigaddset(&m, SIGCHLD); \ + sigaddset(&m, SIGHUP); \ + sigaddset(&m, SIGALRM); \ + sigprocmask(SIG_BLOCK, &m, NULL); \ +} while(0) + +static servtab_t *enter(servtab_t *cp) +{ + servtab_t *sep; + sigset_t omask; + + sep = new_servtab(); + *sep = *cp; + sep->se_fd = -1; +#ifdef CONFIG_FEATURE_INETD_RPC + sep->se_rpcprog = -1; +#endif + Block_Using_Signals(omask); + sep->se_next = servtab; + servtab = sep; + sigprocmask(SIG_UNBLOCK, &omask, NULL); + return sep; +} + +static int matchconf(servtab_t *old, servtab_t *new) +{ + if (strcmp(old->se_service, new->se_service) != 0) + return 0; + + if (strcmp(old->se_hostaddr, new->se_hostaddr) != 0) + return 0; + + if (strcmp(old->se_proto, new->se_proto) != 0) + return 0; + + /* + * If the new servtab is bound to a specific address, check that the + * old servtab is bound to the same entry. If the new service is not + * bound to a specific address then the check of se_hostaddr above + * is sufficient. + */ + + if (old->se_family == AF_INET && new->se_family == AF_INET && + memcmp(&old->se_ctrladdr_in.sin_addr, + &new->se_ctrladdr_in.sin_addr, + sizeof(new->se_ctrladdr_in.sin_addr)) != 0) + return 0; + +#ifdef CONFIG_FEATURE_IPV6 + if (old->se_family == AF_INET6 && new->se_family == AF_INET6 && + memcmp(&old->se_ctrladdr_in6.sin6_addr, + &new->se_ctrladdr_in6.sin6_addr, + sizeof(new->se_ctrladdr_in6.sin6_addr)) != 0) + return 0; +#endif + return 1; +} + +static void config(int sig ATTRIBUTE_UNUSED) +{ + servtab_t *sep, *cp, **sepp; + sigset_t omask; + size_t n; + char protoname[10]; + + if (!setconfig()) { + bb_perror_msg("%s", CONFIG); + return; + } + for (sep = servtab; sep; sep = sep->se_next) + sep->se_checked = 0; + cp = getconfigent(); + while (cp != NULL) { + for (sep = servtab; sep; sep = sep->se_next) + if (matchconf(sep, cp)) + break; + + if (sep != 0) { + int i; + +#define SWAP(type, a, b) do {type c=(type)a; a=(type)b; b=(type)c;} while (0) + + Block_Using_Signals(omask); + /* + * sep->se_wait may be holding the pid of a daemon + * that we're waiting for. If so, don't overwrite + * it unless the config file explicitly says don't + * wait. + */ + if ( +#ifdef INETD_FEATURE_ENABLED + cp->se_bi == 0 && +#endif + (sep->se_wait == 1 || cp->se_wait == 0)) + sep->se_wait = cp->se_wait; + SWAP(int, cp->se_max, sep->se_max); + SWAP(char *, sep->se_user, cp->se_user); + SWAP(char *, sep->se_group, cp->se_group); + SWAP(char *, sep->se_server, cp->se_server); + for (i = 0; i < MAXARGV; i++) + SWAP(char *, sep->se_argv[i], cp->se_argv[i]); +#undef SWAP + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) + unregister_rpc(sep); + sep->se_rpcversl = cp->se_rpcversl; + sep->se_rpcversh = cp->se_rpcversh; +#endif + sigprocmask(SIG_UNBLOCK, &omask, NULL); + freeconfig(cp); + } else { + sep = enter(cp); + } + sep->se_checked = 1; + + switch (sep->se_family) { + case AF_UNIX: + if (sep->se_fd != -1) + break; + (void) unlink(sep->se_service); + n = strlen(sep->se_service); + if (n > sizeof sep->se_ctrladdr_un.sun_path - 1) + n = sizeof sep->se_ctrladdr_un.sun_path - 1; + safe_strncpy(sep->se_ctrladdr_un.sun_path, sep->se_service, n + 1); + sep->se_ctrladdr_un.sun_family = AF_UNIX; + sep->se_ctrladdr_size = n + sizeof sep->se_ctrladdr_un.sun_family; + setup(sep); + break; + case AF_INET: + sep->se_ctrladdr_in.sin_family = AF_INET; + /* se_ctrladdr_in was set in getconfigent */ + sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in; + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) { + struct rpcent *rp; + // FIXME: atoi_or_else(str, 0) would be handy here + sep->se_rpcprog = atoi(sep->se_service); + if (sep->se_rpcprog == 0) { + rp = getrpcbyname(sep->se_service); + if (rp == 0) { + bb_error_msg("%s: unknown rpc service", sep->se_service); + goto serv_unknown; + } + sep->se_rpcprog = rp->r_number; + } + if (sep->se_fd == -1) + setup(sep); + if (sep->se_fd != -1) + register_rpc(sep); + } else +#endif + { + u_short port = htons(atoi(sep->se_service)); + // FIXME: atoi_or_else(str, 0) would be handy here + if (!port) { + /*XXX*/ strncpy(protoname, sep->se_proto, sizeof(protoname)); + if (isdigit(protoname[strlen(protoname) - 1])) + protoname[strlen(protoname) - 1] = '\0'; + sp = getservbyname(sep->se_service, protoname); + if (sp == 0) { + bb_error_msg("%s/%s: unknown service", + sep->se_service, sep->se_proto); + goto serv_unknown; + } + port = sp->s_port; + } + if (port != sep->se_ctrladdr_in.sin_port) { + sep->se_ctrladdr_in.sin_port = port; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } + sep->se_fd = -1; + } + if (sep->se_fd == -1) + setup(sep); + } + break; +#ifdef CONFIG_FEATURE_IPV6 + case AF_INET6: + sep->se_ctrladdr_in6.sin6_family = AF_INET6; + /* se_ctrladdr_in was set in getconfigent */ + sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in6; + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) { + struct rpcent *rp; + + sep->se_rpcprog = atoi(sep->se_service); + if (sep->se_rpcprog == 0) { + rp = getrpcbyname(sep->se_service); + if (rp == 0) { + bb_error_msg("%s: unknown rpc service", sep->se_service); + goto serv_unknown; + } + sep->se_rpcprog = rp->r_number; + } + if (sep->se_fd == -1) + setup(sep); + if (sep->se_fd != -1) + register_rpc(sep); + } else +#endif + { + u_short port = htons(atoi(sep->se_service)); + + if (!port) { + /*XXX*/ strncpy(protoname, sep->se_proto, sizeof(protoname)); + if (isdigit(protoname[strlen(protoname) - 1])) + protoname[strlen(protoname) - 1] = '\0'; + sp = getservbyname(sep->se_service, protoname); + if (sp == 0) { + bb_error_msg("%s/%s: unknown service", + sep->se_service, sep->se_proto); + goto serv_unknown; + } + port = sp->s_port; + } + if (port != sep->se_ctrladdr_in6.sin6_port) { + sep->se_ctrladdr_in6.sin6_port = port; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } + sep->se_fd = -1; + } + if (sep->se_fd == -1) + setup(sep); + } + break; +#endif /* CONFIG_FEATURE_IPV6 */ + } + serv_unknown: + if (cp->se_next != NULL) { + servtab_t *tmp = cp; + + cp = cp->se_next; + free(tmp); + } else { + free(cp); + cp = getconfigent(); + } + } + endconfig(); + /* + * Purge anything not looked at above. + */ + Block_Using_Signals(omask); + sepp = &servtab; + while ((sep = *sepp)) { + if (sep->se_checked) { + sepp = &sep->se_next; + continue; + } + *sepp = sep->se_next; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) + unregister_rpc(sep); +#endif + if (sep->se_family == AF_UNIX) + (void) unlink(sep->se_service); + freeconfig(sep); + free(sep); + } + sigprocmask(SIG_UNBLOCK, &omask, NULL); +} + + +static void reapchild(int sig ATTRIBUTE_UNUSED) +{ + pid_t pid; + int save_errno = errno, status; + servtab_t *sep; + + for (;;) { + pid = wait3(&status, WNOHANG, NULL); + if (pid <= 0) + break; + for (sep = servtab; sep; sep = sep->se_next) + if (sep->se_wait == pid) { + if (WIFEXITED(status) && WEXITSTATUS(status)) + bb_error_msg("%s: exit status 0x%x", + sep->se_server, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + bb_error_msg("%s: exit signal 0x%x", + sep->se_server, WTERMSIG(status)); + sep->se_wait = 1; + FD_SET(sep->se_fd, &allsock); + nsock++; + } + } + errno = save_errno; +} + +static void retry(int sig ATTRIBUTE_UNUSED) +{ + servtab_t *sep; + + timingout = 0; + for (sep = servtab; sep; sep = sep->se_next) { + if (sep->se_fd == -1) { + switch (sep->se_family) { + case AF_UNIX: + case AF_INET: +#ifdef CONFIG_FEATURE_IPV6 + case AF_INET6: +#endif + setup(sep); +#ifdef CONFIG_FEATURE_INETD_RPC + if (sep->se_fd != -1 && isrpcservice(sep)) + register_rpc(sep); +#endif + break; + } + } + } +} + +static void goaway(int sig ATTRIBUTE_UNUSED) +{ + servtab_t *sep; + + /* XXX signal race walking sep list */ + for (sep = servtab; sep; sep = sep->se_next) { + if (sep->se_fd == -1) + continue; + + switch (sep->se_family) { + case AF_UNIX: + (void) unlink(sep->se_service); + break; + case AF_INET: +#ifdef CONFIG_FEATURE_IPV6 + case AF_INET6: +#endif +#ifdef CONFIG_FEATURE_INETD_RPC + if (sep->se_wait == 1 && isrpcservice(sep)) + unregister_rpc(sep); /* XXX signal race */ +#endif + break; + } + (void) close(sep->se_fd); + } + (void) unlink(_PATH_INETDPID); + exit(0); +} + + +#ifdef INETD_SETPROCTITLE +static char **Argv; +static char *LastArg; + +static void +inetd_setproctitle(char *a, int s) +{ + socklen_t size; + char *cp; + struct sockaddr_in prt_sin; + char buf[80]; + + cp = Argv[0]; + size = sizeof(prt_sin); + (void) snprintf(buf, sizeof buf, "-%s", a); + if (getpeername(s, (struct sockaddr *) &prt_sin, &size) == 0) { + char *sa = inet_ntoa(prt_sin.sin_addr); + + buf[sizeof(buf) - 1 - strlen(sa) - 3] = '\0'; + strcat(buf, " ["); + strcat(buf, sa); + strcat(buf, "]"); + } + strncpy(cp, buf, LastArg - cp); + cp += strlen(cp); + while (cp < LastArg) + *cp++ = ' '; +} +#endif + + +int +inetd_main(int argc, char *argv[]) +{ + servtab_t *sep; + struct passwd *pwd; + struct group *grp = NULL; + int tmpint; + struct sigaction sa, sapipe; + int opt; + pid_t pid; + char buf[50]; + char *stoomany; + sigset_t omask, wait_mask; + +#ifdef INETD_SETPROCTITLE + extern char **environ; + char **envp = environ; + + Argv = argv; + if (envp == 0 || *envp == 0) + envp = argv; + while (*envp) + envp++; + LastArg = envp[-1] + strlen(envp[-1]); +#endif + + openlog(applet_name, LOG_PID | LOG_NOWAIT, LOG_DAEMON); + + opt = getopt32(argc, argv, "R:f", &stoomany); + if(opt & 1) { + toomany = xatoi_u(stoomany); + } + argc -= optind; + argv += optind; + + uid = getuid(); + if (uid != 0) + CONFIG = NULL; + if (argc > 0) + CONFIG = argv[0]; + if (CONFIG == NULL) + bb_error_msg_and_die("non-root must specify a config file"); + + if (!(opt & 2)) { +#ifdef BB_NOMMU + /* reexec for vfork() do continue parent */ + vfork_daemon_rexec(0, 0, argc, argv, "-f"); +#else + xdaemon(0, 0); +#endif + } else { + setsid(); + } + logmode = LOGMODE_SYSLOG; + + if (uid == 0) { + gid_t gid = getgid(); + + /* If run by hand, ensure groups vector gets trashed */ + setgroups(1, &gid); + } + + { + FILE *fp = fopen(_PATH_INETDPID, "w"); + + if (fp != NULL) { + fprintf(fp, "%u\n", getpid()); + (void) fclose(fp); + } + } + + if (getrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) { + bb_perror_msg("getrlimit"); + } else { + rlim_ofile_cur = rlim_ofile.rlim_cur; + if (rlim_ofile_cur == RLIM_INFINITY) /* ! */ + rlim_ofile_cur = OPEN_MAX; + } + + memset((char *) &sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGALRM); + sigaddset(&sa.sa_mask, SIGCHLD); + sigaddset(&sa.sa_mask, SIGHUP); + sa.sa_handler = retry; + sigaction(SIGALRM, &sa, NULL); + config(SIGHUP); + sa.sa_handler = config; + sigaction(SIGHUP, &sa, NULL); + sa.sa_handler = reapchild; + sigaction(SIGCHLD, &sa, NULL); + sa.sa_handler = goaway; + sigaction(SIGTERM, &sa, NULL); + sa.sa_handler = goaway; + sigaction(SIGINT, &sa, NULL); + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, &sapipe); + memset(&wait_mask, 0, sizeof(wait_mask)); + { + /* space for daemons to overwrite environment for ps */ +#define DUMMYSIZE 100 + char dummy[DUMMYSIZE]; + + (void) memset(dummy, 'x', DUMMYSIZE - 1); + dummy[DUMMYSIZE - 1] = '\0'; + + (void) setenv("inetd_dummy", dummy, 1); + } + + for (;;) { + int n, ctrl = -1; + fd_set readable; + + if (nsock == 0) { + Block_Using_Signals(omask); + while (nsock == 0) + sigsuspend(&wait_mask); + sigprocmask(SIG_UNBLOCK, &omask, NULL); + } + + readable = allsock; + n = select(maxsock + 1, &readable, NULL, NULL, NULL); + if (n <= 0) { + if (n < 0 && errno != EINTR) { + bb_perror_msg("select"); + sleep(1); + } + continue; + } + + for (sep = servtab; n && sep; sep = sep->se_next) { + if (sep->se_fd == -1 || !FD_ISSET(sep->se_fd, &readable)) + continue; + + n--; + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) { + ctrl = accept(sep->se_fd, NULL, NULL); + if (ctrl < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("accept (for %s)", sep->se_service); + continue; + } + if (sep->se_family == AF_INET && sep->se_socktype == SOCK_STREAM) { + struct sockaddr_in peer; + socklen_t plen = sizeof(peer); + + if (getpeername(ctrl, (struct sockaddr *) &peer, &plen) < 0) { + bb_error_msg("cannot getpeername"); + close(ctrl); + continue; + } + if (ntohs(peer.sin_port) == 20) { + /* XXX ftp bounce */ + close(ctrl); + continue; + } + } + } else + ctrl = sep->se_fd; + + Block_Using_Signals(omask); + pid = 0; +#ifdef INETD_FEATURE_ENABLED + if (sep->se_bi == 0 || sep->se_bi->bi_fork) +#endif + { + if (sep->se_count++ == 0) + (void) gettimeofday(&sep->se_time, NULL); + else if (toomany > 0 && sep->se_count >= sep->se_max) { + struct timeval now; + + (void) gettimeofday(&now, NULL); + if (now.tv_sec - sep->se_time.tv_sec > CNT_INTVL) { + sep->se_time = now; + sep->se_count = 1; + } else { + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + if (sep->se_family == AF_INET && + ntohs(sep->se_ctrladdr_in.sin_port) >= IPPORT_RESERVED) { + /* + * Cannot close it -- there are + * thieves on the system. + * Simply ignore the connection. + */ + --sep->se_count; + continue; + } + bb_error_msg("%s/%s server failing (looping), service terminated", + sep->se_service, sep->se_proto); + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + FD_CLR(sep->se_fd, &allsock); + (void) close(sep->se_fd); + sep->se_fd = -1; + sep->se_count = 0; + nsock--; + sigprocmask(SIG_UNBLOCK, &omask, NULL); + if (!timingout) { + timingout = 1; + alarm(RETRYTIME); + } + continue; + } + } + pid = fork(); + } + if (pid < 0) { + bb_perror_msg("fork"); + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + sigprocmask(SIG_UNBLOCK, &omask, NULL); + sleep(1); + continue; + } + if (pid && sep->se_wait) { + sep->se_wait = pid; + FD_CLR(sep->se_fd, &allsock); + nsock--; + } + sigprocmask(SIG_UNBLOCK, &omask, NULL); + if (pid == 0) { +#ifdef INETD_FEATURE_ENABLED + if (sep->se_bi) { + (*sep->se_bi->bi_fn)(ctrl, sep); + } else +#endif + { + pwd = getpwnam(sep->se_user); + if (pwd == NULL) { + bb_error_msg("getpwnam: %s: no such user", sep->se_user); + goto do_exit1; + } + if (setsid() < 0) + bb_perror_msg("%s: setsid", sep->se_service); + if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) { + bb_error_msg("getgrnam: %s: no such group", sep->se_group); + goto do_exit1; + } + if (uid != 0) { + /* a user running private inetd */ + if (uid != pwd->pw_uid) + _exit(1); + } else if (pwd->pw_uid) { + if (sep->se_group) + pwd->pw_gid = grp->gr_gid; + xsetgid((gid_t) pwd->pw_gid); + initgroups(pwd->pw_name, pwd->pw_gid); + xsetuid((uid_t) pwd->pw_uid); + } else if (sep->se_group) { + xsetgid(grp->gr_gid); + setgroups(1, &grp->gr_gid); + } + dup2(ctrl, 0); + if (ctrl) close(ctrl); + dup2(0, 1); + dup2(0, 2); + if (rlim_ofile.rlim_cur != rlim_ofile_cur) + if (setrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) + bb_perror_msg("setrlimit"); + closelog(); + for (tmpint = rlim_ofile_cur - 1; --tmpint > 2;) + (void) close(tmpint); + sigaction(SIGPIPE, &sapipe, NULL); + execv(sep->se_server, sep->se_argv); + bb_perror_msg("execv %s", sep->se_server); +do_exit1: + if (sep->se_socktype != SOCK_STREAM) + recv(0, buf, sizeof(buf), 0); + _exit(1); + } + } + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + } /* for (sep = servtab...) */ + } /* for(;;) */ +} + +/* + * Internet services provided internally by inetd: + */ +#define BUFSIZE 4096 + +#if defined(CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO) || \ + defined(CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN) || \ + defined(CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME) +static int dg_badinput(struct sockaddr_in *dg_sin) +{ + if (ntohs(dg_sin->sin_port) < IPPORT_RESERVED) + return 1; + if (dg_sin->sin_addr.s_addr == htonl(INADDR_BROADCAST)) + return 1; + /* XXX compare against broadcast addresses in SIOCGIFCONF list? */ + return 0; +} +#endif + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +/* Echo service -- echo data back */ +/* ARGSUSED */ +static void +echo_stream(int s, servtab_t *sep) +{ + char buffer[BUFSIZE]; + int i; + + inetd_setproctitle(sep->se_service, s); + while (1) { + i = read(s, buffer, sizeof(buffer)); + if (i <= 0) break; + /* FIXME: this isnt correct - safe_write()? */ + if (write(s, buffer, i) <= 0) break; + } + exit(0); +} + +/* Echo service -- echo data back */ +/* ARGSUSED */ +static void +echo_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[BUFSIZE]; + int i; + socklen_t size; + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + + size = sizeof(sa); + i = recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size); + if (i < 0) + return; + if (dg_badinput((struct sockaddr_in *) &sa)) + return; + (void) sendto(s, buffer, i, 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO */ + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +/* Discard service -- ignore data */ +/* ARGSUSED */ +static void +discard_stream(int s, servtab_t *sep) +{ + char buffer[BUFSIZE]; + + inetd_setproctitle(sep->se_service, s); + while (1) { + errno = 0; + if (read(s, buffer, sizeof(buffer)) <= 0 && errno != EINTR) + exit(0); + } +} + +/* Discard service -- ignore data */ +/* ARGSUSED */ +static void +discard_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[BUFSIZE]; + + (void) read(s, buffer, sizeof(buffer)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +#define LINESIZ 72 +static char ring[128]; +static char *endring; + +static void +initring(void) +{ + int i; + + endring = ring; + + for (i = 0; i <= 128; ++i) + if (isprint(i)) + *endring++ = i; +} + +/* Character generator */ +/* ARGSUSED */ +static void +chargen_stream(int s, servtab_t *sep) +{ + char *rs; + int len; + char text[LINESIZ + 2]; + + inetd_setproctitle(sep->se_service, s); + + if (!endring) { + initring(); + rs = ring; + } + + text[LINESIZ] = '\r'; + text[LINESIZ + 1] = '\n'; + rs = ring; + for (;;) { + len = endring - rs; + if (len >= LINESIZ) + memmove(text, rs, LINESIZ); + else { + memmove(text, rs, len); + memmove(text + len, ring, LINESIZ - len); + } + if (++rs == endring) + rs = ring; + if (write(s, text, sizeof(text)) != sizeof(text)) + break; + } + exit(0); +} + +/* Character generator */ +/* ARGSUSED */ +static void +chargen_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + static char *rs; + int len; + char text[LINESIZ + 2]; + socklen_t size; + + if (endring == 0) { + initring(); + rs = ring; + } + + size = sizeof(sa); + if (recvfrom(s, text, sizeof(text), 0, &sa, &size) < 0) + return; + if (dg_badinput((struct sockaddr_in *) &sa)) + return; + + if ((len = endring - rs) >= LINESIZ) + memmove(text, rs, LINESIZ); + else { + memmove(text, rs, len); + memmove(text + len, ring, LINESIZ - len); + } + if (++rs == endring) + rs = ring; + text[LINESIZ] = '\r'; + text[LINESIZ + 1] = '\n'; + (void) sendto(s, text, sizeof(text), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME +/* + * Return a machine readable date and time, in the form of the + * number of seconds since midnight, Jan 1, 1900. Since gettimeofday + * returns the number of seconds since midnight, Jan 1, 1970, + * we must add 2208988800 seconds to this figure to make up for + * some seventy years Bell Labs was asleep. + */ + +static u_int machtime(void) +{ + struct timeval tv; + + if (gettimeofday(&tv, NULL) < 0) { + fprintf(stderr, "Unable to get time of day\n"); + return 0L; + } + return htonl((u_int) tv.tv_sec + 2208988800UL); +} + +/* ARGSUSED */ +static void +machtime_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + u_int result; + + result = machtime(); + (void) write(s, (char *) &result, sizeof(result)); +} + +/* ARGSUSED */ +static void +machtime_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + u_int result; + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + struct sockaddr_in *dg_sin; + socklen_t size; + + size = sizeof(sa); + if (recvfrom(s, (char *) &result, sizeof(result), 0, &sa, &size) < 0) + return; + /* if (dg_badinput((struct sockaddr *)&ss)) */ + dg_sin = (struct sockaddr_in *) &sa; + if (dg_sin->sin_addr.s_addr == htonl(INADDR_BROADCAST) || + ntohs(dg_sin->sin_port) < IPPORT_RESERVED / 2) + return; + result = machtime(); + (void) sendto(s, (char *) &result, sizeof(result), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +/* Return human-readable time of day */ +/* ARGSUSED */ +static void daytime_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[256]; + time_t t; + + t = time(NULL); + + (void) sprintf(buffer, "%.24s\r\n", ctime(&t)); + (void) write(s, buffer, strlen(buffer)); +} + +/* Return human-readable time of day */ +/* ARGSUSED */ +void +daytime_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[256]; + time_t t; + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + socklen_t size; + + t = time(NULL); + + size = sizeof(sa); + if (recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size) < 0) + return; + if (dg_badinput((struct sockaddr_in *) &sa)) + return; + (void) sprintf(buffer, "%.24s\r\n", ctime(&t)); + (void) sendto(s, buffer, strlen(buffer), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME */ diff --git a/networking/interface.c b/networking/interface.c new file mode 100644 index 000000000..dd455823b --- /dev/null +++ b/networking/interface.c @@ -0,0 +1,1152 @@ +/* vi: set sw=4 ts=4: */ +/* + * stolen from net-tools-1.59 and stripped down for busybox by + * Erik Andersen + * + * Heavily modified by Manuel Novoa III Mar 12, 2001 + * + * Added print_bytes_scaled function to reduce code size. + * Added some (potentially) missing defines. + * Improved display support for -a and for a named interface. + * + * ----------------------------------------------------------- + * + * ifconfig This file contains an implementation of the command + * that either displays or sets the characteristics of + * one or more of the system's networking interfaces. + * + * Version: $Id: interface.c,v 1.25 2004/08/26 21:45:21 andersen Exp $ + * + * Author: Fred N. van Kempen, + * and others. Copyright 1993 MicroWalt Corporation + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Patched to support 'add' and 'del' keywords for INET(4) addresses + * by Mrs. Brisby + * + * {1.34} - 19980630 - Arnaldo Carvalho de Melo + * - gettext instead of catgets for i18n + * 10/1998 - Andi Kleen. Use interface list primitives. + * 20001008 - Bernd Eckenfels, Patch from RH for setting mtu + * (default AF was wrong) + */ + +#include "inet_common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +#ifdef CONFIG_FEATURE_IPV6 +# define HAVE_AFINET6 1 +#else +# undef HAVE_AFINET6 +#endif + +#define _PATH_PROCNET_DEV "/proc/net/dev" +#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6" + +#ifdef HAVE_AFINET6 + +#ifndef _LINUX_IN6_H +/* + * This is in linux/include/net/ipv6.h. + */ + +struct in6_ifreq { + struct in6_addr ifr6_addr; + uint32_t ifr6_prefixlen; + unsigned int ifr6_ifindex; +}; + +#endif + +#endif /* HAVE_AFINET6 */ + +/* Defines for glibc2.0 users. */ +#ifndef SIOCSIFTXQLEN +#define SIOCSIFTXQLEN 0x8943 +#define SIOCGIFTXQLEN 0x8942 +#endif + +/* ifr_qlen is ifru_ivalue, but it isn't present in 2.0 kernel headers */ +#ifndef ifr_qlen +#define ifr_qlen ifr_ifru.ifru_mtu +#endif + +#ifndef HAVE_TXQUEUELEN +#define HAVE_TXQUEUELEN 1 +#endif + +#ifndef IFF_DYNAMIC +#define IFF_DYNAMIC 0x8000 /* dialup device with changing addresses */ +#endif + +/* This structure defines protocol families and their handlers. */ +struct aftype { + const char *name; + const char *title; + int af; + int alen; + char *(*print) (unsigned char *); + char *(*sprint) (struct sockaddr *, int numeric); + int (*input) (int type, char *bufp, struct sockaddr *); + void (*herror) (char *text); + int (*rprint) (int options); + int (*rinput) (int typ, int ext, char **argv); + + /* may modify src */ + int (*getmask) (char *src, struct sockaddr * mask, char *name); + + int fd; + char *flag_file; +}; + +/* Display an Internet socket address. */ +static char *INET_sprint(struct sockaddr *sap, int numeric) +{ + static char buff[128]; + + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return safe_strncpy(buff, "[NONE SET]", sizeof(buff)); + + if (INET_rresolve(buff, sizeof(buff), (struct sockaddr_in *) sap, + numeric, 0xffffff00) != 0) + return NULL; + + return buff; +} + +static struct aftype inet_aftype = { + .name = "inet", + .title = "DARPA Internet", + .af = AF_INET, + .alen = 4, + .sprint = INET_sprint, + .fd = -1 +}; + +#ifdef HAVE_AFINET6 + +/* Display an Internet socket address. */ +/* dirty! struct sockaddr usually doesn't suffer for inet6 addresses, fst. */ +static char *INET6_sprint(struct sockaddr *sap, int numeric) +{ + static char buff[128]; + + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return safe_strncpy(buff, "[NONE SET]", sizeof(buff)); + if (INET6_rresolve + (buff, sizeof(buff), (struct sockaddr_in6 *) sap, numeric) != 0) + return safe_strncpy(buff, "[UNKNOWN]", sizeof(buff)); + return buff; +} + +static struct aftype inet6_aftype = { + .name = "inet6", + .title = "IPv6", + .af = AF_INET6, + .alen = sizeof(struct in6_addr), + .sprint = INET6_sprint, + .fd = -1 +}; + +#endif /* HAVE_AFINET6 */ + +/* Display an UNSPEC address. */ +static char *UNSPEC_print(unsigned char *ptr) +{ + static char buff[sizeof(struct sockaddr) * 3 + 1]; + char *pos; + unsigned int i; + + pos = buff; + for (i = 0; i < sizeof(struct sockaddr); i++) { + /* careful -- not every libc's sprintf returns # bytes written */ + sprintf(pos, "%02X-", (*ptr++ & 0377)); + pos += 3; + } + /* Erase trailing "-". Works as long as sizeof(struct sockaddr) != 0 */ + *--pos = '\0'; + return buff; +} + +/* Display an UNSPEC socket address. */ +static char *UNSPEC_sprint(struct sockaddr *sap, int numeric) +{ + static char buf[64]; + + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return safe_strncpy(buf, "[NONE SET]", sizeof(buf)); + return UNSPEC_print((unsigned char *)sap->sa_data); +} + +static struct aftype unspec_aftype = { + "unspec", "UNSPEC", AF_UNSPEC, 0, + UNSPEC_print, UNSPEC_sprint, NULL, NULL, + NULL, +}; + +static struct aftype * const aftypes[] = { + &inet_aftype, +#ifdef HAVE_AFINET6 + &inet6_aftype, +#endif + &unspec_aftype, + NULL +}; + +/* Check our protocol family table for this family. */ +static struct aftype *get_afntype(int af) +{ + struct aftype * const *afp; + + afp = aftypes; + while (*afp != NULL) { + if ((*afp)->af == af) + return *afp; + afp++; + } + return NULL; +} + +/* Check our protocol family table for this family and return its socket */ +static int get_socket_for_af(int af) +{ + struct aftype * const *afp; + + afp = aftypes; + while (*afp != NULL) { + if ((*afp)->af == af) + return (*afp)->fd; + afp++; + } + return -1; +} + +struct user_net_device_stats { + unsigned long long rx_packets; /* total packets received */ + unsigned long long tx_packets; /* total packets transmitted */ + unsigned long long rx_bytes; /* total bytes received */ + unsigned long long tx_bytes; /* total bytes transmitted */ + unsigned long rx_errors; /* bad packets received */ + unsigned long tx_errors; /* packet transmit problems */ + unsigned long rx_dropped; /* no space in linux buffers */ + unsigned long tx_dropped; /* no space available in linux */ + unsigned long rx_multicast; /* multicast packets received */ + unsigned long rx_compressed; + unsigned long tx_compressed; + unsigned long collisions; + + /* detailed rx_errors: */ + unsigned long rx_length_errors; + unsigned long rx_over_errors; /* receiver ring buff overflow */ + unsigned long rx_crc_errors; /* recved pkt with crc error */ + unsigned long rx_frame_errors; /* recv'd frame alignment error */ + unsigned long rx_fifo_errors; /* recv'r fifo overrun */ + unsigned long rx_missed_errors; /* receiver missed packet */ + /* detailed tx_errors */ + unsigned long tx_aborted_errors; + unsigned long tx_carrier_errors; + unsigned long tx_fifo_errors; + unsigned long tx_heartbeat_errors; + unsigned long tx_window_errors; +}; + +struct interface { + struct interface *next, *prev; + char name[IFNAMSIZ]; /* interface name */ + short type; /* if type */ + short flags; /* various flags */ + int metric; /* routing metric */ + int mtu; /* MTU value */ + int tx_queue_len; /* transmit queue length */ + struct ifmap map; /* hardware setup */ + struct sockaddr addr; /* IP address */ + struct sockaddr dstaddr; /* P-P IP address */ + struct sockaddr broadaddr; /* IP broadcast address */ + struct sockaddr netmask; /* IP network mask */ + int has_ip; + char hwaddr[32]; /* HW address */ + int statistics_valid; + struct user_net_device_stats stats; /* statistics */ + int keepalive; /* keepalive value for SLIP */ + int outfill; /* outfill value for SLIP */ +}; + + +int interface_opt_a; /* show all interfaces */ + +static struct interface *int_list, *int_last; +static int skfd = -1; /* generic raw socket desc. */ + + +static int sockets_open(int family) +{ + struct aftype * const *aft; + int sfd = -1; + static int force = -1; + + if (force < 0) { + force = 0; + if (get_linux_version_code() < KERNEL_VERSION(2,1,0)) + force = 1; + if (access("/proc/net", R_OK)) + force = 1; + } + for (aft = aftypes; *aft; aft++) { + struct aftype *af = *aft; + int type = SOCK_DGRAM; + + if (af->af == AF_UNSPEC) + continue; + if (family && family != af->af) + continue; + if (af->fd != -1) { + sfd = af->fd; + continue; + } + /* Check some /proc file first to not stress kmod */ + if (!family && !force && af->flag_file) { + if (access(af->flag_file, R_OK)) + continue; + } + af->fd = socket(af->af, type, 0); + if (af->fd >= 0) + sfd = af->fd; + } + if (sfd < 0) { + bb_error_msg("no usable address families found"); + } + return sfd; +} + +#ifdef CONFIG_FEATURE_CLEAN_UP +static void sockets_close(void) +{ + struct aftype * const *aft; + for (aft = aftypes; *aft != NULL; aft++) { + struct aftype *af = *aft; + if( af->fd != -1 ) { + close(af->fd); + af->fd = -1; + } + } +} +#endif +#if 0 +/* like strcmp(), but knows about numbers */ +except that the freshly added calls to xatoul() brf on ethernet aliases with +uClibc with e.g.: ife->name='lo' name='eth0:1' +static int nstrcmp(const char *a, const char *b) +{ + const char *a_ptr = a; + const char *b_ptr = b; + + while (*a == *b) { + if (*a == '\0') { + return 0; + } + if (!isdigit(*a) && isdigit(*(a+1))) { + a_ptr = a+1; + b_ptr = b+1; + } + a++; + b++; + } + + if (isdigit(*a) && isdigit(*b)) { + return xatoul(a_ptr) > xatoul(b_ptr) ? 1 : -1; + } + return *a - *b; +} +#endif + +static struct interface *add_interface(char *name) +{ + struct interface *ife, **nextp, *new; + + for (ife = int_last; ife; ife = ife->prev) { + int n = /*n*/strcmp(ife->name, name); + + if (n == 0) + return ife; + if (n < 0) + break; + } + + new = xzalloc(sizeof(*new)); + safe_strncpy(new->name, name, IFNAMSIZ); + nextp = ife ? &ife->next : &int_list; + new->prev = ife; + new->next = *nextp; + if (new->next) + new->next->prev = new; + else + int_last = new; + *nextp = new; + return new; +} + + +static int if_readconf(void) +{ + int numreqs = 30; + struct ifconf ifc; + struct ifreq *ifr; + int n, err = -1; + int skfd2; + + /* SIOCGIFCONF currently seems to only work properly on AF_INET sockets + (as of 2.1.128) */ + skfd2 = get_socket_for_af(AF_INET); + if (skfd2 < 0) { + bb_perror_msg(("warning: no inet socket available")); + /* Try to soldier on with whatever socket we can get hold of. */ + skfd2 = sockets_open(0); + if (skfd2 < 0) + return -1; + } + + ifc.ifc_buf = NULL; + for (;;) { + ifc.ifc_len = sizeof(struct ifreq) * numreqs; + ifc.ifc_buf = xrealloc(ifc.ifc_buf, ifc.ifc_len); + + if (ioctl(skfd2, SIOCGIFCONF, &ifc) < 0) { + perror("SIOCGIFCONF"); + goto out; + } + if (ifc.ifc_len == sizeof(struct ifreq) * numreqs) { + /* assume it overflowed and try again */ + numreqs += 10; + continue; + } + break; + } + + ifr = ifc.ifc_req; + for (n = 0; n < ifc.ifc_len; n += sizeof(struct ifreq)) { + add_interface(ifr->ifr_name); + ifr++; + } + err = 0; + + out: + free(ifc.ifc_buf); + return err; +} + +static char *get_name(char *name, char *p) +{ + /* Extract from nul-terminated p where p matches + : after leading whitespace. + If match is not made, set name empty and return unchanged p */ + int namestart=0, nameend=0; + while (isspace(p[namestart])) + namestart++; + nameend=namestart; + while (p[nameend] && p[nameend]!=':' && !isspace(p[nameend])) + nameend++; + if (p[nameend]==':') { + if ((nameend-namestart)stats, 0, sizeof(struct user_net_device_stats)); + + sscanf(bp, ss_fmt[procnetdev_vsn], + &ife->stats.rx_bytes, /* missing for 0 */ + &ife->stats.rx_packets, + &ife->stats.rx_errors, + &ife->stats.rx_dropped, + &ife->stats.rx_fifo_errors, + &ife->stats.rx_frame_errors, + &ife->stats.rx_compressed, /* missing for <= 1 */ + &ife->stats.rx_multicast, /* missing for <= 1 */ + &ife->stats.tx_bytes, /* missing for 0 */ + &ife->stats.tx_packets, + &ife->stats.tx_errors, + &ife->stats.tx_dropped, + &ife->stats.tx_fifo_errors, + &ife->stats.collisions, + &ife->stats.tx_carrier_errors, + &ife->stats.tx_compressed /* missing for <= 1 */ + ); + + if (procnetdev_vsn <= 1) { + if (procnetdev_vsn == 0) { + ife->stats.rx_bytes = 0; + ife->stats.tx_bytes = 0; + } + ife->stats.rx_multicast = 0; + ife->stats.rx_compressed = 0; + ife->stats.tx_compressed = 0; + } +} + +static inline int procnetdev_version(char *buf) +{ + if (strstr(buf, "compressed")) + return 2; + if (strstr(buf, "bytes")) + return 1; + return 0; +} + +static int if_readlist_proc(char *target) +{ + static int proc_read; + FILE *fh; + char buf[512]; + struct interface *ife; + int err, procnetdev_vsn; + + if (proc_read) + return 0; + if (!target) + proc_read = 1; + + fh = fopen(_PATH_PROCNET_DEV, "r"); + if (!fh) { + bb_perror_msg("warning: cannot open %s, limiting output", _PATH_PROCNET_DEV); + return if_readconf(); + } + fgets(buf, sizeof buf, fh); /* eat line */ + fgets(buf, sizeof buf, fh); + + procnetdev_vsn = procnetdev_version(buf); + + err = 0; + while (fgets(buf, sizeof buf, fh)) { + char *s, name[128]; + + s = get_name(name, buf); + ife = add_interface(name); + get_dev_fields(s, ife, procnetdev_vsn); + ife->statistics_valid = 1; + if (target && !strcmp(target, name)) + break; + } + if (ferror(fh)) { + perror(_PATH_PROCNET_DEV); + err = -1; + proc_read = 0; + } + fclose(fh); + return err; +} + +static int if_readlist(void) +{ + int err = if_readlist_proc(NULL); + + if (!err) + err = if_readconf(); + return err; +} + +static int for_all_interfaces(int (*doit) (struct interface *, void *), + void *cookie) +{ + struct interface *ife; + + if (!int_list && (if_readlist() < 0)) + return -1; + for (ife = int_list; ife; ife = ife->next) { + int err = doit(ife, cookie); + + if (err) + return err; + } + return 0; +} + +/* Fetch the interface configuration from the kernel. */ +static int if_fetch(struct interface *ife) +{ + struct ifreq ifr; + int fd; + char *ifname = ife->name; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) + return -1; + ife->flags = ifr.ifr_flags; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0) + memset(ife->hwaddr, 0, 32); + else + memcpy(ife->hwaddr, ifr.ifr_hwaddr.sa_data, 8); + + ife->type = ifr.ifr_hwaddr.sa_family; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFMETRIC, &ifr) < 0) + ife->metric = 0; + else + ife->metric = ifr.ifr_metric; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFMTU, &ifr) < 0) + ife->mtu = 0; + else + ife->mtu = ifr.ifr_mtu; + +#ifdef SIOCGIFMAP + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFMAP, &ifr) == 0) + ife->map = ifr.ifr_map; + else +#endif + memset(&ife->map, 0, sizeof(struct ifmap)); + +#ifdef HAVE_TXQUEUELEN + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFTXQLEN, &ifr) < 0) + ife->tx_queue_len = -1; /* unknown value */ + else + ife->tx_queue_len = ifr.ifr_qlen; +#else + ife->tx_queue_len = -1; /* unknown value */ +#endif + + /* IPv4 address? */ + fd = get_socket_for_af(AF_INET); + if (fd >= 0) { + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(fd, SIOCGIFADDR, &ifr) == 0) { + ife->has_ip = 1; + ife->addr = ifr.ifr_addr; + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFDSTADDR, &ifr) < 0) + memset(&ife->dstaddr, 0, sizeof(struct sockaddr)); + else + ife->dstaddr = ifr.ifr_dstaddr; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFBRDADDR, &ifr) < 0) + memset(&ife->broadaddr, 0, sizeof(struct sockaddr)); + else + ife->broadaddr = ifr.ifr_broadaddr; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFNETMASK, &ifr) < 0) + memset(&ife->netmask, 0, sizeof(struct sockaddr)); + else + ife->netmask = ifr.ifr_netmask; + } else + memset(&ife->addr, 0, sizeof(struct sockaddr)); + } + + return 0; +} + + +static int do_if_fetch(struct interface *ife) +{ + if (if_fetch(ife) < 0) { + char *errmsg; + + if (errno == ENODEV) { + /* Give better error message for this case. */ + errmsg = "Device not found"; + } else { + errmsg = strerror(errno); + } + bb_error_msg("%s: error fetching interface information: %s", + ife->name, errmsg); + return -1; + } + return 0; +} + +/* This structure defines hardware protocols and their handlers. */ +struct hwtype { + const char * const name; + const char *title; + int type; + int alen; + char *(*print) (unsigned char *); + int (*input) (char *, struct sockaddr *); + int (*activate) (int fd); + int suppress_null_addr; +}; + +static const struct hwtype unspec_hwtype = { + .name = "unspec", + .title = "UNSPEC", + .type = -1, + .print = UNSPEC_print +}; + +static const struct hwtype loop_hwtype = { + .name = "loop", + .title = "Local Loopback", + .type = ARPHRD_LOOPBACK +}; + +#include + +#if (__GLIBC__ >=2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION) +#include +#else +#include +#endif + +/* Display an Ethernet address in readable format. */ +static char *pr_ether(unsigned char *ptr) +{ + static char buff[64]; + + snprintf(buff, sizeof(buff), "%02X:%02X:%02X:%02X:%02X:%02X", + (ptr[0] & 0377), (ptr[1] & 0377), (ptr[2] & 0377), + (ptr[3] & 0377), (ptr[4] & 0377), (ptr[5] & 0377) + ); + return buff; +} + +static const struct hwtype ether_hwtype = { + .name = "ether", + .title = "Ethernet", + .type = ARPHRD_ETHER, + .alen = ETH_ALEN, + .print = pr_ether +}; + +#include + +static const struct hwtype ppp_hwtype = { + .name = "ppp", + .title = "Point-to-Point Protocol", + .type = ARPHRD_PPP +}; + +#ifdef CONFIG_FEATURE_IPV6 +static const struct hwtype sit_hwtype = { + .name = "sit", + .title = "IPv6-in-IPv4", + .type = ARPHRD_SIT, + .print = UNSPEC_print, + .suppress_null_addr = 1 +} ; +#endif + +static const struct hwtype * const hwtypes[] = { + &loop_hwtype, + ðer_hwtype, + &ppp_hwtype, + &unspec_hwtype, +#ifdef CONFIG_FEATURE_IPV6 + &sit_hwtype, +#endif + NULL +}; + +#ifdef IFF_PORTSEL +static const char * const if_port_text[] = { + /* Keep in step with */ + "unknown", + "10base2", + "10baseT", + "AUI", + "100baseT", + "100baseTX", + "100baseFX", + NULL +}; +#endif + +/* Check our hardware type table for this type. */ +static const struct hwtype *get_hwntype(int type) +{ + const struct hwtype * const *hwp; + + hwp = hwtypes; + while (*hwp != NULL) { + if ((*hwp)->type == type) + return *hwp; + hwp++; + } + return NULL; +} + +/* return 1 if address is all zeros */ +static int hw_null_address(const struct hwtype *hw, void *ap) +{ + unsigned int i; + unsigned char *address = (unsigned char *) ap; + + for (i = 0; i < hw->alen; i++) + if (address[i]) + return 0; + return 1; +} + +static const char TRext[] = "\0\0\0Ki\0Mi\0Gi\0Ti"; + +static void print_bytes_scaled(unsigned long long ull, const char *end) +{ + unsigned long long int_part; + const char *ext; + unsigned int frac_part; + int i; + + frac_part = 0; + ext = TRext; + int_part = ull; + i = 4; + do { + if (int_part >= 1024) { + frac_part = ((((unsigned int) int_part) & (1024-1)) * 10) / 1024; + int_part /= 1024; + ext += 3; /* KiB, MiB, GiB, TiB */ + } + --i; + } while (i); + + printf("X bytes:%llu (%llu.%u %sB)%s", ull, int_part, frac_part, ext, end); +} + +static const char * const ife_print_flags_strs[] = { + "UP ", + "BROADCAST ", + "DEBUG ", + "LOOPBACK ", + "POINTOPOINT ", + "NOTRAILERS ", + "RUNNING ", + "NOARP ", + "PROMISC ", + "ALLMULTI ", + "SLAVE ", + "MASTER ", + "MULTICAST ", +#ifdef HAVE_DYNAMIC + "DYNAMIC " +#endif +}; + +static const unsigned short ife_print_flags_mask[] = { + IFF_UP, + IFF_BROADCAST, + IFF_DEBUG, + IFF_LOOPBACK, + IFF_POINTOPOINT, + IFF_NOTRAILERS, + IFF_RUNNING, + IFF_NOARP, + IFF_PROMISC, + IFF_ALLMULTI, + IFF_SLAVE, + IFF_MASTER, + IFF_MULTICAST, +#ifdef HAVE_DYNAMIC + IFF_DYNAMIC +#endif + 0 +}; + +static void ife_print(struct interface *ptr) +{ + struct aftype *ap; + const struct hwtype *hw; + int hf; + int can_compress = 0; + +#ifdef HAVE_AFINET6 + FILE *f; + char addr6[40], devname[20]; + struct sockaddr_in6 sap; + int plen, scope, dad_status, if_idx; + char addr6p[8][5]; +#endif + + ap = get_afntype(ptr->addr.sa_family); + if (ap == NULL) + ap = get_afntype(0); + + hf = ptr->type; + + if (hf == ARPHRD_CSLIP || hf == ARPHRD_CSLIP6) + can_compress = 1; + + hw = get_hwntype(hf); + if (hw == NULL) + hw = get_hwntype(-1); + + printf("%-9.9s Link encap:%s ", ptr->name, hw->title); + /* For some hardware types (eg Ash, ATM) we don't print the + hardware address if it's null. */ + if (hw->print != NULL && (!(hw_null_address(hw, ptr->hwaddr) && + hw->suppress_null_addr))) + printf("HWaddr %s ", hw->print((unsigned char *)ptr->hwaddr)); +#ifdef IFF_PORTSEL + if (ptr->flags & IFF_PORTSEL) { + printf("Media:%s", if_port_text[ptr->map.port] /* [0] */); + if (ptr->flags & IFF_AUTOMEDIA) + printf("(auto)"); + } +#endif + puts(""); + + if (ptr->has_ip) { + printf(" %s addr:%s ", ap->name, + ap->sprint(&ptr->addr, 1)); + if (ptr->flags & IFF_POINTOPOINT) { + printf(" P-t-P:%s ", ap->sprint(&ptr->dstaddr, 1)); + } + if (ptr->flags & IFF_BROADCAST) { + printf(" Bcast:%s ", ap->sprint(&ptr->broadaddr, 1)); + } + printf(" Mask:%s\n", ap->sprint(&ptr->netmask, 1)); + } + +#ifdef HAVE_AFINET6 + +#define IPV6_ADDR_ANY 0x0000U + +#define IPV6_ADDR_UNICAST 0x0001U +#define IPV6_ADDR_MULTICAST 0x0002U +#define IPV6_ADDR_ANYCAST 0x0004U + +#define IPV6_ADDR_LOOPBACK 0x0010U +#define IPV6_ADDR_LINKLOCAL 0x0020U +#define IPV6_ADDR_SITELOCAL 0x0040U + +#define IPV6_ADDR_COMPATv4 0x0080U + +#define IPV6_ADDR_SCOPE_MASK 0x00f0U + +#define IPV6_ADDR_MAPPED 0x1000U +#define IPV6_ADDR_RESERVED 0x2000U /* reserved address space */ + + if ((f = fopen(_PATH_PROCNET_IFINET6, "r")) != NULL) { + while (fscanf + (f, "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %20s\n", + addr6p[0], addr6p[1], addr6p[2], addr6p[3], addr6p[4], + addr6p[5], addr6p[6], addr6p[7], &if_idx, &plen, &scope, + &dad_status, devname) != EOF) { + if (!strcmp(devname, ptr->name)) { + sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s", + addr6p[0], addr6p[1], addr6p[2], addr6p[3], + addr6p[4], addr6p[5], addr6p[6], addr6p[7]); + inet_pton(AF_INET6, addr6, + (struct sockaddr *) &sap.sin6_addr); + sap.sin6_family = AF_INET6; + printf(" inet6 addr: %s/%d", + inet6_aftype.sprint((struct sockaddr *) &sap, 1), + plen); + printf(" Scope:"); + switch (scope & IPV6_ADDR_SCOPE_MASK) { + case 0: + printf("Global"); + break; + case IPV6_ADDR_LINKLOCAL: + printf("Link"); + break; + case IPV6_ADDR_SITELOCAL: + printf("Site"); + break; + case IPV6_ADDR_COMPATv4: + printf("Compat"); + break; + case IPV6_ADDR_LOOPBACK: + printf("Host"); + break; + default: + printf("Unknown"); + } + puts(""); + } + } + fclose(f); + } +#endif + + printf(" "); + /* DONT FORGET TO ADD THE FLAGS IN ife_print_short, too */ + + if (ptr->flags == 0) { + printf("[NO FLAGS] "); + } else { + int i = 0; + do { + if (ptr->flags & ife_print_flags_mask[i]) { + printf(ife_print_flags_strs[i]); + } + } while (ife_print_flags_mask[++i]); + } + + /* DONT FORGET TO ADD THE FLAGS IN ife_print_short */ + printf(" MTU:%d Metric:%d", ptr->mtu, ptr->metric ? ptr->metric : 1); +#ifdef SIOCSKEEPALIVE + if (ptr->outfill || ptr->keepalive) + printf(" Outfill:%d Keepalive:%d", ptr->outfill, ptr->keepalive); +#endif + puts(""); + + /* If needed, display the interface statistics. */ + + if (ptr->statistics_valid) { + /* XXX: statistics are currently only printed for the primary address, + * not for the aliases, although strictly speaking they're shared + * by all addresses. + */ + printf(" "); + + printf("RX packets:%llu errors:%lu dropped:%lu overruns:%lu frame:%lu\n", + ptr->stats.rx_packets, ptr->stats.rx_errors, + ptr->stats.rx_dropped, ptr->stats.rx_fifo_errors, + ptr->stats.rx_frame_errors); + if (can_compress) + printf(" compressed:%lu\n", + ptr->stats.rx_compressed); + printf(" "); + printf("TX packets:%llu errors:%lu dropped:%lu overruns:%lu carrier:%lu\n", + ptr->stats.tx_packets, ptr->stats.tx_errors, + ptr->stats.tx_dropped, ptr->stats.tx_fifo_errors, + ptr->stats.tx_carrier_errors); + printf(" collisions:%lu ", ptr->stats.collisions); + if (can_compress) + printf("compressed:%lu ", ptr->stats.tx_compressed); + if (ptr->tx_queue_len != -1) + printf("txqueuelen:%d ", ptr->tx_queue_len); + printf("\n R"); + print_bytes_scaled(ptr->stats.rx_bytes, " T"); + print_bytes_scaled(ptr->stats.tx_bytes, "\n"); + + } + + if ((ptr->map.irq || ptr->map.mem_start || ptr->map.dma || + ptr->map.base_addr)) { + printf(" "); + if (ptr->map.irq) + printf("Interrupt:%d ", ptr->map.irq); + if (ptr->map.base_addr >= 0x100) /* Only print devices using it for + I/O maps */ + printf("Base address:0x%lx ", + (unsigned long) ptr->map.base_addr); + if (ptr->map.mem_start) { + printf("Memory:%lx-%lx ", ptr->map.mem_start, + ptr->map.mem_end); + } + if (ptr->map.dma) + printf("DMA chan:%x ", ptr->map.dma); + puts(""); + } + puts(""); +} + + +static int do_if_print(struct interface *ife, void *cookie) +{ + int *opt_a = (int *) cookie; + int res; + + res = do_if_fetch(ife); + if (res >= 0) { + if ((ife->flags & IFF_UP) || *opt_a) + ife_print(ife); + } + return res; +} + +static struct interface *lookup_interface(char *name) +{ + struct interface *ife = NULL; + + if (if_readlist_proc(name) < 0) + return NULL; + ife = add_interface(name); + return ife; +} + +/* for ipv4 add/del modes */ +static int if_print(char *ifname) +{ + int res; + + if (!ifname) { + res = for_all_interfaces(do_if_print, &interface_opt_a); + } else { + struct interface *ife; + + ife = lookup_interface(ifname); + res = do_if_fetch(ife); + if (res >= 0) + ife_print(ife); + } + return res; +} + +int display_interfaces(char *ifname); +int display_interfaces(char *ifname) +{ + int status; + + /* Create a channel to the NET kernel. */ + if ((skfd = sockets_open(0)) < 0) { + bb_perror_msg_and_die("socket"); + } + + /* Do we have to show the current setup? */ + status = if_print(ifname); +#ifdef CONFIG_FEATURE_CLEAN_UP + sockets_close(); +#endif + exit(status < 0); +} diff --git a/networking/ip.c b/networking/ip.c new file mode 100644 index 000000000..4c8b89e2f --- /dev/null +++ b/networking/ip.c @@ -0,0 +1,48 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "busybox.h" + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +int ip_main(int argc, char **argv) +{ + int ret = EXIT_FAILURE; + + ip_parse_common_args(&argc, &argv); + + if (argc > 1) { + if (ENABLE_FEATURE_IP_ADDRESS && matches(argv[1], "address") == 0) { + ret = do_ipaddr(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_ROUTE && matches(argv[1], "route") == 0) { + ret = do_iproute(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_LINK && matches(argv[1], "link") == 0) { + ret = do_iplink(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_TUNNEL && + (matches(argv[1], "tunnel") == 0 || strcmp(argv[1], "tunl") == 0)) { + ret = do_iptunnel(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_RULE && matches(argv[1], "rule") == 0) { + ret = do_iprule(argc-2, argv+2); + } + } + if (ret) { + bb_show_usage(); + } + return EXIT_SUCCESS; +} diff --git a/networking/ipaddr.c b/networking/ipaddr.c new file mode 100644 index 000000000..c90780879 --- /dev/null +++ b/networking/ipaddr.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int ipaddr_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_ipaddr(argc-1, argv+1); +} diff --git a/networking/ipcalc.c b/networking/ipcalc.c new file mode 100644 index 000000000..0ee9646c2 --- /dev/null +++ b/networking/ipcalc.c @@ -0,0 +1,194 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini ipcalc implementation for busybox + * + * By Jordan Crouse + * Stephan Linz + * + * This is a complete reimplementation of the ipcalc program + * from Red Hat. I didn't look at their source code, but there + * is no denying that this is a loving reimplementation + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include +#include + +#define CLASS_A_NETMASK ntohl(0xFF000000) +#define CLASS_B_NETMASK ntohl(0xFFFF0000) +#define CLASS_C_NETMASK ntohl(0xFFFFFF00) + +static unsigned long get_netmask(unsigned long ipaddr) +{ + ipaddr = htonl(ipaddr); + + if ((ipaddr & 0xC0000000) == 0xC0000000) + return CLASS_C_NETMASK; + else if ((ipaddr & 0x80000000) == 0x80000000) + return CLASS_B_NETMASK; + else if ((ipaddr & 0x80000000) == 0) + return CLASS_A_NETMASK; + else + return 0; +} + +#ifdef CONFIG_FEATURE_IPCALC_FANCY +static int get_prefix(unsigned long netmask) +{ + unsigned long msk = 0x80000000; + int ret = 0; + + netmask = htonl(netmask); + while(msk) { + if (netmask & msk) + ret++; + msk >>= 1; + } + return ret; +} +#else +int get_prefix(unsigned long netmask); +#endif + + +#define NETMASK 0x01 +#define BROADCAST 0x02 +#define NETWORK 0x04 +#define NETPREFIX 0x08 +#define HOSTNAME 0x10 +#define SILENT 0x20 + +#if ENABLE_FEATURE_IPCALC_LONG_OPTIONS + static const struct option long_options[] = { + { "netmask", no_argument, NULL, 'm' }, + { "broadcast", no_argument, NULL, 'b' }, + { "network", no_argument, NULL, 'n' }, +# if ENABLE_FEATURE_IPCALC_FANCY + { "prefix", no_argument, NULL, 'p' }, + { "hostname", no_argument, NULL, 'h' }, + { "silent", no_argument, NULL, 's' }, +# endif + { NULL, 0, NULL, 0 } + }; +#endif + +int ipcalc_main(int argc, char **argv) +{ + unsigned opt; + int have_netmask = 0; + in_addr_t netmask, broadcast, network, ipaddr; + struct in_addr a; + char *ipstr; + +#if ENABLE_FEATURE_IPCALC_LONG_OPTIONS + applet_long_options = long_options; +#endif + opt = getopt32(argc, argv, "mbn" USE_FEATURE_IPCALC_FANCY("phs")); + argc -= optind; + argv += optind; + if (opt & (BROADCAST | NETWORK | NETPREFIX)) { + if (argc > 2 || argc <= 0) + bb_show_usage(); + } else { + if (argc != 1) + bb_show_usage(); + } + if (opt & SILENT) + logmode = LOGMODE_NONE; /* Suppress error_msg() output */ + + ipstr = argv[0]; + if (ENABLE_FEATURE_IPCALC_FANCY) { + unsigned long netprefix = 0; + char *prefixstr; + + prefixstr = ipstr; + + while (*prefixstr) { + if (*prefixstr == '/') { + *prefixstr = (char)0; + prefixstr++; + if (*prefixstr) { + unsigned msk; + netprefix = xatoul_range(prefixstr, 0, 32); + netmask = 0; + msk = 0x80000000; + while (netprefix > 0) { + netmask |= msk; + msk >>= 1; + netprefix--; + } + netmask = htonl(netmask); + /* Even if it was 0, we will signify that we have a netmask. This allows */ + /* for specification of default routes, etc which have a 0 netmask/prefix */ + have_netmask = 1; + } + break; + } + prefixstr++; + } + } + ipaddr = inet_aton(ipstr, &a); + + if (ipaddr == 0) { + bb_error_msg_and_die("bad IP address: %s", argv[0]); + } + ipaddr = a.s_addr; + + if (argc == 2) { + if (ENABLE_FEATURE_IPCALC_FANCY && have_netmask) { + bb_error_msg_and_die("use prefix or netmask, not both"); + } + + netmask = inet_aton(argv[1], &a); + if (netmask == 0) { + bb_error_msg_and_die("bad netmask: %s", argv[1]); + } + netmask = a.s_addr; + } else { + + /* JHC - If the netmask wasn't provided then calculate it */ + if (!ENABLE_FEATURE_IPCALC_FANCY || !have_netmask) + netmask = get_netmask(ipaddr); + } + + if (opt & NETMASK) { + printf("NETMASK=%s\n", inet_ntoa((*(struct in_addr *) &netmask))); + } + + if (opt & BROADCAST) { + broadcast = (ipaddr & netmask) | ~netmask; + printf("BROADCAST=%s\n", inet_ntoa((*(struct in_addr *) &broadcast))); + } + + if (opt & NETWORK) { + network = ipaddr & netmask; + printf("NETWORK=%s\n", inet_ntoa((*(struct in_addr *) &network))); + } + + if (ENABLE_FEATURE_IPCALC_FANCY) { + if (opt & NETPREFIX) { + printf("PREFIX=%i\n", get_prefix(netmask)); + } + + if (opt & HOSTNAME) { + struct hostent *hostinfo; + int x; + + hostinfo = gethostbyaddr((char *) &ipaddr, sizeof(ipaddr), AF_INET); + if (!hostinfo) { + bb_herror_msg_and_die("cannot find hostname for %s", argv[0]); + } + for (x = 0; hostinfo->h_name[x]; x++) { + hostinfo->h_name[x] = tolower(hostinfo->h_name[x]); + } + + printf("HOSTNAME=%s\n", hostinfo->h_name); + } + } + + return EXIT_SUCCESS; +} diff --git a/networking/iplink.c b/networking/iplink.c new file mode 100644 index 000000000..4a28c74a9 --- /dev/null +++ b/networking/iplink.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iplink_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iplink(argc-1, argv+1); +} diff --git a/networking/iproute.c b/networking/iproute.c new file mode 100644 index 000000000..62335a8b3 --- /dev/null +++ b/networking/iproute.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iproute_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iproute(argc-1, argv+1); +} diff --git a/networking/iprule.c b/networking/iprule.c new file mode 100644 index 000000000..43eaea633 --- /dev/null +++ b/networking/iprule.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iprule_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iprule(argc-1, argv+1); +} diff --git a/networking/iptunnel.c b/networking/iptunnel.c new file mode 100644 index 000000000..9ae734a21 --- /dev/null +++ b/networking/iptunnel.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iptunnel_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iptunnel(argc-1, argv+1); +} diff --git a/networking/libiproute/Kbuild b/networking/libiproute/Kbuild new file mode 100644 index 000000000..8383630f2 --- /dev/null +++ b/networking/libiproute/Kbuild @@ -0,0 +1,66 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2004 by Erik Andersen +# +# Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +# + +lib-y:= +lib-$(CONFIG_IP) += \ + ip_parse_common_args.o \ + ipaddress.o \ + iplink.o \ + iproute.o \ + iptunnel.o \ + iprule.o \ + libnetlink.o \ + ll_addr.o \ + ll_map.o \ + ll_proto.o \ + ll_types.o \ + rt_names.o \ + rtm_map.o \ + utils.o + +lib-$(CONFIG_IPADDR) += \ + ip_parse_common_args.o \ + ipaddress.o \ + libnetlink.o \ + ll_addr.o \ + ll_map.o \ + ll_types.o \ + rt_names.o \ + utils.o + +lib-$(CONFIG_IPLINK) += \ + ip_parse_common_args.o \ + ipaddress.o \ + iplink.o \ + libnetlink.o \ + ll_addr.o \ + ll_map.o \ + ll_types.o \ + rt_names.o \ + utils.o + +lib-$(CONFIG_IPROUTE) += \ + ip_parse_common_args.o \ + iproute.o \ + libnetlink.o \ + ll_map.o \ + rt_names.o \ + rtm_map.o \ + utils.o + +lib-$(CONFIG_IPTUNNEL) += \ + ip_parse_common_args.o \ + iptunnel.o \ + rt_names.o \ + utils.o + +lib-$(CONFIG_IPRULE) += \ + ip_parse_common_args.o \ + iprule.o \ + rt_names.o \ + utils.o + diff --git a/networking/libiproute/ip_common.h b/networking/libiproute/ip_common.h new file mode 100644 index 000000000..15291780e --- /dev/null +++ b/networking/libiproute/ip_common.h @@ -0,0 +1,34 @@ +/* vi: set sw=4 ts=4: */ +#ifndef _IP_COMMON_H +#define _IP_COMMON_H 1 + +#include "busybox.h" +#include +#include +#include +#if !defined IFA_RTA +#include +#endif +#if !defined IFLA_RTA +#include +#endif + +extern int preferred_family; +extern char * _SL_; + +extern void ip_parse_common_args(int *argcp, char ***argvp); +extern int print_neigh(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +extern int ipaddr_list_or_flush(int argc, char **argv, int flush); +extern int iproute_monitor(int argc, char **argv); +extern void iplink_usage(void) ATTRIBUTE_NORETURN; +extern void ipneigh_reset_filter(void); +extern int do_ipaddr(int argc, char **argv); +extern int do_iproute(int argc, char **argv); +extern int do_iprule(int argc, char **argv); +extern int do_ipneigh(int argc, char **argv); +extern int do_iptunnel(int argc, char **argv); +extern int do_iplink(int argc, char **argv); +extern int do_ipmonitor(int argc, char **argv); +extern int do_multiaddr(int argc, char **argv); +extern int do_multiroute(int argc, char **argv); +#endif /* ip_common.h */ diff --git a/networking/libiproute/ip_parse_common_args.c b/networking/libiproute/ip_parse_common_args.c new file mode 100644 index 000000000..fee6e5e95 --- /dev/null +++ b/networking/libiproute/ip_parse_common_args.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include + +#include "libbb.h" +#include "utils.h" +#include "ip_common.h" + + +int preferred_family = AF_UNSPEC; +int oneline = 0; +char * _SL_ = NULL; + +void ip_parse_common_args(int *argcp, char ***argvp) +{ + int argc = *argcp; + char **argv = *argvp; + + while (argc > 1) { + char *opt = argv[1]; + + if (strcmp(opt,"--") == 0) { + argc--; argv++; + break; + } + + if (opt[0] != '-') + break; + + if (opt[1] == '-') + opt++; + + if (matches(opt, "-family") == 0) { + argc--; + argv++; + if (!argv[1]) + bb_show_usage(); + if (strcmp(argv[1], "inet") == 0) + preferred_family = AF_INET; + else if (strcmp(argv[1], "inet6") == 0) + preferred_family = AF_INET6; + else if (strcmp(argv[1], "link") == 0) + preferred_family = AF_PACKET; + else + invarg(argv[1], "protocol family"); + } else if (strcmp(opt, "-4") == 0) { + preferred_family = AF_INET; + } else if (strcmp(opt, "-6") == 0) { + preferred_family = AF_INET6; + } else if (strcmp(opt, "-0") == 0) { + preferred_family = AF_PACKET; + } else if (matches(opt, "-oneline") == 0) { + ++oneline; + } else { + bb_show_usage(); + } + argc--; argv++; + } + _SL_ = oneline ? "\\" : "\n" ; + *argcp = argc; + *argvp = argv; +} diff --git a/networking/libiproute/ipaddress.c b/networking/libiproute/ipaddress.c new file mode 100644 index 000000000..2a267fef6 --- /dev/null +++ b/networking/libiproute/ipaddress.c @@ -0,0 +1,824 @@ +/* vi: set sw=4 ts=4: */ +/* + * ipaddress.c "ip address". + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Authors: Alexey Kuznetsov, + * + * Changes: + * Laszlo Valko 990223: address label must be zero terminated + */ + +#include "libbb.h" +#include +#include + +#include +#include +#include + +#include +#include + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + + +static struct +{ + int ifindex; + int family; + int oneline; + int showqueue; + inet_prefix pfx; + int scope, scopemask; + int flags, flagmask; + int up; + char *label; + int flushed; + char *flushb; + int flushp; + int flushe; + struct rtnl_handle *rth; +} filter; + +static void print_link_flags(FILE *fp, unsigned flags, unsigned mdown) +{ + fprintf(fp, "<"); + flags &= ~IFF_RUNNING; +#define _PF(f) if (flags&IFF_##f) { \ + flags &= ~IFF_##f ; \ + fprintf(fp, #f "%s", flags ? "," : ""); } + _PF(LOOPBACK); + _PF(BROADCAST); + _PF(POINTOPOINT); + _PF(MULTICAST); + _PF(NOARP); +#if 0 + _PF(ALLMULTI); + _PF(PROMISC); + _PF(MASTER); + _PF(SLAVE); + _PF(DEBUG); + _PF(DYNAMIC); + _PF(AUTOMEDIA); + _PF(PORTSEL); + _PF(NOTRAILERS); +#endif + _PF(UP); +#undef _PF + if (flags) + fprintf(fp, "%x", flags); + if (mdown) + fprintf(fp, ",M-DOWN"); + fprintf(fp, "> "); +} + +static void print_queuelen(char *name) +{ + struct ifreq ifr; + int s; + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) + return; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFTXQLEN, &ifr) < 0) { + perror("SIOCGIFXQLEN"); + close(s); + return; + } + close(s); + + if (ifr.ifr_qlen) + printf("qlen %d", ifr.ifr_qlen); +} + +static int print_linkinfo(struct sockaddr_nl ATTRIBUTE_UNUSED *who, + const struct nlmsghdr *n, void ATTRIBUTE_UNUSED *arg) +{ + FILE *fp = (FILE*)arg; + struct ifinfomsg *ifi = NLMSG_DATA(n); + struct rtattr * tb[IFLA_MAX+1]; + int len = n->nlmsg_len; + unsigned m_flag = 0; + + if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK) + return 0; + + len -= NLMSG_LENGTH(sizeof(*ifi)); + if (len < 0) + return -1; + + if (filter.ifindex && ifi->ifi_index != filter.ifindex) + return 0; + if (filter.up && !(ifi->ifi_flags&IFF_UP)) + return 0; + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); + if (tb[IFLA_IFNAME] == NULL) { + bb_error_msg("nil ifname"); + return -1; + } + if (filter.label && + (!filter.family || filter.family == AF_PACKET) && + fnmatch(filter.label, RTA_DATA(tb[IFLA_IFNAME]), 0)) + return 0; + + if (n->nlmsg_type == RTM_DELLINK) + fprintf(fp, "Deleted "); + + fprintf(fp, "%d: %s", ifi->ifi_index, + tb[IFLA_IFNAME] ? (char*)RTA_DATA(tb[IFLA_IFNAME]) : ""); + + if (tb[IFLA_LINK]) { + SPRINT_BUF(b1); + int iflink = *(int*)RTA_DATA(tb[IFLA_LINK]); + if (iflink == 0) + fprintf(fp, "@NONE: "); + else { + fprintf(fp, "@%s: ", ll_idx_n2a(iflink, b1)); + m_flag = ll_index_to_flags(iflink); + m_flag = !(m_flag & IFF_UP); + } + } else { + fprintf(fp, ": "); + } + print_link_flags(fp, ifi->ifi_flags, m_flag); + + if (tb[IFLA_MTU]) + fprintf(fp, "mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU])); + if (tb[IFLA_QDISC]) + fprintf(fp, "qdisc %s ", (char*)RTA_DATA(tb[IFLA_QDISC])); +#ifdef IFLA_MASTER + if (tb[IFLA_MASTER]) { + SPRINT_BUF(b1); + fprintf(fp, "master %s ", ll_idx_n2a(*(int*)RTA_DATA(tb[IFLA_MASTER]), b1)); + } +#endif + if (filter.showqueue) + print_queuelen((char*)RTA_DATA(tb[IFLA_IFNAME])); + + if (!filter.family || filter.family == AF_PACKET) { + SPRINT_BUF(b1); + fprintf(fp, "%s", _SL_); + fprintf(fp, " link/%s ", ll_type_n2a(ifi->ifi_type, b1, sizeof(b1))); + + if (tb[IFLA_ADDRESS]) { + fprintf(fp, "%s", ll_addr_n2a(RTA_DATA(tb[IFLA_ADDRESS]), + RTA_PAYLOAD(tb[IFLA_ADDRESS]), + ifi->ifi_type, + b1, sizeof(b1))); + } + if (tb[IFLA_BROADCAST]) { + if (ifi->ifi_flags&IFF_POINTOPOINT) + fprintf(fp, " peer "); + else + fprintf(fp, " brd "); + fprintf(fp, "%s", ll_addr_n2a(RTA_DATA(tb[IFLA_BROADCAST]), + RTA_PAYLOAD(tb[IFLA_BROADCAST]), + ifi->ifi_type, + b1, sizeof(b1))); + } + } + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + +static int flush_update(void) +{ + if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) { + perror("Failed to send flush request\n"); + return -1; + } + filter.flushp = 0; + return 0; +} + +static int print_addrinfo(struct sockaddr_nl ATTRIBUTE_UNUSED *who, + struct nlmsghdr *n, void ATTRIBUTE_UNUSED *arg) +{ + FILE *fp = (FILE*)arg; + struct ifaddrmsg *ifa = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr * rta_tb[IFA_MAX+1]; + char abuf[256]; + SPRINT_BUF(b1); + + if (n->nlmsg_type != RTM_NEWADDR && n->nlmsg_type != RTM_DELADDR) + return 0; + len -= NLMSG_LENGTH(sizeof(*ifa)); + if (len < 0) { + bb_error_msg("wrong nlmsg len %d", len); + return -1; + } + + if (filter.flushb && n->nlmsg_type != RTM_NEWADDR) + return 0; + + memset(rta_tb, 0, sizeof(rta_tb)); + parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa))); + + if (!rta_tb[IFA_LOCAL]) + rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS]; + if (!rta_tb[IFA_ADDRESS]) + rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL]; + + if (filter.ifindex && filter.ifindex != ifa->ifa_index) + return 0; + if ((filter.scope^ifa->ifa_scope)&filter.scopemask) + return 0; + if ((filter.flags^ifa->ifa_flags)&filter.flagmask) + return 0; + if (filter.label) { + const char *label; + if (rta_tb[IFA_LABEL]) + label = RTA_DATA(rta_tb[IFA_LABEL]); + else + label = ll_idx_n2a(ifa->ifa_index, b1); + if (fnmatch(filter.label, label, 0) != 0) + return 0; + } + if (filter.pfx.family) { + if (rta_tb[IFA_LOCAL]) { + inet_prefix dst; + memset(&dst, 0, sizeof(dst)); + dst.family = ifa->ifa_family; + memcpy(&dst.data, RTA_DATA(rta_tb[IFA_LOCAL]), RTA_PAYLOAD(rta_tb[IFA_LOCAL])); + if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen)) + return 0; + } + } + + if (filter.flushb) { + struct nlmsghdr *fn; + if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) { + if (flush_update()) + return -1; + } + fn = (struct nlmsghdr*)(filter.flushb + NLMSG_ALIGN(filter.flushp)); + memcpy(fn, n, n->nlmsg_len); + fn->nlmsg_type = RTM_DELADDR; + fn->nlmsg_flags = NLM_F_REQUEST; + fn->nlmsg_seq = ++filter.rth->seq; + filter.flushp = (((char*)fn) + n->nlmsg_len) - filter.flushb; + filter.flushed++; + return 0; + } + + if (n->nlmsg_type == RTM_DELADDR) + fprintf(fp, "Deleted "); + + if (filter.oneline) + fprintf(fp, "%u: %s", ifa->ifa_index, ll_index_to_name(ifa->ifa_index)); + if (ifa->ifa_family == AF_INET) + fprintf(fp, " inet "); + else if (ifa->ifa_family == AF_INET6) + fprintf(fp, " inet6 "); + else + fprintf(fp, " family %d ", ifa->ifa_family); + + if (rta_tb[IFA_LOCAL]) { + fprintf(fp, "%s", rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_LOCAL]), + RTA_DATA(rta_tb[IFA_LOCAL]), + abuf, sizeof(abuf))); + + if (rta_tb[IFA_ADDRESS] == NULL || + memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]), 4) == 0) { + fprintf(fp, "/%d ", ifa->ifa_prefixlen); + } else { + fprintf(fp, " peer %s/%d ", + rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_ADDRESS]), + RTA_DATA(rta_tb[IFA_ADDRESS]), + abuf, sizeof(abuf)), + ifa->ifa_prefixlen); + } + } + + if (rta_tb[IFA_BROADCAST]) { + fprintf(fp, "brd %s ", + rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_BROADCAST]), + RTA_DATA(rta_tb[IFA_BROADCAST]), + abuf, sizeof(abuf))); + } + if (rta_tb[IFA_ANYCAST]) { + fprintf(fp, "any %s ", + rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_ANYCAST]), + RTA_DATA(rta_tb[IFA_ANYCAST]), + abuf, sizeof(abuf))); + } + fprintf(fp, "scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1))); + if (ifa->ifa_flags&IFA_F_SECONDARY) { + ifa->ifa_flags &= ~IFA_F_SECONDARY; + fprintf(fp, "secondary "); + } + if (ifa->ifa_flags&IFA_F_TENTATIVE) { + ifa->ifa_flags &= ~IFA_F_TENTATIVE; + fprintf(fp, "tentative "); + } + if (ifa->ifa_flags&IFA_F_DEPRECATED) { + ifa->ifa_flags &= ~IFA_F_DEPRECATED; + fprintf(fp, "deprecated "); + } + if (!(ifa->ifa_flags&IFA_F_PERMANENT)) { + fprintf(fp, "dynamic "); + } else + ifa->ifa_flags &= ~IFA_F_PERMANENT; + if (ifa->ifa_flags) + fprintf(fp, "flags %02x ", ifa->ifa_flags); + if (rta_tb[IFA_LABEL]) + fprintf(fp, "%s", (char*)RTA_DATA(rta_tb[IFA_LABEL])); + if (rta_tb[IFA_CACHEINFO]) { + struct ifa_cacheinfo *ci = RTA_DATA(rta_tb[IFA_CACHEINFO]); + char buf[128]; + fprintf(fp, "%s", _SL_); + if (ci->ifa_valid == 0xFFFFFFFFU) + sprintf(buf, "valid_lft forever"); + else + sprintf(buf, "valid_lft %dsec", ci->ifa_valid); + if (ci->ifa_prefered == 0xFFFFFFFFU) + sprintf(buf+strlen(buf), " preferred_lft forever"); + else + sprintf(buf+strlen(buf), " preferred_lft %dsec", ci->ifa_prefered); + fprintf(fp, " %s", buf); + } + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + + +struct nlmsg_list +{ + struct nlmsg_list *next; + struct nlmsghdr h; +}; + +static int print_selected_addrinfo(int ifindex, struct nlmsg_list *ainfo, FILE *fp) +{ + for ( ;ainfo ; ainfo = ainfo->next) { + struct nlmsghdr *n = &ainfo->h; + struct ifaddrmsg *ifa = NLMSG_DATA(n); + + if (n->nlmsg_type != RTM_NEWADDR) + continue; + + if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifa))) + return -1; + + if (ifa->ifa_index != ifindex || + (filter.family && filter.family != ifa->ifa_family)) + continue; + + print_addrinfo(NULL, n, fp); + } + return 0; +} + + +static int store_nlmsg(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) +{ + struct nlmsg_list **linfo = (struct nlmsg_list**)arg; + struct nlmsg_list *h; + struct nlmsg_list **lp; + + h = malloc(n->nlmsg_len+sizeof(void*)); + if (h == NULL) + return -1; + + memcpy(&h->h, n, n->nlmsg_len); + h->next = NULL; + + for (lp = linfo; *lp; lp = &(*lp)->next) /* NOTHING */; + *lp = h; + + ll_remember_index(who, n, NULL); + return 0; +} + +static void ipaddr_reset_filter(int _oneline) +{ + memset(&filter, 0, sizeof(filter)); + filter.oneline = _oneline; +} + +int ipaddr_list_or_flush(int argc, char **argv, int flush) +{ + static const char *const option[] = { "to", "scope", "up", "label", "dev", 0 }; + + struct nlmsg_list *linfo = NULL; + struct nlmsg_list *ainfo = NULL; + struct nlmsg_list *l; + struct rtnl_handle rth; + char *filter_dev = NULL; + int no_link = 0; + + ipaddr_reset_filter(oneline); + filter.showqueue = 1; + + if (filter.family == AF_UNSPEC) + filter.family = preferred_family; + + if (flush) { + if (argc <= 0) { + bb_error_msg(bb_msg_requires_arg, "flush"); + return -1; + } + if (filter.family == AF_PACKET) { + bb_error_msg("cannot flush link addresses"); + return -1; + } + } + + while (argc > 0) { + const int option_num = index_in_str_array(option, *argv); + switch (option_num) { + case 0: /* to */ + NEXT_ARG(); + get_prefix(&filter.pfx, *argv, filter.family); + if (filter.family == AF_UNSPEC) { + filter.family = filter.pfx.family; + } + break; + case 1: /* scope */ + { + uint32_t scope = 0; + NEXT_ARG(); + filter.scopemask = -1; + if (rtnl_rtscope_a2n(&scope, *argv)) { + if (strcmp(*argv, "all") != 0) { + invarg(*argv, "scope"); + } + scope = RT_SCOPE_NOWHERE; + filter.scopemask = 0; + } + filter.scope = scope; + break; + } + case 2: /* up */ + filter.up = 1; + break; + case 3: /* label */ + NEXT_ARG(); + filter.label = *argv; + break; + case 4: /* dev */ + NEXT_ARG(); + default: + if (filter_dev) { + duparg2("dev", *argv); + } + filter_dev = *argv; + } + argv++; + argc--; + } + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + if (rtnl_wilddump_request(&rth, preferred_family, RTM_GETLINK) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + + if (rtnl_dump_filter(&rth, store_nlmsg, &linfo, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + + if (filter_dev) { + filter.ifindex = ll_name_to_index(filter_dev); + if (filter.ifindex <= 0) { + bb_error_msg("device \"%s\" does not exist", filter_dev); + return -1; + } + } + + if (flush) { + char flushb[4096-512]; + + filter.flushb = flushb; + filter.flushp = 0; + filter.flushe = sizeof(flushb); + filter.rth = &rth; + + for (;;) { + if (rtnl_wilddump_request(&rth, filter.family, RTM_GETADDR) < 0) { + perror("Cannot send dump request"); + exit(1); + } + filter.flushed = 0; + if (rtnl_dump_filter(&rth, print_addrinfo, stdout, NULL, NULL) < 0) { + fprintf(stderr, "Flush terminated\n"); + exit(1); + } + if (filter.flushed == 0) { + fflush(stdout); + return 0; + } + if (flush_update() < 0) + exit(1); + } + } + + if (filter.family != AF_PACKET) { + if (rtnl_wilddump_request(&rth, filter.family, RTM_GETADDR) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + + if (rtnl_dump_filter(&rth, store_nlmsg, &ainfo, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + } + + + if (filter.family && filter.family != AF_PACKET) { + struct nlmsg_list **lp; + lp=&linfo; + + if (filter.oneline) + no_link = 1; + + while ((l=*lp)!=NULL) { + int ok = 0; + struct ifinfomsg *ifi = NLMSG_DATA(&l->h); + struct nlmsg_list *a; + + for (a=ainfo; a; a=a->next) { + struct nlmsghdr *n = &a->h; + struct ifaddrmsg *ifa = NLMSG_DATA(n); + + if (ifa->ifa_index != ifi->ifi_index || + (filter.family && filter.family != ifa->ifa_family)) + continue; + if ((filter.scope^ifa->ifa_scope)&filter.scopemask) + continue; + if ((filter.flags^ifa->ifa_flags)&filter.flagmask) + continue; + if (filter.pfx.family || filter.label) { + struct rtattr *tb[IFA_MAX+1]; + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(n)); + if (!tb[IFA_LOCAL]) + tb[IFA_LOCAL] = tb[IFA_ADDRESS]; + + if (filter.pfx.family && tb[IFA_LOCAL]) { + inet_prefix dst; + memset(&dst, 0, sizeof(dst)); + dst.family = ifa->ifa_family; + memcpy(&dst.data, RTA_DATA(tb[IFA_LOCAL]), RTA_PAYLOAD(tb[IFA_LOCAL])); + if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen)) + continue; + } + if (filter.label) { + SPRINT_BUF(b1); + const char *label; + if (tb[IFA_LABEL]) + label = RTA_DATA(tb[IFA_LABEL]); + else + label = ll_idx_n2a(ifa->ifa_index, b1); + if (fnmatch(filter.label, label, 0) != 0) + continue; + } + } + + ok = 1; + break; + } + if (!ok) + *lp = l->next; + else + lp = &l->next; + } + } + + for (l=linfo; l; l = l->next) { + if (no_link || print_linkinfo(NULL, &l->h, stdout) == 0) { + struct ifinfomsg *ifi = NLMSG_DATA(&l->h); + if (filter.family != AF_PACKET) + print_selected_addrinfo(ifi->ifi_index, ainfo, stdout); + } + fflush(stdout); + } + + exit(0); +} + +static int default_scope(inet_prefix *lcl) +{ + if (lcl->family == AF_INET) { + if (lcl->bytelen >= 1 && *(__u8*)&lcl->data == 127) + return RT_SCOPE_HOST; + } + return 0; +} + +static int ipaddr_modify(int cmd, int argc, char **argv) +{ + static const char *const option[] = { + "peer", "remote", "broadcast", "brd", + "anycast", "scope", "dev", "label", "local", 0 + }; + + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct ifaddrmsg ifa; + char buf[256]; + } req; + char *d = NULL; + char *l = NULL; + inet_prefix lcl; + inet_prefix peer; + int local_len = 0; + int peer_len = 0; + int brd_len = 0; + int any_len = 0; + int scoped = 0; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = cmd; + req.ifa.ifa_family = preferred_family; + + while (argc > 0) { + const int option_num = index_in_str_array(option, *argv); + switch (option_num) { + case 0: /* peer */ + case 1: /* remote */ + NEXT_ARG(); + + if (peer_len) { + duparg("peer", *argv); + } + get_prefix(&peer, *argv, req.ifa.ifa_family); + peer_len = peer.bytelen; + if (req.ifa.ifa_family == AF_UNSPEC) { + req.ifa.ifa_family = peer.family; + } + addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &peer.data, peer.bytelen); + req.ifa.ifa_prefixlen = peer.bitlen; + break; + case 2: /* broadcast */ + case 3: /* brd */ + { + inet_prefix addr; + NEXT_ARG(); + if (brd_len) { + duparg("broadcast", *argv); + } + if (strcmp(*argv, "+") == 0) { + brd_len = -1; + } + else if (strcmp(*argv, "-") == 0) { + brd_len = -2; + } else { + get_addr(&addr, *argv, req.ifa.ifa_family); + if (req.ifa.ifa_family == AF_UNSPEC) + req.ifa.ifa_family = addr.family; + addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &addr.data, addr.bytelen); + brd_len = addr.bytelen; + } + break; + } + case 4: /* anycast */ + { + inet_prefix addr; + NEXT_ARG(); + if (any_len) { + duparg("anycast", *argv); + } + get_addr(&addr, *argv, req.ifa.ifa_family); + if (req.ifa.ifa_family == AF_UNSPEC) { + req.ifa.ifa_family = addr.family; + } + addattr_l(&req.n, sizeof(req), IFA_ANYCAST, &addr.data, addr.bytelen); + any_len = addr.bytelen; + break; + } + case 5: /* scope */ + { + uint32_t scope = 0; + NEXT_ARG(); + if (rtnl_rtscope_a2n(&scope, *argv)) { + invarg(*argv, "scope"); + } + req.ifa.ifa_scope = scope; + scoped = 1; + break; + } + case 6: /* dev */ + NEXT_ARG(); + d = *argv; + break; + case 7: /* label */ + NEXT_ARG(); + l = *argv; + addattr_l(&req.n, sizeof(req), IFA_LABEL, l, strlen(l)+1); + break; + case 8: /* local */ + NEXT_ARG(); + default: + if (local_len) { + duparg2("local", *argv); + } + get_prefix(&lcl, *argv, req.ifa.ifa_family); + if (req.ifa.ifa_family == AF_UNSPEC) { + req.ifa.ifa_family = lcl.family; + } + addattr_l(&req.n, sizeof(req), IFA_LOCAL, &lcl.data, lcl.bytelen); + local_len = lcl.bytelen; + } + argc--; + argv++; + } + + if (d == NULL) { + bb_error_msg(bb_msg_requires_arg,"\"dev\""); + return -1; + } + if (l && matches(d, l) != 0) { + bb_error_msg_and_die("\"dev\" (%s) must match \"label\" (%s)", d, l); + } + + if (peer_len == 0 && local_len && cmd != RTM_DELADDR) { + peer = lcl; + addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &lcl.data, lcl.bytelen); + } + if (req.ifa.ifa_prefixlen == 0) + req.ifa.ifa_prefixlen = lcl.bitlen; + + if (brd_len < 0 && cmd != RTM_DELADDR) { + inet_prefix brd; + int i; + if (req.ifa.ifa_family != AF_INET) { + bb_error_msg("broadcast can be set only for IPv4 addresses"); + return -1; + } + brd = peer; + if (brd.bitlen <= 30) { + for (i=31; i>=brd.bitlen; i--) { + if (brd_len == -1) + brd.data[0] |= htonl(1<<(31-i)); + else + brd.data[0] &= ~htonl(1<<(31-i)); + } + addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &brd.data, brd.bytelen); + brd_len = brd.bytelen; + } + } + if (!scoped && cmd != RTM_DELADDR) + req.ifa.ifa_scope = default_scope(&lcl); + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + ll_init_map(&rth); + + if ((req.ifa.ifa_index = ll_name_to_index(d)) == 0) { + bb_error_msg("cannot find device \"%s\"", d); + return -1; + } + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) + exit(2); + + exit(0); +} + +int do_ipaddr(int argc, char **argv) +{ + static const char *const commands[] = { + "add", "delete", "list", "show", "lst", "flush", 0 + }; + + int command_num = 2; + + if (*argv) { + command_num = index_in_substr_array(commands, *argv); + } + switch (command_num) { + case 0: /* add */ + return ipaddr_modify(RTM_NEWADDR, argc-1, argv+1); + case 1: /* delete */ + return ipaddr_modify(RTM_DELADDR, argc-1, argv+1); + case 2: /* list */ + case 3: /* show */ + case 4: /* lst */ + return ipaddr_list_or_flush(argc-1, argv+1, 0); + case 5: /* flush */ + return ipaddr_list_or_flush(argc-1, argv+1, 1); + } + bb_error_msg_and_die("unknown command %s", *argv); +} diff --git a/networking/libiproute/iplink.c b/networking/libiproute/iplink.c new file mode 100644 index 000000000..1ea11f60b --- /dev/null +++ b/networking/libiproute/iplink.c @@ -0,0 +1,354 @@ +/* vi: set sw=4 ts=4: */ +/* + * iplink.c "ip link". + * + * Authors: Alexey Kuznetsov, + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + +/* take from linux/sockios.h */ +#define SIOCSIFNAME 0x8923 /* set interface name */ + +static int on_off(char *msg) +{ + bb_error_msg("error: argument of \"%s\" must be \"on\" or \"off\"", msg); + return -1; +} + +static int get_ctl_fd(void) +{ + int s_errno; + int fd; + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + s_errno = errno; + fd = socket(PF_PACKET, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + fd = socket(PF_INET6, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + errno = s_errno; + perror("Cannot create control socket"); + return -1; +} + +static int do_chflags(char *dev, __u32 flags, __u32 mask) +{ + struct ifreq ifr; + int fd; + int err; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = get_ctl_fd(); + if (fd < 0) + return -1; + err = ioctl(fd, SIOCGIFFLAGS, &ifr); + if (err) { + perror("SIOCGIFFLAGS"); + close(fd); + return -1; + } + if ((ifr.ifr_flags^flags)&mask) { + ifr.ifr_flags &= ~mask; + ifr.ifr_flags |= mask&flags; + err = ioctl(fd, SIOCSIFFLAGS, &ifr); + if (err) + perror("SIOCSIFFLAGS"); + } + close(fd); + return err; +} + +static int do_changename(char *dev, char *newdev) +{ + struct ifreq ifr; + int fd; + int err; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + strncpy(ifr.ifr_newname, newdev, sizeof(ifr.ifr_newname)); + fd = get_ctl_fd(); + if (fd < 0) + return -1; + err = ioctl(fd, SIOCSIFNAME, &ifr); + if (err) { + perror("SIOCSIFNAME"); + close(fd); + return -1; + } + close(fd); + return err; +} + +static int set_qlen(char *dev, int qlen) +{ + struct ifreq ifr; + int s; + + s = get_ctl_fd(); + if (s < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + ifr.ifr_qlen = qlen; + if (ioctl(s, SIOCSIFTXQLEN, &ifr) < 0) { + perror("SIOCSIFXQLEN"); + close(s); + return -1; + } + close(s); + + return 0; +} + +static int set_mtu(char *dev, int mtu) +{ + struct ifreq ifr; + int s; + + s = get_ctl_fd(); + if (s < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = mtu; + if (ioctl(s, SIOCSIFMTU, &ifr) < 0) { + perror("SIOCSIFMTU"); + close(s); + return -1; + } + close(s); + + return 0; +} + +static int get_address(char *dev, int *htype) +{ + struct ifreq ifr; + struct sockaddr_ll me; + socklen_t alen; + int s; + + s = socket(PF_PACKET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket(PF_PACKET)"); + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { + perror("SIOCGIFINDEX"); + close(s); + return -1; + } + + memset(&me, 0, sizeof(me)); + me.sll_family = AF_PACKET; + me.sll_ifindex = ifr.ifr_ifindex; + me.sll_protocol = htons(ETH_P_LOOP); + if (bind(s, (struct sockaddr*)&me, sizeof(me)) == -1) { + perror("bind"); + close(s); + return -1; + } + + alen = sizeof(me); + if (getsockname(s, (struct sockaddr*)&me, &alen) == -1) { + perror("getsockname"); + close(s); + return -1; + } + close(s); + *htype = me.sll_hatype; + return me.sll_halen; +} + +static int parse_address(char *dev, int hatype, int halen, char *lla, struct ifreq *ifr) +{ + int alen; + + memset(ifr, 0, sizeof(*ifr)); + strncpy(ifr->ifr_name, dev, sizeof(ifr->ifr_name)); + ifr->ifr_hwaddr.sa_family = hatype; + alen = ll_addr_a2n((unsigned char *)(ifr->ifr_hwaddr.sa_data), 14, lla); + if (alen < 0) + return -1; + if (alen != halen) { + bb_error_msg("wrong address (%s) length: expected %d bytes", lla, halen); + return -1; + } + return 0; +} + +static int set_address(struct ifreq *ifr, int brd) +{ + int s; + + s = get_ctl_fd(); + if (s < 0) + return -1; + if (ioctl(s, brd?SIOCSIFHWBROADCAST:SIOCSIFHWADDR, ifr) < 0) { + perror(brd?"SIOCSIFHWBROADCAST":"SIOCSIFHWADDR"); + close(s); + return -1; + } + close(s); + return 0; +} + + +static int do_set(int argc, char **argv) +{ + char *dev = NULL; + __u32 mask = 0; + __u32 flags = 0; + int qlen = -1; + int mtu = -1; + char *newaddr = NULL; + char *newbrd = NULL; + struct ifreq ifr0, ifr1; + char *newname = NULL; + int htype, halen; + + while (argc > 0) { + if (strcmp(*argv, "up") == 0) { + mask |= IFF_UP; + flags |= IFF_UP; + } else if (strcmp(*argv, "down") == 0) { + mask |= IFF_UP; + flags &= ~IFF_UP; + } else if (strcmp(*argv, "name") == 0) { + NEXT_ARG(); + newname = *argv; + } else if (strcmp(*argv, "mtu") == 0) { + NEXT_ARG(); + if (mtu != -1) + duparg("mtu", *argv); + if (get_integer(&mtu, *argv, 0)) + invarg(*argv, "mtu"); + } else if (strcmp(*argv, "multicast") == 0) { + NEXT_ARG(); + mask |= IFF_MULTICAST; + if (strcmp(*argv, "on") == 0) { + flags |= IFF_MULTICAST; + } else if (strcmp(*argv, "off") == 0) { + flags &= ~IFF_MULTICAST; + } else + return on_off("multicast"); + } else if (strcmp(*argv, "arp") == 0) { + NEXT_ARG(); + mask |= IFF_NOARP; + if (strcmp(*argv, "on") == 0) { + flags &= ~IFF_NOARP; + } else if (strcmp(*argv, "off") == 0) { + flags |= IFF_NOARP; + } else + return on_off("noarp"); + } else if (strcmp(*argv, "addr") == 0) { + NEXT_ARG(); + newaddr = *argv; + } else { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + } + if (dev) + duparg2("dev", *argv); + dev = *argv; + } + argc--; argv++; + } + + if (!dev) { + bb_error_msg(bb_msg_requires_arg, "\"dev\""); + exit(-1); + } + + if (newaddr || newbrd) { + halen = get_address(dev, &htype); + if (halen < 0) + return -1; + if (newaddr) { + if (parse_address(dev, htype, halen, newaddr, &ifr0) < 0) + return -1; + } + if (newbrd) { + if (parse_address(dev, htype, halen, newbrd, &ifr1) < 0) + return -1; + } + } + + if (newname && strcmp(dev, newname)) { + if (do_changename(dev, newname) < 0) + return -1; + dev = newname; + } + if (qlen != -1) { + if (set_qlen(dev, qlen) < 0) + return -1; + } + if (mtu != -1) { + if (set_mtu(dev, mtu) < 0) + return -1; + } + if (newaddr || newbrd) { + if (newbrd) { + if (set_address(&ifr1, 1) < 0) + return -1; + } + if (newaddr) { + if (set_address(&ifr0, 0) < 0) + return -1; + } + } + if (mask) + return do_chflags(dev, flags, mask); + return 0; +} + +static int ipaddr_list_link(int argc, char **argv) +{ + preferred_family = AF_PACKET; + return ipaddr_list_or_flush(argc, argv, 0); +} + +int do_iplink(int argc, char **argv) +{ + if (argc > 0) { + if (matches(*argv, "set") == 0) + return do_set(argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return ipaddr_list_link(argc-1, argv+1); + } else + return ipaddr_list_link(0, NULL); + + bb_error_msg("command \"%s\" is unknown", *argv); + exit(-1); +} diff --git a/networking/libiproute/iproute.c b/networking/libiproute/iproute.c new file mode 100644 index 000000000..9c3b87040 --- /dev/null +++ b/networking/libiproute/iproute.c @@ -0,0 +1,858 @@ +/* vi: set sw=4 ts=4: */ +/* + * iproute.c "ip route". + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + * Kunihiro Ishiguro 001102: rtnh_ifindex was not initialized + */ + +#include "libbb.h" + +#include + +#include +#include +#include + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + +#ifndef RTAX_RTTVAR +#define RTAX_RTTVAR RTAX_HOPS +#endif + + +static struct +{ + int tb; + int flushed; + char *flushb; + int flushp; + int flushe; + struct rtnl_handle *rth; + int protocol, protocolmask; + int scope, scopemask; + int type, typemask; + int tos, tosmask; + int iif, iifmask; + int oif, oifmask; + int realm, realmmask; + inet_prefix rprefsrc; + inet_prefix rvia; + inet_prefix rdst; + inet_prefix mdst; + inet_prefix rsrc; + inet_prefix msrc; +} filter; + +static int flush_update(void) +{ + if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) { + perror("Failed to send flush request\n"); + return -1; + } + filter.flushp = 0; + return 0; +} + +static int print_route(struct sockaddr_nl *who ATTRIBUTE_UNUSED, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = (FILE*)arg; + struct rtmsg *r = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr * tb[RTA_MAX+1]; + char abuf[256]; + inet_prefix dst; + inet_prefix src; + int host_len = -1; + SPRINT_BUF(b1); + + + if (n->nlmsg_type != RTM_NEWROUTE && n->nlmsg_type != RTM_DELROUTE) { + fprintf(stderr, "Not a route: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + return 0; + } + if (filter.flushb && n->nlmsg_type != RTM_NEWROUTE) + return 0; + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + bb_error_msg("wrong nlmsg len %d", len); + return -1; + } + + if (r->rtm_family == AF_INET6) + host_len = 128; + else if (r->rtm_family == AF_INET) + host_len = 32; + + if (r->rtm_family == AF_INET6) { + if (filter.tb) { + if (filter.tb < 0) { + if (!(r->rtm_flags&RTM_F_CLONED)) { + return 0; + } + } else { + if (r->rtm_flags&RTM_F_CLONED) { + return 0; + } + if (filter.tb == RT_TABLE_LOCAL) { + if (r->rtm_type != RTN_LOCAL) { + return 0; + } + } else if (filter.tb == RT_TABLE_MAIN) { + if (r->rtm_type == RTN_LOCAL) { + return 0; + } + } else { + return 0; + } + } + } + } else { + if (filter.tb > 0 && filter.tb != r->rtm_table) { + return 0; + } + } + if (filter.rdst.family && + (r->rtm_family != filter.rdst.family || filter.rdst.bitlen > r->rtm_dst_len)) { + return 0; + } + if (filter.mdst.family && + (r->rtm_family != filter.mdst.family || + (filter.mdst.bitlen >= 0 && filter.mdst.bitlen < r->rtm_dst_len))) { + return 0; + } + if (filter.rsrc.family && + (r->rtm_family != filter.rsrc.family || filter.rsrc.bitlen > r->rtm_src_len)) { + return 0; + } + if (filter.msrc.family && + (r->rtm_family != filter.msrc.family || + (filter.msrc.bitlen >= 0 && filter.msrc.bitlen < r->rtm_src_len))) { + return 0; + } + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); + + if (filter.rdst.family && inet_addr_match(&dst, &filter.rdst, filter.rdst.bitlen)) + return 0; + if (filter.mdst.family && filter.mdst.bitlen >= 0 && + inet_addr_match(&dst, &filter.mdst, r->rtm_dst_len)) + return 0; + + if (filter.rsrc.family && inet_addr_match(&src, &filter.rsrc, filter.rsrc.bitlen)) + return 0; + if (filter.msrc.family && filter.msrc.bitlen >= 0 && + inet_addr_match(&src, &filter.msrc, r->rtm_src_len)) + return 0; + + if (filter.flushb && + r->rtm_family == AF_INET6 && + r->rtm_dst_len == 0 && + r->rtm_type == RTN_UNREACHABLE && + tb[RTA_PRIORITY] && + *(int*)RTA_DATA(tb[RTA_PRIORITY]) == -1) + return 0; + + if (filter.flushb) { + struct nlmsghdr *fn; + if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) { + if (flush_update()) + return -1; + } + fn = (struct nlmsghdr*)(filter.flushb + NLMSG_ALIGN(filter.flushp)); + memcpy(fn, n, n->nlmsg_len); + fn->nlmsg_type = RTM_DELROUTE; + fn->nlmsg_flags = NLM_F_REQUEST; + fn->nlmsg_seq = ++filter.rth->seq; + filter.flushp = (((char*)fn) + n->nlmsg_len) - filter.flushb; + filter.flushed++; + return 0; + } + + if (n->nlmsg_type == RTM_DELROUTE) { + fprintf(fp, "Deleted "); + } + if (r->rtm_type != RTN_UNICAST && !filter.type) { + fprintf(fp, "%s ", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1))); + } + + if (tb[RTA_DST]) { + if (r->rtm_dst_len != host_len) { + fprintf(fp, "%s/%u ", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf)), + r->rtm_dst_len + ); + } else { + fprintf(fp, "%s ", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf)) + ); + } + } else if (r->rtm_dst_len) { + fprintf(fp, "0/%d ", r->rtm_dst_len); + } else { + fprintf(fp, "default "); + } + if (tb[RTA_SRC]) { + if (r->rtm_src_len != host_len) { + fprintf(fp, "from %s/%u ", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)), + r->rtm_src_len + ); + } else { + fprintf(fp, "from %s ", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)) + ); + } + } else if (r->rtm_src_len) { + fprintf(fp, "from 0/%u ", r->rtm_src_len); + } + if (tb[RTA_GATEWAY] && filter.rvia.bitlen != host_len) { + fprintf(fp, "via %s ", + format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_GATEWAY]), + RTA_DATA(tb[RTA_GATEWAY]), + abuf, sizeof(abuf))); + } + if (tb[RTA_OIF] && filter.oifmask != -1) { + fprintf(fp, "dev %s ", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_OIF]))); + } + + if (tb[RTA_PREFSRC] && filter.rprefsrc.bitlen != host_len) { + /* Do not use format_host(). It is our local addr + and symbolic name will not be useful. + */ + fprintf(fp, " src %s ", + rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_PREFSRC]), + RTA_DATA(tb[RTA_PREFSRC]), + abuf, sizeof(abuf))); + } + if (tb[RTA_PRIORITY]) { + fprintf(fp, " metric %d ", *(__u32*)RTA_DATA(tb[RTA_PRIORITY])); + } + if (r->rtm_family == AF_INET6) { + struct rta_cacheinfo *ci = NULL; + if (tb[RTA_CACHEINFO]) { + ci = RTA_DATA(tb[RTA_CACHEINFO]); + } + if ((r->rtm_flags & RTM_F_CLONED) || (ci && ci->rta_expires)) { + static int hz; + if (!hz) { + hz = get_hz(); + } + if (r->rtm_flags & RTM_F_CLONED) { + fprintf(fp, "%s cache ", _SL_); + } + if (ci->rta_expires) { + fprintf(fp, " expires %dsec", ci->rta_expires/hz); + } + if (ci->rta_error != 0) { + fprintf(fp, " error %d", ci->rta_error); + } + } else if (ci) { + if (ci->rta_error != 0) + fprintf(fp, " error %d", ci->rta_error); + } + } + if (tb[RTA_IIF] && filter.iifmask != -1) { + fprintf(fp, " iif %s", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_IIF]))); + } + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + +static int iproute_modify(int cmd, unsigned flags, int argc, char **argv) +{ + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + char mxbuf[256]; + struct rtattr * mxrta = (void*)mxbuf; + unsigned mxlock = 0; + char *d = NULL; + int gw_ok = 0; + int dst_ok = 0; + int proto_ok = 0; + int type_ok = 0; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST|flags; + req.n.nlmsg_type = cmd; + req.r.rtm_family = preferred_family; + req.r.rtm_table = RT_TABLE_MAIN; + req.r.rtm_scope = RT_SCOPE_NOWHERE; + + if (cmd != RTM_DELROUTE) { + req.r.rtm_protocol = RTPROT_BOOT; + req.r.rtm_scope = RT_SCOPE_UNIVERSE; + req.r.rtm_type = RTN_UNICAST; + } + + mxrta->rta_type = RTA_METRICS; + mxrta->rta_len = RTA_LENGTH(0); + + while (argc > 0) { + if (strcmp(*argv, "src") == 0) { + inet_prefix addr; + NEXT_ARG(); + get_addr(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + addattr_l(&req.n, sizeof(req), RTA_PREFSRC, &addr.data, addr.bytelen); + } else if (strcmp(*argv, "via") == 0) { + inet_prefix addr; + gw_ok = 1; + NEXT_ARG(); + get_addr(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + addattr_l(&req.n, sizeof(req), RTA_GATEWAY, &addr.data, addr.bytelen); + } else if (strcmp(*argv, "mtu") == 0) { + unsigned mtu; + NEXT_ARG(); + if (strcmp(*argv, "lock") == 0) { + mxlock |= (1< '9') && + rtnl_rtntype_a2n(&type, *argv) == 0) { + NEXT_ARG(); + req.r.rtm_type = type; + type_ok = 1; + } + + if (dst_ok) { + duparg2("to", *argv); + } + get_prefix(&dst, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = dst.family; + } + req.r.rtm_dst_len = dst.bitlen; + dst_ok = 1; + if (dst.bytelen) { + addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen); + } + } + argc--; argv++; + } + + if (rtnl_open(&rth, 0) < 0) { + exit(1); + } + + if (d) { + int idx; + + ll_init_map(&rth); + + if (d) { + if ((idx = ll_name_to_index(d)) == 0) { + bb_error_msg("cannot find device \"%s\"", d); + return -1; + } + addattr32(&req.n, sizeof(req), RTA_OIF, idx); + } + } + + if (mxrta->rta_len > RTA_LENGTH(0)) { + if (mxlock) { + rta_addattr32(mxrta, sizeof(mxbuf), RTAX_LOCK, mxlock); + } + addattr_l(&req.n, sizeof(req), RTA_METRICS, RTA_DATA(mxrta), RTA_PAYLOAD(mxrta)); + } + + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = AF_INET; + } + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) { + exit(2); + } + + return 0; +} + +static int rtnl_rtcache_request(struct rtnl_handle *rth, int family) +{ + struct { + struct nlmsghdr nlh; + struct rtmsg rtm; + } req; + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + memset(&req, 0, sizeof(req)); + nladdr.nl_family = AF_NETLINK; + + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = RTM_GETROUTE; + req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.rtm.rtm_family = family; + req.rtm.rtm_flags |= RTM_F_CLONED; + + return sendto(rth->fd, (void*)&req, sizeof(req), 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +static int iproute_flush_cache(void) +{ +#define ROUTE_FLUSH_PATH "/proc/sys/net/ipv4/route/flush" + + int len; + int flush_fd = open (ROUTE_FLUSH_PATH, O_WRONLY); + char *buffer = "-1"; + + if (flush_fd < 0) { + fprintf(stderr, "Cannot open \"%s\"\n", ROUTE_FLUSH_PATH); + return -1; + } + + len = strlen (buffer); + + if ((write (flush_fd, (void *)buffer, len)) < len) { + fprintf(stderr, "Cannot flush routing cache\n"); + return -1; + } + close(flush_fd); + return 0; +} + +static void iproute_reset_filter(void) +{ + memset(&filter, 0, sizeof(filter)); + filter.mdst.bitlen = -1; + filter.msrc.bitlen = -1; +} + +static int iproute_list_or_flush(int argc, char **argv, int flush) +{ + int do_ipv6 = preferred_family; + struct rtnl_handle rth; + char *id = NULL; + char *od = NULL; + + iproute_reset_filter(); + filter.tb = RT_TABLE_MAIN; + + if (flush && argc <= 0) { + bb_error_msg(bb_msg_requires_arg, "\"ip route flush\""); + return -1; + } + + while (argc > 0) { + if (matches(*argv, "protocol") == 0) { + uint32_t prot = 0; + NEXT_ARG(); + filter.protocolmask = -1; + if (rtnl_rtprot_a2n(&prot, *argv)) { + if (strcmp(*argv, "all") != 0) { + invarg(*argv, "protocol"); + } + prot = 0; + filter.protocolmask = 0; + } + filter.protocol = prot; + } else if (strcmp(*argv, "dev") == 0 || + strcmp(*argv, "oif") == 0) { + NEXT_ARG(); + od = *argv; + } else if (strcmp(*argv, "iif") == 0) { + NEXT_ARG(); + id = *argv; + } else if (matches(*argv, "from") == 0) { + NEXT_ARG(); + if (matches(*argv, "root") == 0) { + NEXT_ARG(); + get_prefix(&filter.rsrc, *argv, do_ipv6); + } else if (matches(*argv, "match") == 0) { + NEXT_ARG(); + get_prefix(&filter.msrc, *argv, do_ipv6); + } else { + if (matches(*argv, "exact") == 0) { + NEXT_ARG(); + } + get_prefix(&filter.msrc, *argv, do_ipv6); + filter.rsrc = filter.msrc; + } + } else { + if (matches(*argv, "to") == 0) { + NEXT_ARG(); + } + if (matches(*argv, "root") == 0) { + NEXT_ARG(); + get_prefix(&filter.rdst, *argv, do_ipv6); + } else if (matches(*argv, "match") == 0) { + NEXT_ARG(); + get_prefix(&filter.mdst, *argv, do_ipv6); + } else if (matches(*argv, "table") == 0) { + NEXT_ARG(); + if (matches(*argv, "cache") == 0) { + filter.tb = -1; + } else if (matches(*argv, "main") != 0) { + invarg(*argv, "table"); + } + } else if (matches(*argv, "cache") == 0) { + filter.tb = -1; + } else { + if (matches(*argv, "exact") == 0) { + NEXT_ARG(); + } + get_prefix(&filter.mdst, *argv, do_ipv6); + filter.rdst = filter.mdst; + } + } + argc--; argv++; + } + + if (do_ipv6 == AF_UNSPEC && filter.tb) { + do_ipv6 = AF_INET; + } + + if (rtnl_open(&rth, 0) < 0) { + exit(1); + } + + ll_init_map(&rth); + + if (id || od) { + int idx; + + if (id) { + if ((idx = ll_name_to_index(id)) == 0) { + bb_error_msg("cannot find device \"%s\"", id); + return -1; + } + filter.iif = idx; + filter.iifmask = -1; + } + if (od) { + if ((idx = ll_name_to_index(od)) == 0) { + bb_error_msg("cannot find device \"%s\"", od); + } + filter.oif = idx; + filter.oifmask = -1; + } + } + + if (flush) { + char flushb[4096-512]; + + if (filter.tb == -1) { + if (do_ipv6 != AF_INET6) + iproute_flush_cache(); + if (do_ipv6 == AF_INET) + return 0; + } + + filter.flushb = flushb; + filter.flushp = 0; + filter.flushe = sizeof(flushb); + filter.rth = &rth; + + for (;;) { + if (rtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE) < 0) { + perror("Cannot send dump request"); + return -1; + } + filter.flushed = 0; + if (rtnl_dump_filter(&rth, print_route, stdout, NULL, NULL) < 0) { + bb_error_msg("flush terminated"); + return -1; + } + if (filter.flushed == 0) { + fflush(stdout); + return 0; + } + if (flush_update() < 0) + exit(1); + } + } + + if (filter.tb != -1) { + if (rtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + } else { + if (rtnl_rtcache_request(&rth, do_ipv6) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + } + + if (rtnl_dump_filter(&rth, print_route, stdout, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + + exit(0); +} + + +static int iproute_get(int argc, char **argv) +{ + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + char *idev = NULL; + char *odev = NULL; + int connected = 0; + int from_ok = 0; + static const char * const options[] = + { "from", "iif", "oif", "dev", "notify", "connected", "to", 0 }; + + memset(&req, 0, sizeof(req)); + + iproute_reset_filter(); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_GETROUTE; + req.r.rtm_family = preferred_family; + req.r.rtm_table = 0; + req.r.rtm_protocol = 0; + req.r.rtm_scope = 0; + req.r.rtm_type = 0; + req.r.rtm_src_len = 0; + req.r.rtm_dst_len = 0; + req.r.rtm_tos = 0; + + while (argc > 0) { + switch (index_in_str_array(options, *argv)) { + case 0: /* from */ + { + inet_prefix addr; + NEXT_ARG(); + from_ok = 1; + get_prefix(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + if (addr.bytelen) { + addattr_l(&req.n, sizeof(req), RTA_SRC, &addr.data, addr.bytelen); + } + req.r.rtm_src_len = addr.bitlen; + break; + } + case 1: /* iif */ + NEXT_ARG(); + idev = *argv; + break; + case 2: /* oif */ + case 3: /* dev */ + NEXT_ARG(); + odev = *argv; + break; + case 4: /* notify */ + req.r.rtm_flags |= RTM_F_NOTIFY; + break; + case 5: /* connected */ + connected = 1; + break; + case 6: /* to */ + NEXT_ARG(); + default: + { + inet_prefix addr; + get_prefix(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + if (addr.bytelen) { + addattr_l(&req.n, sizeof(req), RTA_DST, &addr.data, addr.bytelen); + } + req.r.rtm_dst_len = addr.bitlen; + } + argc--; argv++; + } + } + + if (req.r.rtm_dst_len == 0) { + bb_error_msg_and_die("need at least destination address"); + } + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + ll_init_map(&rth); + + if (idev || odev) { + int idx; + + if (idev) { + if ((idx = ll_name_to_index(idev)) == 0) { + bb_error_msg("cannot find device \"%s\"", idev); + return -1; + } + addattr32(&req.n, sizeof(req), RTA_IIF, idx); + } + if (odev) { + if ((idx = ll_name_to_index(odev)) == 0) { + bb_error_msg("cannot find device \"%s\"", odev); + return -1; + } + addattr32(&req.n, sizeof(req), RTA_OIF, idx); + } + } + + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = AF_INET; + } + + if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) { + exit(2); + } + + if (connected && !from_ok) { + struct rtmsg *r = NLMSG_DATA(&req.n); + int len = req.n.nlmsg_len; + struct rtattr * tb[RTA_MAX+1]; + + if (print_route(NULL, &req.n, (void*)stdout) < 0) { + bb_error_msg_and_die("an error :-)"); + } + + if (req.n.nlmsg_type != RTM_NEWROUTE) { + bb_error_msg("not a route?"); + return -1; + } + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + bb_error_msg("wrong len %d", len); + return -1; + } + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); + + if (tb[RTA_PREFSRC]) { + tb[RTA_PREFSRC]->rta_type = RTA_SRC; + r->rtm_src_len = 8*RTA_PAYLOAD(tb[RTA_PREFSRC]); + } else if (!tb[RTA_SRC]) { + bb_error_msg("failed to connect the route"); + return -1; + } + if (!odev && tb[RTA_OIF]) { + tb[RTA_OIF]->rta_type = 0; + } + if (tb[RTA_GATEWAY]) { + tb[RTA_GATEWAY]->rta_type = 0; + } + if (!idev && tb[RTA_IIF]) { + tb[RTA_IIF]->rta_type = 0; + } + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_GETROUTE; + + if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) { + exit(2); + } + } + + if (print_route(NULL, &req.n, (void*)stdout) < 0) { + bb_error_msg_and_die("an error :-)"); + } + + exit(0); +} + +int do_iproute(int argc, char **argv) +{ + static const char * const ip_route_commands[] = + { "add", "append", "change", "chg", "delete", "get", + "list", "show", "prepend", "replace", "test", "flush", 0 }; + int command_num = 6; + unsigned int flags = 0; + int cmd = RTM_NEWROUTE; + + /* "Standard" 'ip r a' treats 'a' as 'add', not 'append' */ + /* It probably means that it is using "first match" rule */ + if (*argv) { + command_num = index_in_substr_array(ip_route_commands, *argv); + } + switch (command_num) { + case 0: /* add*/ + flags = NLM_F_CREATE|NLM_F_EXCL; + break; + case 1: /* append */ + flags = NLM_F_CREATE|NLM_F_APPEND; + break; + case 2: /* change */ + case 3: /* chg */ + flags = NLM_F_REPLACE; + break; + case 4: /* delete */ + case 5: /* del */ + cmd = RTM_DELROUTE; + break; + case 6: /* get */ + return iproute_get(argc-1, argv+1); + case 7: /* list */ + case 8: /* show */ + return iproute_list_or_flush(argc-1, argv+1, 0); + case 9: /* prepend */ + flags = NLM_F_CREATE; + case 10: /* replace */ + flags = NLM_F_CREATE|NLM_F_REPLACE; + case 11: /* test */ + flags = NLM_F_EXCL; + case 12: /* flush */ + return iproute_list_or_flush(argc-1, argv+1, 1); + default: + bb_error_msg_and_die("unknown command %s", *argv); + } + + return iproute_modify(cmd, flags, argc-1, argv+1); +} diff --git a/networking/libiproute/iprule.c b/networking/libiproute/iprule.c new file mode 100644 index 000000000..19338770f --- /dev/null +++ b/networking/libiproute/iprule.c @@ -0,0 +1,331 @@ +/* vi: set sw=4 ts=4: */ +/* + * iprule.c "ip rule". + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libbb.h" +#include +#include +#include +#include +#include + +#include "rt_names.h" +#include "utils.h" +/* +static void usage(void) __attribute__((noreturn)); + +static void usage(void) +{ + fprintf(stderr, "Usage: ip rule [ list | add | del ] SELECTOR ACTION\n"); + fprintf(stderr, "SELECTOR := [ from PREFIX ] [ to PREFIX ] [ tos TOS ] [ fwmark FWMARK ]\n"); + fprintf(stderr, " [ dev STRING ] [ pref NUMBER ]\n"); + fprintf(stderr, "ACTION := [ table TABLE_ID ] [ nat ADDRESS ]\n"); + fprintf(stderr, " [ prohibit | reject | unreachable ]\n"); + fprintf(stderr, " [ realms [SRCREALM/]DSTREALM ]\n"); + fprintf(stderr, "TABLE_ID := [ local | main | default | NUMBER ]\n"); + exit(-1); +} +*/ +static int print_rule(struct sockaddr_nl *who ATTRIBUTE_UNUSED, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = (FILE*)arg; + struct rtmsg *r = NLMSG_DATA(n); + int len = n->nlmsg_len; + int host_len = -1; + struct rtattr * tb[RTA_MAX+1]; + char abuf[256]; + SPRINT_BUF(b1); + + if (n->nlmsg_type != RTM_NEWRULE) + return 0; + + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) + return -1; + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); + + if (r->rtm_family == AF_INET) + host_len = 32; + else if (r->rtm_family == AF_INET6) + host_len = 128; +/* else if (r->rtm_family == AF_DECnet) + host_len = 16; + else if (r->rtm_family == AF_IPX) + host_len = 80; +*/ + if (tb[RTA_PRIORITY]) + fprintf(fp, "%u:\t", *(unsigned*)RTA_DATA(tb[RTA_PRIORITY])); + else + fprintf(fp, "0:\t"); + + fprintf(fp, "from "); + if (tb[RTA_SRC]) { + if (r->rtm_src_len != host_len) { + fprintf(fp, "%s/%u", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)), + r->rtm_src_len + ); + } else { + fprintf(fp, "%s", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)) + ); + } + } else if (r->rtm_src_len) { + fprintf(fp, "0/%d", r->rtm_src_len); + } else { + fprintf(fp, "all"); + } + fprintf(fp, " "); + + if (tb[RTA_DST]) { + if (r->rtm_dst_len != host_len) { + fprintf(fp, "to %s/%u ", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf)), + r->rtm_dst_len + ); + } else { + fprintf(fp, "to %s ", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf))); + } + } else if (r->rtm_dst_len) { + fprintf(fp, "to 0/%d ", r->rtm_dst_len); + } + + if (r->rtm_tos) { + fprintf(fp, "tos %s ", rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1))); + } + if (tb[RTA_PROTOINFO]) { + fprintf(fp, "fwmark %#x ", *(__u32*)RTA_DATA(tb[RTA_PROTOINFO])); + } + + if (tb[RTA_IIF]) { + fprintf(fp, "iif %s ", (char*)RTA_DATA(tb[RTA_IIF])); + } + + if (r->rtm_table) + fprintf(fp, "lookup %s ", rtnl_rttable_n2a(r->rtm_table, b1, sizeof(b1))); + + if (tb[RTA_FLOW]) { + __u32 to = *(__u32*)RTA_DATA(tb[RTA_FLOW]); + __u32 from = to>>16; + to &= 0xFFFF; + if (from) { + fprintf(fp, "realms %s/", + rtnl_rtrealm_n2a(from, b1, sizeof(b1))); + } + fprintf(fp, "%s ", + rtnl_rtrealm_n2a(to, b1, sizeof(b1))); + } + + if (r->rtm_type == RTN_NAT) { + if (tb[RTA_GATEWAY]) { + fprintf(fp, "map-to %s ", + format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_GATEWAY]), + RTA_DATA(tb[RTA_GATEWAY]), + abuf, sizeof(abuf))); + } else + fprintf(fp, "masquerade"); + } else if (r->rtm_type != RTN_UNICAST) + fprintf(fp, "%s", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1))); + + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + +int iprule_list(int argc, char **argv) +{ + struct rtnl_handle rth; + int af = preferred_family; + + if (af == AF_UNSPEC) + af = AF_INET; + + if (argc > 0) { + bb_error_msg("\"rule show\" needs no arguments"); + return -1; + } + + if (rtnl_open(&rth, 0) < 0) + return 1; + + if (rtnl_wilddump_request(&rth, af, RTM_GETRULE) < 0) { + bb_perror_msg("Cannot send dump request"); + return 1; + } + + if (rtnl_dump_filter(&rth, print_rule, stdout, NULL, NULL) < 0) { + bb_error_msg("Dump terminated"); + return 1; + } + + return 0; +} + + +int iprule_modify(int cmd, int argc, char **argv) +{ + int table_ok = 0; + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_type = cmd; + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.r.rtm_family = preferred_family; + req.r.rtm_protocol = RTPROT_BOOT; + req.r.rtm_scope = RT_SCOPE_UNIVERSE; + req.r.rtm_table = 0; + req.r.rtm_type = RTN_UNSPEC; + + if (cmd == RTM_NEWRULE) { + req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_EXCL; + req.r.rtm_type = RTN_UNICAST; + } + + while (argc > 0) { + if (strcmp(*argv, "from") == 0) { + inet_prefix dst; + NEXT_ARG(); + get_prefix(&dst, *argv, req.r.rtm_family); + req.r.rtm_src_len = dst.bitlen; + addattr_l(&req.n, sizeof(req), RTA_SRC, &dst.data, dst.bytelen); + } else if (strcmp(*argv, "to") == 0) { + inet_prefix dst; + NEXT_ARG(); + get_prefix(&dst, *argv, req.r.rtm_family); + req.r.rtm_dst_len = dst.bitlen; + addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen); + } else if (matches(*argv, "preference") == 0 || + matches(*argv, "order") == 0 || + matches(*argv, "priority") == 0) { + __u32 pref; + NEXT_ARG(); + if (get_u32(&pref, *argv, 0)) + invarg("preference value", *argv); + addattr32(&req.n, sizeof(req), RTA_PRIORITY, pref); + } else if (strcmp(*argv, "tos") == 0) { + __u32 tos; + NEXT_ARG(); + if (rtnl_dsfield_a2n(&tos, *argv)) + invarg("TOS value", *argv); + req.r.rtm_tos = tos; + } else if (strcmp(*argv, "fwmark") == 0) { + __u32 fwmark; + NEXT_ARG(); + if (get_u32(&fwmark, *argv, 0)) + invarg("fwmark value", *argv); + addattr32(&req.n, sizeof(req), RTA_PROTOINFO, fwmark); + } else if (matches(*argv, "realms") == 0) { + __u32 realm; + NEXT_ARG(); + if (get_rt_realms(&realm, *argv)) + invarg("realms", *argv); + addattr32(&req.n, sizeof(req), RTA_FLOW, realm); + } else if (matches(*argv, "table") == 0 || + strcmp(*argv, "lookup") == 0) { + int tid; + NEXT_ARG(); + if (rtnl_rttable_a2n(&tid, *argv)) + invarg("table ID", *argv); + req.r.rtm_table = tid; + table_ok = 1; + } else if (strcmp(*argv, "dev") == 0 || + strcmp(*argv, "iif") == 0) { + NEXT_ARG(); + addattr_l(&req.n, sizeof(req), RTA_IIF, *argv, strlen(*argv)+1); + } else if (strcmp(*argv, "nat") == 0 || + matches(*argv, "map-to") == 0) { + NEXT_ARG(); + addattr32(&req.n, sizeof(req), RTA_GATEWAY, get_addr32(*argv)); + req.r.rtm_type = RTN_NAT; + } else { + int type; + + if (strcmp(*argv, "type") == 0) { + NEXT_ARG(); + } + if (matches(*argv, "help") == 0) + bb_show_usage(); + if (rtnl_rtntype_a2n(&type, *argv)) + invarg("Failed to parse rule type", *argv); + req.r.rtm_type = type; + } + argc--; + argv++; + } + + if (req.r.rtm_family == AF_UNSPEC) + req.r.rtm_family = AF_INET; + + if (!table_ok && cmd == RTM_NEWRULE) + req.r.rtm_table = RT_TABLE_MAIN; + + if (rtnl_open(&rth, 0) < 0) + return 1; + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) + return 2; + + return 0; +} + +int do_iprule(int argc, char **argv) +{ + static const char * const ip_rule_commands[] = + {"add", "delete", "list", "show", 0}; + int command_num = 2; + int cmd; + + if (argc < 1) + return iprule_list(0, NULL); + if (*argv) + command_num = index_in_substr_array(ip_rule_commands, *argv); + switch (command_num) { + case 0: /* add */ + cmd = RTM_NEWRULE; + break; + case 1: /* delete */ + cmd = RTM_DELRULE; + break; + case 2: /* list */ + case 3: /* show */ + return iprule_list(argc-1, argv+1); + break; + default: + bb_error_msg_and_die("unknown command %s", *argv); + } + return iprule_modify(cmd, argc-1, argv+1); +} + diff --git a/networking/libiproute/iptunnel.c b/networking/libiproute/iptunnel.c new file mode 100644 index 000000000..2080324ac --- /dev/null +++ b/networking/libiproute/iptunnel.c @@ -0,0 +1,546 @@ +/* vi: set sw=4 ts=4: */ +/* + * iptunnel.c "ip tunnel" + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + * Rani Assaf 980930: do not allow key for ipip/sit + * Phil Karn 990408: "pmtudisc" flag + */ + +#include "libbb.h" +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#ifndef __constant_htons +#define __constant_htons htons +#endif +#include + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + + +static int do_ioctl_get_ifindex(char *dev) +{ + struct ifreq ifr; + int fd; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + if (ioctl(fd, SIOCGIFINDEX, &ifr)) { + bb_perror_msg("ioctl"); + return 0; + } + close(fd); + return ifr.ifr_ifindex; +} + +static int do_ioctl_get_iftype(char *dev) +{ + struct ifreq ifr; + int fd; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + if (ioctl(fd, SIOCGIFHWADDR, &ifr)) { + bb_perror_msg("ioctl"); + return -1; + } + close(fd); + return ifr.ifr_addr.sa_family; +} + + +static char *do_ioctl_get_ifname(int idx) +{ + static struct ifreq ifr; + int fd; + + ifr.ifr_ifindex = idx; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + if (ioctl(fd, SIOCGIFNAME, &ifr)) { + bb_perror_msg("ioctl"); + return NULL; + } + close(fd); + return ifr.ifr_name; +} + + + +static int do_get_ioctl(char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + int err; + + strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name)); + ifr.ifr_ifru.ifru_data = (void*)p; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl(fd, SIOCGETTUNNEL, &ifr); + if (err) { + bb_perror_msg("ioctl"); + } + close(fd); + return err; +} + +static int do_add_ioctl(int cmd, char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + int err; + + if (cmd == SIOCCHGTUNNEL && p->name[0]) { + strncpy(ifr.ifr_name, p->name, sizeof(ifr.ifr_name)); + } else { + strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name)); + } + ifr.ifr_ifru.ifru_data = (void*)p; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl(fd, cmd, &ifr); + if (err) { + bb_perror_msg("ioctl"); + } + close(fd); + return err; +} + +static int do_del_ioctl(char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + int err; + + if (p->name[0]) { + strncpy(ifr.ifr_name, p->name, sizeof(ifr.ifr_name)); + } else { + strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name)); + } + ifr.ifr_ifru.ifru_data = (void*)p; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl(fd, SIOCDELTUNNEL, &ifr); + if (err) { + bb_perror_msg("ioctl"); + } + close(fd); + return err; +} + +static int parse_args(int argc, char **argv, int cmd, struct ip_tunnel_parm *p) +{ + int count = 0; + char medium[IFNAMSIZ]; + memset(p, 0, sizeof(*p)); + memset(&medium, 0, sizeof(medium)); + + p->iph.version = 4; + p->iph.ihl = 5; +#ifndef IP_DF +#define IP_DF 0x4000 /* Flag: "Don't Fragment" */ +#endif + p->iph.frag_off = htons(IP_DF); + + while (argc > 0) { + if (strcmp(*argv, "mode") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "ipip") == 0 || + strcmp(*argv, "ip/ip") == 0) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_IPIP) { + bb_error_msg("you managed to ask for more than one tunnel mode"); + exit(-1); + } + p->iph.protocol = IPPROTO_IPIP; + } else if (strcmp(*argv, "gre") == 0 || + strcmp(*argv, "gre/ip") == 0) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_GRE) { + bb_error_msg("you managed to ask for more than one tunnel mode"); + exit(-1); + } + p->iph.protocol = IPPROTO_GRE; + } else if (strcmp(*argv, "sit") == 0 || + strcmp(*argv, "ipv6/ip") == 0) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_IPV6) { + bb_error_msg("you managed to ask for more than one tunnel mode"); + exit(-1); + } + p->iph.protocol = IPPROTO_IPV6; + } else { + bb_error_msg("cannot guess tunnel mode"); + exit(-1); + } + } else if (strcmp(*argv, "key") == 0) { + unsigned uval; + NEXT_ARG(); + p->i_flags |= GRE_KEY; + p->o_flags |= GRE_KEY; + if (strchr(*argv, '.')) + p->i_key = p->o_key = get_addr32(*argv); + else { + if (get_unsigned(&uval, *argv, 0)<0) { + bb_error_msg("invalid value of \"key\""); + exit(-1); + } + p->i_key = p->o_key = htonl(uval); + } + } else if (strcmp(*argv, "ikey") == 0) { + unsigned uval; + NEXT_ARG(); + p->i_flags |= GRE_KEY; + if (strchr(*argv, '.')) + p->o_key = get_addr32(*argv); + else { + if (get_unsigned(&uval, *argv, 0)<0) { + bb_error_msg("invalid value of \"ikey\""); + exit(-1); + } + p->i_key = htonl(uval); + } + } else if (strcmp(*argv, "okey") == 0) { + unsigned uval; + NEXT_ARG(); + p->o_flags |= GRE_KEY; + if (strchr(*argv, '.')) + p->o_key = get_addr32(*argv); + else { + if (get_unsigned(&uval, *argv, 0)<0) { + bb_error_msg("invalid value of \"okey\""); + exit(-1); + } + p->o_key = htonl(uval); + } + } else if (strcmp(*argv, "seq") == 0) { + p->i_flags |= GRE_SEQ; + p->o_flags |= GRE_SEQ; + } else if (strcmp(*argv, "iseq") == 0) { + p->i_flags |= GRE_SEQ; + } else if (strcmp(*argv, "oseq") == 0) { + p->o_flags |= GRE_SEQ; + } else if (strcmp(*argv, "csum") == 0) { + p->i_flags |= GRE_CSUM; + p->o_flags |= GRE_CSUM; + } else if (strcmp(*argv, "icsum") == 0) { + p->i_flags |= GRE_CSUM; + } else if (strcmp(*argv, "ocsum") == 0) { + p->o_flags |= GRE_CSUM; + } else if (strcmp(*argv, "nopmtudisc") == 0) { + p->iph.frag_off = 0; + } else if (strcmp(*argv, "pmtudisc") == 0) { + p->iph.frag_off = htons(IP_DF); + } else if (strcmp(*argv, "remote") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "any")) + p->iph.daddr = get_addr32(*argv); + } else if (strcmp(*argv, "local") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "any")) + p->iph.saddr = get_addr32(*argv); + } else if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + strncpy(medium, *argv, IFNAMSIZ-1); + } else if (strcmp(*argv, "ttl") == 0) { + unsigned uval; + NEXT_ARG(); + if (strcmp(*argv, "inherit") != 0) { + if (get_unsigned(&uval, *argv, 0)) + invarg(*argv, "TTL"); + if (uval > 255) + invarg(*argv, "TTL must be <=255"); + p->iph.ttl = uval; + } + } else if (strcmp(*argv, "tos") == 0 || + matches(*argv, "dsfield") == 0) { + __u32 uval; + NEXT_ARG(); + if (strcmp(*argv, "inherit") != 0) { + if (rtnl_dsfield_a2n(&uval, *argv)) + invarg(*argv, "TOS"); + p->iph.tos = uval; + } else + p->iph.tos = 1; + } else { + if (strcmp(*argv, "name") == 0) { + NEXT_ARG(); + } + if (p->name[0]) + duparg2("name", *argv); + strncpy(p->name, *argv, IFNAMSIZ); + if (cmd == SIOCCHGTUNNEL && count == 0) { + struct ip_tunnel_parm old_p; + memset(&old_p, 0, sizeof(old_p)); + if (do_get_ioctl(*argv, &old_p)) + return -1; + *p = old_p; + } + } + count++; + argc--; argv++; + } + + + if (p->iph.protocol == 0) { + if (memcmp(p->name, "gre", 3) == 0) + p->iph.protocol = IPPROTO_GRE; + else if (memcmp(p->name, "ipip", 4) == 0) + p->iph.protocol = IPPROTO_IPIP; + else if (memcmp(p->name, "sit", 3) == 0) + p->iph.protocol = IPPROTO_IPV6; + } + + if (p->iph.protocol == IPPROTO_IPIP || p->iph.protocol == IPPROTO_IPV6) { + if ((p->i_flags & GRE_KEY) || (p->o_flags & GRE_KEY)) { + bb_error_msg("keys are not allowed with ipip and sit"); + return -1; + } + } + + if (medium[0]) { + p->link = do_ioctl_get_ifindex(medium); + if (p->link == 0) + return -1; + } + + if (p->i_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) { + p->i_key = p->iph.daddr; + p->i_flags |= GRE_KEY; + } + if (p->o_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) { + p->o_key = p->iph.daddr; + p->o_flags |= GRE_KEY; + } + if (IN_MULTICAST(ntohl(p->iph.daddr)) && !p->iph.saddr) { + bb_error_msg("broadcast tunnel requires a source address"); + return -1; + } + return 0; +} + + +static int do_add(int cmd, int argc, char **argv) +{ + struct ip_tunnel_parm p; + + if (parse_args(argc, argv, cmd, &p) < 0) + return -1; + + if (p.iph.ttl && p.iph.frag_off == 0) { + bb_error_msg("ttl != 0 and noptmudisc are incompatible"); + return -1; + } + + switch (p.iph.protocol) { + case IPPROTO_IPIP: + return do_add_ioctl(cmd, "tunl0", &p); + case IPPROTO_GRE: + return do_add_ioctl(cmd, "gre0", &p); + case IPPROTO_IPV6: + return do_add_ioctl(cmd, "sit0", &p); + default: + bb_error_msg("cannot determine tunnel mode (ipip, gre or sit)"); + return -1; + } + return -1; +} + +static int do_del(int argc, char **argv) +{ + struct ip_tunnel_parm p; + + if (parse_args(argc, argv, SIOCDELTUNNEL, &p) < 0) + return -1; + + switch (p.iph.protocol) { + case IPPROTO_IPIP: + return do_del_ioctl("tunl0", &p); + case IPPROTO_GRE: + return do_del_ioctl("gre0", &p); + case IPPROTO_IPV6: + return do_del_ioctl("sit0", &p); + default: + return do_del_ioctl(p.name, &p); + } + return -1; +} + +static void print_tunnel(struct ip_tunnel_parm *p) +{ + char s1[256]; + char s2[256]; + char s3[64]; + char s4[64]; + + format_host(AF_INET, 4, &p->iph.daddr, s1, sizeof(s1)); + format_host(AF_INET, 4, &p->iph.saddr, s2, sizeof(s2)); + inet_ntop(AF_INET, &p->i_key, s3, sizeof(s3)); + inet_ntop(AF_INET, &p->o_key, s4, sizeof(s4)); + + printf("%s: %s/ip remote %s local %s ", + p->name, + p->iph.protocol == IPPROTO_IPIP ? "ip" : + (p->iph.protocol == IPPROTO_GRE ? "gre" : + (p->iph.protocol == IPPROTO_IPV6 ? "ipv6" : "unknown")), + p->iph.daddr ? s1 : "any", p->iph.saddr ? s2 : "any"); + if (p->link) { + char *n = do_ioctl_get_ifname(p->link); + if (n) + printf(" dev %s ", n); + } + if (p->iph.ttl) + printf(" ttl %d ", p->iph.ttl); + else + printf(" ttl inherit "); + if (p->iph.tos) { + SPRINT_BUF(b1); + printf(" tos"); + if (p->iph.tos&1) + printf(" inherit"); + if (p->iph.tos&~1) + printf("%c%s ", p->iph.tos&1 ? '/' : ' ', + rtnl_dsfield_n2a(p->iph.tos&~1, b1, sizeof(b1))); + } + if (!(p->iph.frag_off&htons(IP_DF))) + printf(" nopmtudisc"); + + if ((p->i_flags&GRE_KEY) && (p->o_flags&GRE_KEY) && p->o_key == p->i_key) + printf(" key %s", s3); + else if ((p->i_flags|p->o_flags)&GRE_KEY) { + if (p->i_flags&GRE_KEY) + printf(" ikey %s ", s3); + if (p->o_flags&GRE_KEY) + printf(" okey %s ", s4); + } + + if (p->i_flags&GRE_SEQ) + printf("%s Drop packets out of sequence.\n", _SL_); + if (p->i_flags&GRE_CSUM) + printf("%s Checksum in received packet is required.", _SL_); + if (p->o_flags&GRE_SEQ) + printf("%s Sequence packets on output.", _SL_); + if (p->o_flags&GRE_CSUM) + printf("%s Checksum output packets.", _SL_); +} + +static int do_tunnels_list(struct ip_tunnel_parm *p) +{ + char name[IFNAMSIZ]; + unsigned long rx_bytes, rx_packets, rx_errs, rx_drops, + rx_fifo, rx_frame, + tx_bytes, tx_packets, tx_errs, tx_drops, + tx_fifo, tx_colls, tx_carrier, rx_multi; + int type; + struct ip_tunnel_parm p1; + + char buf[512]; + FILE *fp = fopen("/proc/net/dev", "r"); + if (fp == NULL) { + perror("fopen"); + return -1; + } + + fgets(buf, sizeof(buf), fp); + fgets(buf, sizeof(buf), fp); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + char *ptr; + buf[sizeof(buf) - 1] = 0; + if ((ptr = strchr(buf, ':')) == NULL || + (*ptr++ = 0, sscanf(buf, "%s", name) != 1)) { + bb_error_msg("wrong format of /proc/net/dev. Sorry"); + return -1; + } + if (sscanf(ptr, "%lu%lu%lu%lu%lu%lu%lu%*d%lu%lu%lu%lu%lu%lu%lu", + &rx_bytes, &rx_packets, &rx_errs, &rx_drops, + &rx_fifo, &rx_frame, &rx_multi, + &tx_bytes, &tx_packets, &tx_errs, &tx_drops, + &tx_fifo, &tx_colls, &tx_carrier) != 14) + continue; + if (p->name[0] && strcmp(p->name, name)) + continue; + type = do_ioctl_get_iftype(name); + if (type == -1) { + bb_error_msg("failed to get type of [%s]", name); + continue; + } + if (type != ARPHRD_TUNNEL && type != ARPHRD_IPGRE && type != ARPHRD_SIT) + continue; + memset(&p1, 0, sizeof(p1)); + if (do_get_ioctl(name, &p1)) + continue; + if ((p->link && p1.link != p->link) || + (p->name[0] && strcmp(p1.name, p->name)) || + (p->iph.daddr && p1.iph.daddr != p->iph.daddr) || + (p->iph.saddr && p1.iph.saddr != p->iph.saddr) || + (p->i_key && p1.i_key != p->i_key)) + continue; + print_tunnel(&p1); + puts(""); + } + return 0; +} + +static int do_show(int argc, char **argv) +{ + int err; + struct ip_tunnel_parm p; + + if (parse_args(argc, argv, SIOCGETTUNNEL, &p) < 0) + return -1; + + switch (p.iph.protocol) { + case IPPROTO_IPIP: + err = do_get_ioctl(p.name[0] ? p.name : "tunl0", &p); + break; + case IPPROTO_GRE: + err = do_get_ioctl(p.name[0] ? p.name : "gre0", &p); + break; + case IPPROTO_IPV6: + err = do_get_ioctl(p.name[0] ? p.name : "sit0", &p); + break; + default: + do_tunnels_list(&p); + return 0; + } + if (err) + return -1; + + print_tunnel(&p); + puts(""); + return 0; +} + +int do_iptunnel(int argc, char **argv) +{ + if (argc > 0) { + if (matches(*argv, "add") == 0) + return do_add(SIOCADDTUNNEL, argc-1, argv+1); + if (matches(*argv, "change") == 0) + return do_add(SIOCCHGTUNNEL, argc-1, argv+1); + if (matches(*argv, "del") == 0) + return do_del(argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return do_show(argc-1, argv+1); + } else + return do_show(0, NULL); + + bb_error_msg("command \"%s\" is unknown", *argv); + exit(-1); +} diff --git a/networking/libiproute/libnetlink.c b/networking/libiproute/libnetlink.c new file mode 100644 index 000000000..c7e17e1dd --- /dev/null +++ b/networking/libiproute/libnetlink.c @@ -0,0 +1,396 @@ +/* vi: set sw=4 ts=4: */ +/* + * libnetlink.c RTnetlink service routines. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, + * + */ + +#include "libbb.h" +#include + +#include +#include +#include +#include + +#include + +#include "libnetlink.h" + +void rtnl_close(struct rtnl_handle *rth) +{ + close(rth->fd); +} + +int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions) +{ + socklen_t addr_len; + + memset(rth, 0, sizeof(rth)); + + rth->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (rth->fd < 0) { + bb_perror_msg("cannot open netlink socket"); + return -1; + } + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = subscriptions; + + if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) { + bb_perror_msg("cannot bind netlink socket"); + return -1; + } + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0) { + bb_perror_msg("cannot getsockname"); + return -1; + } + if (addr_len != sizeof(rth->local)) { + bb_error_msg("wrong address length %d", addr_len); + return -1; + } + if (rth->local.nl_family != AF_NETLINK) { + bb_error_msg("wrong address family %d", rth->local.nl_family); + return -1; + } + rth->seq = time(NULL); + return 0; +} + +int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) +{ + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = type; + req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.g.rtgen_family = family; + + return sendto(rth->fd, (void*)&req, sizeof(req), 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int rtnl_send(struct rtnl_handle *rth, char *buf, int len) +{ + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + return sendto(rth->fd, buf, len, 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) +{ + struct nlmsghdr nlh; + struct sockaddr_nl nladdr; + struct iovec iov[2] = { { &nlh, sizeof(nlh) }, { req, len } }; + struct msghdr msg = { + (void*)&nladdr, sizeof(nladdr), + iov, 2, + NULL, 0, + 0 + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + nlh.nlmsg_len = NLMSG_LENGTH(len); + nlh.nlmsg_type = type; + nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + nlh.nlmsg_pid = 0; + nlh.nlmsg_seq = rth->dump = ++rth->seq; + + return sendmsg(rth->fd, &msg, 0); +} + +int rtnl_dump_filter(struct rtnl_handle *rth, + int (*filter)(struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg1, + int (*junk)(struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg2) +{ + char buf[8192]; + struct sockaddr_nl nladdr; + struct iovec iov = { buf, sizeof(buf) }; + + while (1) { + int status; + struct nlmsghdr *h; + + struct msghdr msg = { + (void*)&nladdr, sizeof(nladdr), + &iov, 1, + NULL, 0, + 0 + }; + + status = recvmsg(rth->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("OVERRUN"); + continue; + } + if (status == 0) { + bb_error_msg("EOF on netlink"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + bb_error_msg_and_die("sender address length == %d", msg.msg_namelen); + } + + h = (struct nlmsghdr*)buf; + while (NLMSG_OK(h, status)) { + int err; + + if (nladdr.nl_pid != 0 || + h->nlmsg_pid != rth->local.nl_pid || + h->nlmsg_seq != rth->dump) { + if (junk) { + err = junk(&nladdr, h, arg2); + if (err < 0) { + return err; + } + } + goto skip_it; + } + + if (h->nlmsg_type == NLMSG_DONE) { + return 0; + } + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *l_err = (struct nlmsgerr*)NLMSG_DATA(h); + if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { + bb_error_msg("ERROR truncated"); + } else { + errno = -l_err->error; + bb_perror_msg("RTNETLINK answers"); + } + return -1; + } + err = filter(&nladdr, h, arg1); + if (err < 0) { + return err; + } + +skip_it: + h = NLMSG_NEXT(h, status); + } + if (msg.msg_flags & MSG_TRUNC) { + bb_error_msg("message truncated"); + continue; + } + if (status) { + bb_error_msg_and_die("remnant of size %d!", status); + } + } +} + +int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n, void *), + void *jarg) +{ + int status; + unsigned seq; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov = { (void*)n, n->nlmsg_len }; + char buf[8192]; + struct msghdr msg = { + (void*)&nladdr, sizeof(nladdr), + &iov, 1, + NULL, 0, + 0 + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = peer; + nladdr.nl_groups = groups; + + n->nlmsg_seq = seq = ++rtnl->seq; + if (answer == NULL) { + n->nlmsg_flags |= NLM_F_ACK; + } + status = sendmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + bb_perror_msg("cannot talk to rtnetlink"); + return -1; + } + + iov.iov_base = buf; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) { + continue; + } + bb_perror_msg("OVERRUN"); + continue; + } + if (status == 0) { + bb_error_msg("EOF on netlink"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + bb_error_msg_and_die("sender address length == %d", msg.msg_namelen); + } + for (h = (struct nlmsghdr*)buf; status >= sizeof(*h); ) { + int l_err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l<0 || len>status) { + if (msg.msg_flags & MSG_TRUNC) { + bb_error_msg("truncated message"); + return -1; + } + bb_error_msg_and_die("malformed message: len=%d!", len); + } + + if (nladdr.nl_pid != peer || + h->nlmsg_pid != rtnl->local.nl_pid || + h->nlmsg_seq != seq) { + if (junk) { + l_err = junk(&nladdr, h, jarg); + if (l_err < 0) { + return l_err; + } + } + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); + if (l < sizeof(struct nlmsgerr)) { + bb_error_msg("ERROR truncated"); + } else { + errno = -err->error; + if (errno == 0) { + if (answer) { + memcpy(answer, h, h->nlmsg_len); + } + return 0; + } + bb_perror_msg("RTNETLINK answers"); + } + return -1; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + return 0; + } + + bb_error_msg("unexpected reply!"); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + bb_error_msg("message truncated"); + continue; + } + if (status) { + bb_error_msg_and_die("remnant of size %d!", status); + } + } +} + +int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) + return -1; + rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 4); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) + return -1; + rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *subrta; + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), &data, 4); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + +int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen) +{ + struct rtattr *subrta; + int len = RTA_LENGTH(alen); + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), data, alen); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + + +int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max) { + tb[rta->rta_type] = rta; + } + rta = RTA_NEXT(rta,len); + } + if (len) { + bb_error_msg("deficit %d, rta_len=%d!", len, rta->rta_len); + } + return 0; +} diff --git a/networking/libiproute/libnetlink.h b/networking/libiproute/libnetlink.h new file mode 100644 index 000000000..7cfcd2042 --- /dev/null +++ b/networking/libiproute/libnetlink.h @@ -0,0 +1,43 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LIBNETLINK_H__ +#define __LIBNETLINK_H__ 1 + +#include +#include +#include + +struct rtnl_handle +{ + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + __u32 seq; + __u32 dump; +}; + +extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions); +extern void rtnl_close(struct rtnl_handle *rth); +extern int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type); +extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len); +extern int rtnl_dump_filter(struct rtnl_handle *rth, + int (*filter)(struct sockaddr_nl*, struct nlmsghdr *n, void*), + void *arg1, + int (*junk)(struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg2); +extern int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n, void *), + void *jarg); +extern int rtnl_send(struct rtnl_handle *rth, char *buf, int); + + +extern int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data); +extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen); +extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data); +extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen); + +extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len); + + +#endif /* __LIBNETLINK_H__ */ + diff --git a/networking/libiproute/linux/pkt_sched.h b/networking/libiproute/linux/pkt_sched.h new file mode 100644 index 000000000..d6cf1fc06 --- /dev/null +++ b/networking/libiproute/linux/pkt_sched.h @@ -0,0 +1,414 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LINUX_PKT_SCHED_H +#define __LINUX_PKT_SCHED_H + +/* Logical priority bands not depending on specific packet scheduler. + Every scheduler will map them to real traffic classes, if it has + no more precise mechanism to classify packets. + + These numbers have no special meaning, though their coincidence + with obsolete IPv6 values is not occasional :-). New IPv6 drafts + preferred full anarchy inspired by diffserv group. + + Note: TC_PRIO_BESTEFFORT does not mean that it is the most unhappy + class, actually, as rule it will be handled with more care than + filler or even bulk. + */ + +#include + +#define TC_PRIO_BESTEFFORT 0 +#define TC_PRIO_FILLER 1 +#define TC_PRIO_BULK 2 +#define TC_PRIO_INTERACTIVE_BULK 4 +#define TC_PRIO_INTERACTIVE 6 +#define TC_PRIO_CONTROL 7 + +#define TC_PRIO_MAX 15 + +/* Generic queue statistics, available for all the elements. + Particular schedulers may have also their private records. + */ + +struct tc_stats +{ + __u64 bytes; /* NUmber of enqueues bytes */ + __u32 packets; /* Number of enqueued packets */ + __u32 drops; /* Packets dropped because of lack of resources */ + __u32 overlimits; /* Number of throttle events when this + * flow goes out of allocated bandwidth */ + __u32 bps; /* Current flow byte rate */ + __u32 pps; /* Current flow packet rate */ + __u32 qlen; + __u32 backlog; +#ifdef __KERNEL__ + spinlock_t *lock; +#endif +}; + +struct tc_estimator +{ + char interval; + unsigned char ewma_log; +}; + +/* "Handles" + --------- + + All the traffic control objects have 32bit identifiers, or "handles". + + They can be considered as opaque numbers from user API viewpoint, + but actually they always consist of two fields: major and + minor numbers, which are interpreted by kernel specially, + that may be used by applications, though not recommended. + + F.e. qdisc handles always have minor number equal to zero, + classes (or flows) have major equal to parent qdisc major, and + minor uniquely identifying class inside qdisc. + + Macros to manipulate handles: + */ + +#define TC_H_MAJ_MASK (0xFFFF0000U) +#define TC_H_MIN_MASK (0x0000FFFFU) +#define TC_H_MAJ(h) ((h)&TC_H_MAJ_MASK) +#define TC_H_MIN(h) ((h)&TC_H_MIN_MASK) +#define TC_H_MAKE(maj,min) (((maj)&TC_H_MAJ_MASK)|((min)&TC_H_MIN_MASK)) + +#define TC_H_UNSPEC (0U) +#define TC_H_ROOT (0xFFFFFFFFU) +#define TC_H_INGRESS (0xFFFFFFF1U) + +struct tc_ratespec +{ + unsigned char cell_log; + unsigned char __reserved; + unsigned short feature; + short addend; + unsigned short mpu; + __u32 rate; +}; + +/* FIFO section */ + +struct tc_fifo_qopt +{ + __u32 limit; /* Queue length: bytes for bfifo, packets for pfifo */ +}; + +/* PRIO section */ + +#define TCQ_PRIO_BANDS 16 + +struct tc_prio_qopt +{ + int bands; /* Number of bands */ + __u8 priomap[TC_PRIO_MAX+1]; /* Map: logical priority -> PRIO band */ +}; + +/* CSZ section */ + +struct tc_csz_qopt +{ + int flows; /* Maximal number of guaranteed flows */ + unsigned char R_log; /* Fixed point position for round number */ + unsigned char delta_log; /* Log of maximal managed time interval */ + __u8 priomap[TC_PRIO_MAX+1]; /* Map: logical priority -> CSZ band */ +}; + +struct tc_csz_copt +{ + struct tc_ratespec slice; + struct tc_ratespec rate; + struct tc_ratespec peakrate; + __u32 limit; + __u32 buffer; + __u32 mtu; +}; + +enum +{ + TCA_CSZ_UNSPEC, + TCA_CSZ_PARMS, + TCA_CSZ_RTAB, + TCA_CSZ_PTAB, +}; + +/* TBF section */ + +struct tc_tbf_qopt +{ + struct tc_ratespec rate; + struct tc_ratespec peakrate; + __u32 limit; + __u32 buffer; + __u32 mtu; +}; + +enum +{ + TCA_TBF_UNSPEC, + TCA_TBF_PARMS, + TCA_TBF_RTAB, + TCA_TBF_PTAB, +}; + + +/* TEQL section */ + +/* TEQL does not require any parameters */ + +/* SFQ section */ + +struct tc_sfq_qopt +{ + unsigned quantum; /* Bytes per round allocated to flow */ + int perturb_period; /* Period of hash perturbation */ + __u32 limit; /* Maximal packets in queue */ + unsigned divisor; /* Hash divisor */ + unsigned flows; /* Maximal number of flows */ +}; + +/* + * NOTE: limit, divisor and flows are hardwired to code at the moment. + * + * limit=flows=128, divisor=1024; + * + * The only reason for this is efficiency, it is possible + * to change these parameters in compile time. + */ + +/* RED section */ + +enum +{ + TCA_RED_UNSPEC, + TCA_RED_PARMS, + TCA_RED_STAB, +}; + +struct tc_red_qopt +{ + __u32 limit; /* HARD maximal queue length (bytes) */ + __u32 qth_min; /* Min average length threshold (bytes) */ + __u32 qth_max; /* Max average length threshold (bytes) */ + unsigned char Wlog; /* log(W) */ + unsigned char Plog; /* log(P_max/(qth_max-qth_min)) */ + unsigned char Scell_log; /* cell size for idle damping */ + unsigned char flags; +#define TC_RED_ECN 1 +}; + +struct tc_red_xstats +{ + __u32 early; /* Early drops */ + __u32 pdrop; /* Drops due to queue limits */ + __u32 other; /* Drops due to drop() calls */ + __u32 marked; /* Marked packets */ +}; + +/* GRED section */ + +#define MAX_DPs 16 + +enum +{ + TCA_GRED_UNSPEC, + TCA_GRED_PARMS, + TCA_GRED_STAB, + TCA_GRED_DPS, +}; + +#define TCA_SET_OFF TCA_GRED_PARMS +struct tc_gred_qopt +{ + __u32 limit; /* HARD maximal queue length (bytes) +*/ + __u32 qth_min; /* Min average length threshold (bytes) +*/ + __u32 qth_max; /* Max average length threshold (bytes) +*/ + __u32 DP; /* upto 2^32 DPs */ + __u32 backlog; + __u32 qave; + __u32 forced; + __u32 early; + __u32 other; + __u32 pdrop; + + unsigned char Wlog; /* log(W) */ + unsigned char Plog; /* log(P_max/(qth_max-qth_min)) */ + unsigned char Scell_log; /* cell size for idle damping */ + __u8 prio; /* prio of this VQ */ + __u32 packets; + __u32 bytesin; +}; +/* gred setup */ +struct tc_gred_sopt +{ + __u32 DPs; + __u32 def_DP; + __u8 grio; +}; + +/* HTB section */ +#define TC_HTB_NUMPRIO 4 +#define TC_HTB_MAXDEPTH 4 + +struct tc_htb_opt +{ + struct tc_ratespec rate; + struct tc_ratespec ceil; + __u32 buffer; + __u32 cbuffer; + __u32 quantum; /* out only */ + __u32 level; /* out only */ + __u8 prio; + __u8 injectd; /* inject class distance */ + __u8 pad[2]; +}; +struct tc_htb_glob +{ + __u32 rate2quantum; /* bps->quantum divisor */ + __u32 defcls; /* default class number */ + __u32 use_dcache; /* use dequeue cache ? */ + __u32 debug; /* debug flags */ + + + /* stats */ + __u32 deq_rate; /* dequeue rate */ + __u32 utilz; /* dequeue utilization */ + __u32 trials; /* deq_prio trials per dequeue */ + __u32 dcache_hits; + __u32 direct_pkts; /* count of non shapped packets */ +}; +enum +{ + TCA_HTB_UNSPEC, + TCA_HTB_PARMS, + TCA_HTB_INIT, + TCA_HTB_CTAB, + TCA_HTB_RTAB, +}; +struct tc_htb_xstats +{ + __u32 lends; + __u32 borrows; + __u32 giants; /* too big packets (rate will not be accurate) */ + __u32 injects; /* how many times leaf used injected bw */ + __u32 tokens; + __u32 ctokens; +}; + +/* CBQ section */ + +#define TC_CBQ_MAXPRIO 8 +#define TC_CBQ_MAXLEVEL 8 +#define TC_CBQ_DEF_EWMA 5 + +struct tc_cbq_lssopt +{ + unsigned char change; + unsigned char flags; +#define TCF_CBQ_LSS_BOUNDED 1 +#define TCF_CBQ_LSS_ISOLATED 2 + unsigned char ewma_log; + unsigned char level; +#define TCF_CBQ_LSS_FLAGS 1 +#define TCF_CBQ_LSS_EWMA 2 +#define TCF_CBQ_LSS_MAXIDLE 4 +#define TCF_CBQ_LSS_MINIDLE 8 +#define TCF_CBQ_LSS_OFFTIME 0x10 +#define TCF_CBQ_LSS_AVPKT 0x20 + __u32 maxidle; + __u32 minidle; + __u32 offtime; + __u32 avpkt; +}; + +struct tc_cbq_wrropt +{ + unsigned char flags; + unsigned char priority; + unsigned char cpriority; + unsigned char __reserved; + __u32 allot; + __u32 weight; +}; + +struct tc_cbq_ovl +{ + unsigned char strategy; +#define TC_CBQ_OVL_CLASSIC 0 +#define TC_CBQ_OVL_DELAY 1 +#define TC_CBQ_OVL_LOWPRIO 2 +#define TC_CBQ_OVL_DROP 3 +#define TC_CBQ_OVL_RCLASSIC 4 + unsigned char priority2; + __u32 penalty; +}; + +struct tc_cbq_police +{ + unsigned char police; + unsigned char __res1; + unsigned short __res2; +}; + +struct tc_cbq_fopt +{ + __u32 split; + __u32 defmap; + __u32 defchange; +}; + +struct tc_cbq_xstats +{ + __u32 borrows; + __u32 overactions; + __s32 avgidle; + __s32 undertime; +}; + +enum +{ + TCA_CBQ_UNSPEC, + TCA_CBQ_LSSOPT, + TCA_CBQ_WRROPT, + TCA_CBQ_FOPT, + TCA_CBQ_OVL_STRATEGY, + TCA_CBQ_RATE, + TCA_CBQ_RTAB, + TCA_CBQ_POLICE, +}; + +#define TCA_CBQ_MAX TCA_CBQ_POLICE + +/* dsmark section */ + +enum { + TCA_DSMARK_UNSPEC, + TCA_DSMARK_INDICES, + TCA_DSMARK_DEFAULT_INDEX, + TCA_DSMARK_SET_TC_INDEX, + TCA_DSMARK_MASK, + TCA_DSMARK_VALUE +}; + +#define TCA_DSMARK_MAX TCA_DSMARK_VALUE + +/* ATM section */ + +enum { + TCA_ATM_UNSPEC, + TCA_ATM_FD, /* file/socket descriptor */ + TCA_ATM_PTR, /* pointer to descriptor - later */ + TCA_ATM_HDR, /* LL header */ + TCA_ATM_EXCESS, /* excess traffic class (0 for CLP) */ + TCA_ATM_ADDR, /* PVC address (for output only) */ + TCA_ATM_STATE /* VC state (ATM_VS_*; for output only) */ +}; + +#define TCA_ATM_MAX TCA_ATM_STATE + +#endif diff --git a/networking/libiproute/ll_addr.c b/networking/libiproute/ll_addr.c new file mode 100644 index 000000000..ba0a65a18 --- /dev/null +++ b/networking/libiproute/ll_addr.c @@ -0,0 +1,85 @@ +/* vi: set sw=4 ts=4: */ +/* + * ll_addr.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, + */ + +#include "libbb.h" + +#include +#include + +#include "rt_names.h" +#include "utils.h" + + +const char *ll_addr_n2a(unsigned char *addr, int alen, int type, char *buf, int blen) +{ + int i; + int l; + + if (alen == 4 && + (type == ARPHRD_TUNNEL || type == ARPHRD_SIT || type == ARPHRD_IPGRE)) { + return inet_ntop(AF_INET, addr, buf, blen); + } + l = 0; + for (i=0; i 255) { + bb_error_msg("\"%s\" is invalid lladdr", arg); + return -1; + } + lladdr[i] = temp; + if (!cp) { + break; + } + arg = cp; + } + return i+1; + } +} diff --git a/networking/libiproute/ll_map.c b/networking/libiproute/ll_map.c new file mode 100644 index 000000000..a14fa4e42 --- /dev/null +++ b/networking/libiproute/ll_map.c @@ -0,0 +1,186 @@ +/* vi: set sw=4 ts=4: */ +/* + * ll_map.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, + * + */ + +#include "libbb.h" +#include + +#include "libnetlink.h" +#include "ll_map.h" + +#include /* socket() */ +#include /* struct ifreq and co. */ +#include /* ioctl() & SIOCGIFINDEX */ + +struct idxmap { + struct idxmap * next; + int index; + int type; + int alen; + unsigned flags; + unsigned char addr[8]; + char name[16]; +}; + +static struct idxmap *idxmap[16]; + +int ll_remember_index(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) +{ + int h; + struct ifinfomsg *ifi = NLMSG_DATA(n); + struct idxmap *im, **imp; + struct rtattr *tb[IFLA_MAX+1]; + + if (n->nlmsg_type != RTM_NEWLINK) + return 0; + + if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifi))) + return -1; + + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n)); + if (tb[IFLA_IFNAME] == NULL) + return 0; + + h = ifi->ifi_index&0xF; + + for (imp=&idxmap[h]; (im=*imp)!=NULL; imp = &im->next) + if (im->index == ifi->ifi_index) + break; + + if (im == NULL) { + im = xmalloc(sizeof(*im)); + im->next = *imp; + im->index = ifi->ifi_index; + *imp = im; + } + + im->type = ifi->ifi_type; + im->flags = ifi->ifi_flags; + if (tb[IFLA_ADDRESS]) { + int alen; + im->alen = alen = RTA_PAYLOAD(tb[IFLA_ADDRESS]); + if (alen > sizeof(im->addr)) + alen = sizeof(im->addr); + memcpy(im->addr, RTA_DATA(tb[IFLA_ADDRESS]), alen); + } else { + im->alen = 0; + memset(im->addr, 0, sizeof(im->addr)); + } + strcpy(im->name, RTA_DATA(tb[IFLA_IFNAME])); + return 0; +} + +const char *ll_idx_n2a(int idx, char *buf) +{ + struct idxmap *im; + + if (idx == 0) + return "*"; + for (im = idxmap[idx&0xF]; im; im = im->next) + if (im->index == idx) + return im->name; + snprintf(buf, 16, "if%d", idx); + return buf; +} + + +const char *ll_index_to_name(int idx) +{ + static char nbuf[16]; + + return ll_idx_n2a(idx, nbuf); +} + +int ll_index_to_type(int idx) +{ + struct idxmap *im; + + if (idx == 0) + return -1; + for (im = idxmap[idx&0xF]; im; im = im->next) + if (im->index == idx) + return im->type; + return -1; +} + +unsigned ll_index_to_flags(int idx) +{ + struct idxmap *im; + + if (idx == 0) + return 0; + + for (im = idxmap[idx&0xF]; im; im = im->next) + if (im->index == idx) + return im->flags; + return 0; +} + +int ll_name_to_index(char *name) +{ + static char ncache[16]; + static int icache; + struct idxmap *im; + int sock_fd; + int i; + + if (name == NULL) + return 0; + if (icache && strcmp(name, ncache) == 0) + return icache; + for (i=0; i<16; i++) { + for (im = idxmap[i]; im; im = im->next) { + if (strcmp(im->name, name) == 0) { + icache = im->index; + strcpy(ncache, name); + return im->index; + } + } + } + /* We have not found the interface in our cache, but the kernel + * may still know about it. One reason is that we may be using + * module on-demand loading, which means that the kernel will + * load the module and make the interface exist only when + * we explicitely request it (check for dev_load() in net/core/dev.c). + * I can think of other similar scenario, but they are less common... + * Jean II */ + sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_fd) { + struct ifreq ifr; + int ret; + strncpy(ifr.ifr_name, name, IFNAMSIZ); + ifr.ifr_ifindex = -1; + ret = ioctl(sock_fd, SIOCGIFINDEX, &ifr); + close(sock_fd); + if (ret >= 0) + /* In theory, we should redump the interface list + * to update our cache, this is left as an exercise + * to the reader... Jean II */ + return ifr.ifr_ifindex; + } + + return 0; +} + +int ll_init_map(struct rtnl_handle *rth) +{ + if (rtnl_wilddump_request(rth, AF_UNSPEC, RTM_GETLINK) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + + if (rtnl_dump_filter(rth, ll_remember_index, &idxmap, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + return 0; +} diff --git a/networking/libiproute/ll_map.h b/networking/libiproute/ll_map.h new file mode 100644 index 000000000..226d48fc2 --- /dev/null +++ b/networking/libiproute/ll_map.h @@ -0,0 +1,13 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LL_MAP_H__ +#define __LL_MAP_H__ 1 + +extern int ll_remember_index(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +extern int ll_init_map(struct rtnl_handle *rth); +extern int ll_name_to_index(char *name); +extern const char *ll_index_to_name(int idx); +extern const char *ll_idx_n2a(int idx, char *buf); +extern int ll_index_to_type(int idx); +extern unsigned ll_index_to_flags(int idx); + +#endif /* __LL_MAP_H__ */ diff --git a/networking/libiproute/ll_proto.c b/networking/libiproute/ll_proto.c new file mode 100644 index 000000000..a3fe9d376 --- /dev/null +++ b/networking/libiproute/ll_proto.c @@ -0,0 +1,122 @@ +/* vi: set sw=4 ts=4: */ +/* + * ll_proto.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, + */ + +#include "libbb.h" +#include + +#include "rt_names.h" +#include "utils.h" + +#if __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1 +#include +#else +#include +#endif + +#define __PF(f,n) { ETH_P_##f, #n }, +static struct { + int id; + char *name; +} llproto_names[] = { +__PF(LOOP,loop) +__PF(PUP,pup) +#ifdef ETH_P_PUPAT +__PF(PUPAT,pupat) +#endif +__PF(IP,ip) +__PF(X25,x25) +__PF(ARP,arp) +__PF(BPQ,bpq) +#ifdef ETH_P_IEEEPUP +__PF(IEEEPUP,ieeepup) +#endif +#ifdef ETH_P_IEEEPUPAT +__PF(IEEEPUPAT,ieeepupat) +#endif +__PF(DEC,dec) +__PF(DNA_DL,dna_dl) +__PF(DNA_RC,dna_rc) +__PF(DNA_RT,dna_rt) +__PF(LAT,lat) +__PF(DIAG,diag) +__PF(CUST,cust) +__PF(SCA,sca) +__PF(RARP,rarp) +__PF(ATALK,atalk) +__PF(AARP,aarp) +__PF(IPX,ipx) +__PF(IPV6,ipv6) +#ifdef ETH_P_PPP_DISC +__PF(PPP_DISC,ppp_disc) +#endif +#ifdef ETH_P_PPP_SES +__PF(PPP_SES,ppp_ses) +#endif +#ifdef ETH_P_ATMMPOA +__PF(ATMMPOA,atmmpoa) +#endif +#ifdef ETH_P_ATMFATE +__PF(ATMFATE,atmfate) +#endif + +__PF(802_3,802_3) +__PF(AX25,ax25) +__PF(ALL,all) +__PF(802_2,802_2) +__PF(SNAP,snap) +__PF(DDCMP,ddcmp) +__PF(WAN_PPP,wan_ppp) +__PF(PPP_MP,ppp_mp) +__PF(LOCALTALK,localtalk) +__PF(PPPTALK,ppptalk) +__PF(TR_802_2,tr_802_2) +__PF(MOBITEX,mobitex) +__PF(CONTROL,control) +__PF(IRDA,irda) +#ifdef ETH_P_ECONET +__PF(ECONET,econet) +#endif + +{ 0x8100, "802.1Q" }, +{ ETH_P_IP, "ipv4" }, +}; +#undef __PF + + +const char * ll_proto_n2a(unsigned short id, char *buf, int len) +{ + int i; + + id = ntohs(id); + + for (i=0; i + */ +#include +#include + +#include + +#include "rt_names.h" + +const char * ll_type_n2a(int type, char *buf, int len) +{ +#define __PF(f,n) { ARPHRD_##f, #n }, +static struct { + int type; + char *name; +} arphrd_names[] = { +{ 0, "generic" }, +__PF(ETHER,ether) +__PF(EETHER,eether) +__PF(AX25,ax25) +__PF(PRONET,pronet) +__PF(CHAOS,chaos) +#ifdef ARPHRD_IEEE802_TR +__PF(IEEE802,ieee802) +#else +__PF(IEEE802,tr) +#endif +__PF(ARCNET,arcnet) +__PF(APPLETLK,atalk) +__PF(DLCI,dlci) +#ifdef ARPHRD_ATM +__PF(ATM,atm) +#endif +__PF(METRICOM,metricom) +#ifdef ARPHRD_IEEE1394 +__PF(IEEE1394,ieee1394) +#endif + +__PF(SLIP,slip) +__PF(CSLIP,cslip) +__PF(SLIP6,slip6) +__PF(CSLIP6,cslip6) +__PF(RSRVD,rsrvd) +__PF(ADAPT,adapt) +__PF(ROSE,rose) +__PF(X25,x25) +#ifdef ARPHRD_HWX25 +__PF(HWX25,hwx25) +#endif +__PF(PPP,ppp) +__PF(HDLC,hdlc) +__PF(LAPB,lapb) +#ifdef ARPHRD_DDCMP +__PF(DDCMP,ddcmp) +__PF(RAWHDLC,rawhdlc) +#endif + +__PF(TUNNEL,ipip) +__PF(TUNNEL6,tunnel6) +__PF(FRAD,frad) +__PF(SKIP,skip) +__PF(LOOPBACK,loopback) +__PF(LOCALTLK,ltalk) +__PF(FDDI,fddi) +__PF(BIF,bif) +__PF(SIT,sit) +__PF(IPDDP,ip/ddp) +__PF(IPGRE,gre) +__PF(PIMREG,pimreg) +__PF(HIPPI,hippi) +__PF(ASH,ash) +__PF(ECONET,econet) +__PF(IRDA,irda) +__PF(FCPP,fcpp) +__PF(FCAL,fcal) +__PF(FCPL,fcpl) +__PF(FCFABRIC,fcfb0) +__PF(FCFABRIC+1,fcfb1) +__PF(FCFABRIC+2,fcfb2) +__PF(FCFABRIC+3,fcfb3) +__PF(FCFABRIC+4,fcfb4) +__PF(FCFABRIC+5,fcfb5) +__PF(FCFABRIC+6,fcfb6) +__PF(FCFABRIC+7,fcfb7) +__PF(FCFABRIC+8,fcfb8) +__PF(FCFABRIC+9,fcfb9) +__PF(FCFABRIC+10,fcfb10) +__PF(FCFABRIC+11,fcfb11) +__PF(FCFABRIC+12,fcfb12) +#ifdef ARPHRD_IEEE802_TR +__PF(IEEE802_TR,tr) +#endif +#ifdef ARPHRD_IEEE80211 +__PF(IEEE80211,ieee802.11) +#endif +#ifdef ARPHRD_VOID +__PF(VOID,void) +#endif +}; +#undef __PF + + int i; + for (i=0; i + */ +#include +#include +#include + +#include +#include "rt_names.h" + +static void rtnl_tab_initialize(char *file, const char **tab, int size) +{ + char buf[512]; + FILE *fp; + + fp = fopen(file, "r"); + if (!fp) + return; + while (fgets(buf, sizeof(buf), fp)) { + char *p = buf; + int id; + char namebuf[512]; + + while (*p == ' ' || *p == '\t') + p++; + if (*p == '#' || *p == '\n' || *p == 0) + continue; + if (sscanf(p, "0x%x %s\n", &id, namebuf) != 2 && + sscanf(p, "0x%x %s #", &id, namebuf) != 2 && + sscanf(p, "%d %s\n", &id, namebuf) != 2 && + sscanf(p, "%d %s #", &id, namebuf) != 2) { + fprintf(stderr, "Database %s is corrupted at %s\n", + file, p); + return; + } + + if (id<0 || id>size) + continue; + + tab[id] = strdup(namebuf); + } + fclose(fp); +} + + +static const char * rtnl_rtprot_tab[256] = { + "none", + "redirect", + "kernel", + "boot", + "static", + NULL, + NULL, + NULL, + "gated", + "ra", + "mrt", + "zebra", + "bird", +}; + + + +static int rtnl_rtprot_init; + +static void rtnl_rtprot_initialize(void) +{ + rtnl_rtprot_init = 1; + rtnl_tab_initialize("/etc/iproute2/rt_protos", + rtnl_rtprot_tab, 256); +} + +const char * rtnl_rtprot_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtprot_tab[id]) { + if (!rtnl_rtprot_init) + rtnl_rtprot_initialize(); + } + if (rtnl_rtprot_tab[id]) + return rtnl_rtprot_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} + +int rtnl_rtprot_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtprot_init) + rtnl_rtprot_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtprot_tab[i] && + strcmp(rtnl_rtprot_tab[i], arg) == 0) { + cache = rtnl_rtprot_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + + + +static const char * rtnl_rtscope_tab[256] = { + "global", +}; + +static int rtnl_rtscope_init; + +static void rtnl_rtscope_initialize(void) +{ + rtnl_rtscope_init = 1; + rtnl_rtscope_tab[255] = "nowhere"; + rtnl_rtscope_tab[254] = "host"; + rtnl_rtscope_tab[253] = "link"; + rtnl_rtscope_tab[200] = "site"; + rtnl_tab_initialize("/etc/iproute2/rt_scopes", + rtnl_rtscope_tab, 256); +} + +const char * rtnl_rtscope_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtscope_tab[id]) { + if (!rtnl_rtscope_init) + rtnl_rtscope_initialize(); + } + if (rtnl_rtscope_tab[id]) + return rtnl_rtscope_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} + +int rtnl_rtscope_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtscope_init) + rtnl_rtscope_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtscope_tab[i] && + strcmp(rtnl_rtscope_tab[i], arg) == 0) { + cache = rtnl_rtscope_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + + + +static const char * rtnl_rtrealm_tab[256] = { + "unknown", +}; + +static int rtnl_rtrealm_init; + +static void rtnl_rtrealm_initialize(void) +{ + rtnl_rtrealm_init = 1; + rtnl_tab_initialize("/etc/iproute2/rt_realms", + rtnl_rtrealm_tab, 256); +} + +int rtnl_rtrealm_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtrealm_init) + rtnl_rtrealm_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtrealm_tab[i] && + strcmp(rtnl_rtrealm_tab[i], arg) == 0) { + cache = rtnl_rtrealm_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + +#if ENABLE_FEATURE_IP_RULE +const char * rtnl_rtrealm_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtrealm_tab[id]) { + if (!rtnl_rtrealm_init) + rtnl_rtrealm_initialize(); + } + if (rtnl_rtrealm_tab[id]) + return rtnl_rtrealm_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} +#endif + +static const char * rtnl_rtdsfield_tab[256] = { + "0", +}; + +static int rtnl_rtdsfield_init; + +static void rtnl_rtdsfield_initialize(void) +{ + rtnl_rtdsfield_init = 1; + rtnl_tab_initialize("/etc/iproute2/rt_dsfield", + rtnl_rtdsfield_tab, 256); +} + +const char * rtnl_dsfield_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtdsfield_tab[id]) { + if (!rtnl_rtdsfield_init) + rtnl_rtdsfield_initialize(); + } + if (rtnl_rtdsfield_tab[id]) + return rtnl_rtdsfield_tab[id]; + snprintf(buf, len, "0x%02x", id); + return buf; +} + + +int rtnl_dsfield_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtdsfield_init) + rtnl_rtdsfield_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtdsfield_tab[i] && + strcmp(rtnl_rtdsfield_tab[i], arg) == 0) { + cache = rtnl_rtdsfield_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 16); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + +#if ENABLE_FEATURE_IP_RULE +static int rtnl_rttable_init; +static const char * rtnl_rttable_tab[256] = { + "unspec", +}; +static void rtnl_rttable_initialize(void) +{ + rtnl_rttable_init = 1; + rtnl_rttable_tab[255] = "local"; + rtnl_rttable_tab[254] = "main"; + rtnl_rttable_tab[253] = "default"; + rtnl_tab_initialize("/etc/iproute2/rt_tables", rtnl_rttable_tab, 256); +} + +const char *rtnl_rttable_n2a(int id, char *buf, int len) +{ + if (id < 0 || id >= 256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rttable_tab[id]) { + if (!rtnl_rttable_init) + rtnl_rttable_initialize(); + } + if (rtnl_rttable_tab[id]) + return rtnl_rttable_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} + +int rtnl_rttable_a2n(uint32_t * id, char *arg) +{ + static char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rttable_init) + rtnl_rttable_initialize(); + + for (i = 0; i < 256; i++) { + if (rtnl_rttable_tab[i] && strcmp(rtnl_rttable_tab[i], arg) == 0) { + cache = (char*)rtnl_rttable_tab[i]; + res = i; + *id = res; + return 0; + } + } + + i = strtoul(arg, &end, 0); + if (!end || end == arg || *end || i > 255) + return -1; + *id = i; + return 0; +} + +#endif diff --git a/networking/libiproute/rt_names.h b/networking/libiproute/rt_names.h new file mode 100644 index 000000000..92b807f3d --- /dev/null +++ b/networking/libiproute/rt_names.h @@ -0,0 +1,29 @@ +/* vi: set sw=4 ts=4: */ +#ifndef RT_NAMES_H_ +#define RT_NAMES_H_ 1 + +#include + +extern const char* rtnl_rtprot_n2a(int id, char *buf, int len); +extern const char* rtnl_rtscope_n2a(int id, char *buf, int len); +extern const char* rtnl_rtrealm_n2a(int id, char *buf, int len); +extern const char* rtnl_dsfield_n2a(int id, char *buf, int len); +extern const char* rtnl_rttable_n2a(int id, char *buf, int len); +extern int rtnl_rtprot_a2n(uint32_t *id, char *arg); +extern int rtnl_rtscope_a2n(uint32_t *id, char *arg); +extern int rtnl_rtrealm_a2n(uint32_t *id, char *arg); +extern int rtnl_dsfield_a2n(uint32_t *id, char *arg); +extern int rtnl_rttable_a2n(uint32_t *id, char *arg); + + +extern const char * ll_type_n2a(int type, char *buf, int len); + +extern const char *ll_addr_n2a(unsigned char *addr, int alen, int type, + char *buf, int blen); +extern int ll_addr_a2n(unsigned char *lladdr, int len, char *arg); + +extern const char * ll_proto_n2a(unsigned short id, char *buf, int len); +extern int ll_proto_a2n(unsigned short *id, char *buf); + + +#endif diff --git a/networking/libiproute/rtm_map.c b/networking/libiproute/rtm_map.c new file mode 100644 index 000000000..8aaee4bd7 --- /dev/null +++ b/networking/libiproute/rtm_map.c @@ -0,0 +1,111 @@ +/* vi: set sw=4 ts=4: */ +/* + * rtm_map.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * Authors: Alexey Kuznetsov, + * + */ + +#include +#include + +#include "rt_names.h" +#include "utils.h" + +char *rtnl_rtntype_n2a(int id, char *buf, int len) +{ + switch (id) { + case RTN_UNSPEC: + return "none"; + case RTN_UNICAST: + return "unicast"; + case RTN_LOCAL: + return "local"; + case RTN_BROADCAST: + return "broadcast"; + case RTN_ANYCAST: + return "anycast"; + case RTN_MULTICAST: + return "multicast"; + case RTN_BLACKHOLE: + return "blackhole"; + case RTN_UNREACHABLE: + return "unreachable"; + case RTN_PROHIBIT: + return "prohibit"; + case RTN_THROW: + return "throw"; + case RTN_NAT: + return "nat"; + case RTN_XRESOLVE: + return "xresolve"; + default: + snprintf(buf, len, "%d", id); + return buf; + } +} + + +int rtnl_rtntype_a2n(int *id, char *arg) +{ + char *end; + unsigned long res; + + if (strcmp(arg, "local") == 0) + res = RTN_LOCAL; + else if (strcmp(arg, "nat") == 0) + res = RTN_NAT; + else if (matches(arg, "broadcast") == 0 || + strcmp(arg, "brd") == 0) + res = RTN_BROADCAST; + else if (matches(arg, "anycast") == 0) + res = RTN_ANYCAST; + else if (matches(arg, "multicast") == 0) + res = RTN_MULTICAST; + else if (matches(arg, "prohibit") == 0) + res = RTN_PROHIBIT; + else if (matches(arg, "unreachable") == 0) + res = RTN_UNREACHABLE; + else if (matches(arg, "blackhole") == 0) + res = RTN_BLACKHOLE; + else if (matches(arg, "xresolve") == 0) + res = RTN_XRESOLVE; + else if (matches(arg, "unicast") == 0) + res = RTN_UNICAST; + else if (strcmp(arg, "throw") == 0) + res = RTN_THROW; + else { + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + } + *id = res; + return 0; +} + +int get_rt_realms(__u32 *realms, char *arg) +{ + __u32 realm = 0; + char *p = strchr(arg, '/'); + + *realms = 0; + if (p) { + *p = 0; + if (rtnl_rtrealm_a2n(realms, arg)) { + *p = '/'; + return -1; + } + *realms <<= 16; + *p = '/'; + arg = p+1; + } + if (*arg && rtnl_rtrealm_a2n(&realm, arg)) + return -1; + *realms |= realm; + return 0; +} diff --git a/networking/libiproute/rtm_map.h b/networking/libiproute/rtm_map.h new file mode 100644 index 000000000..1ffb940b9 --- /dev/null +++ b/networking/libiproute/rtm_map.h @@ -0,0 +1,11 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __RTM_MAP_H__ +#define __RTM_MAP_H__ 1 + +char *rtnl_rtntype_n2a(int id, char *buf, int len); +int rtnl_rtntype_a2n(int *id, char *arg); + +int get_rt_realms(__u32 *realms, char *arg); + + +#endif /* __RTM_MAP_H__ */ diff --git a/networking/libiproute/utils.c b/networking/libiproute/utils.c new file mode 100644 index 000000000..fc6aff1d1 --- /dev/null +++ b/networking/libiproute/utils.c @@ -0,0 +1,354 @@ +/* vi: set sw=4 ts=4: */ +/* + * utils.c + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Authors: Alexey Kuznetsov, + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libbb.h" + +#include +#include + +#include "utils.h" +#include "inet_common.h" + +int get_integer(int *val, char *arg, int base) +{ + long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtol(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > INT_MAX || res < INT_MIN) + return -1; + *val = res; + return 0; +} + +int get_unsigned(unsigned *val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > UINT_MAX) + return -1; + *val = res; + return 0; +} + +int get_u32(__u32 * val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0xFFFFFFFFUL) + return -1; + *val = res; + return 0; +} + +int get_u16(__u16 * val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0xFFFF) + return -1; + *val = res; + return 0; +} + +int get_u8(__u8 * val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0xFF) + return -1; + *val = res; + return 0; +} + +int get_s16(__s16 * val, char *arg, int base) +{ + long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtol(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0x7FFF || res < -0x8000) + return -1; + *val = res; + return 0; +} + +int get_s8(__s8 * val, char *arg, int base) +{ + long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtol(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0x7F || res < -0x80) + return -1; + *val = res; + return 0; +} + +int get_addr_1(inet_prefix * addr, char *name, int family) +{ + char *cp; + unsigned char *ap = (unsigned char *) addr->data; + int i; + + memset(addr, 0, sizeof(*addr)); + + if (strcmp(name, bb_str_default) == 0 || + strcmp(name, "all") == 0 || strcmp(name, "any") == 0) { + addr->family = family; + addr->bytelen = (family == AF_INET6 ? 16 : 4); + addr->bitlen = -1; + return 0; + } + + if (strchr(name, ':')) { + addr->family = AF_INET6; + if (family != AF_UNSPEC && family != AF_INET6) + return -1; + if (inet_pton(AF_INET6, name, addr->data) <= 0) + return -1; + addr->bytelen = 16; + addr->bitlen = -1; + return 0; + } + + addr->family = AF_INET; + if (family != AF_UNSPEC && family != AF_INET) + return -1; + addr->bytelen = 4; + addr->bitlen = -1; + for (cp = name, i = 0; *cp; cp++) { + if (*cp <= '9' && *cp >= '0') { + ap[i] = 10 * ap[i] + (*cp - '0'); + continue; + } + if (*cp == '.' && ++i <= 3) + continue; + return -1; + } + return 0; +} + +int get_prefix_1(inet_prefix * dst, char *arg, int family) +{ + int err; + int plen; + char *slash; + + memset(dst, 0, sizeof(*dst)); + + if (strcmp(arg, bb_str_default) == 0 || strcmp(arg, "any") == 0) { + dst->family = family; + dst->bytelen = 0; + dst->bitlen = 0; + return 0; + } + + slash = strchr(arg, '/'); + if (slash) + *slash = 0; + err = get_addr_1(dst, arg, family); + if (err == 0) { + switch (dst->family) { + case AF_INET6: + dst->bitlen = 128; + break; + default: + case AF_INET: + dst->bitlen = 32; + } + if (slash) { + if (get_integer(&plen, slash + 1, 0) || plen > dst->bitlen) { + err = -1; + goto done; + } + dst->bitlen = plen; + } + } + done: + if (slash) + *slash = '/'; + return err; +} + +int get_addr(inet_prefix * dst, char *arg, int family) +{ + if (family == AF_PACKET) { + bb_error_msg_and_die("\"%s\" may be inet address, but it is not allowed in this context", arg); + } + if (get_addr_1(dst, arg, family)) { + bb_error_msg_and_die("an inet address is expected rather than \"%s\"", arg); + } + return 0; +} + +int get_prefix(inet_prefix * dst, char *arg, int family) +{ + if (family == AF_PACKET) { + bb_error_msg_and_die("\"%s\" may be inet address, but it is not allowed in this context", arg); + } + if (get_prefix_1(dst, arg, family)) { + bb_error_msg_and_die("an inet address is expected rather than \"%s\"", arg); + } + return 0; +} + +__u32 get_addr32(char *name) +{ + inet_prefix addr; + + if (get_addr_1(&addr, name, AF_INET)) { + bb_error_msg_and_die("an IP address is expected rather than \"%s\"", name); + } + return addr.data[0]; +} + +void incomplete_command(void) +{ + bb_error_msg("command line is not complete, try option \"help\""); + exit(-1); +} + +void invarg(const char * const arg, const char * const opt) +{ + bb_error_msg(bb_msg_invalid_arg, arg, opt); + exit(-1); +} + +void duparg(char *key, char *arg) +{ + bb_error_msg("duplicate \"%s\": \"%s\" is the second value", key, arg); + exit(-1); +} + +void duparg2(char *key, char *arg) +{ + bb_error_msg("either \"%s\" is duplicate, or \"%s\" is garbage", key, arg); + exit(-1); +} + +int matches(char *cmd, char *pattern) +{ + int len = strlen(cmd); + + return strncmp(pattern, cmd, len); +} + +int inet_addr_match(inet_prefix * a, inet_prefix * b, int bits) +{ + __u32 *a1 = a->data; + __u32 *a2 = b->data; + int words = bits >> 0x05; + + bits &= 0x1f; + + if (words) + if (memcmp(a1, a2, words << 2)) + return -1; + + if (bits) { + __u32 w1, w2; + __u32 mask; + + w1 = a1[words]; + w2 = a2[words]; + + mask = htonl((0xffffffff) << (0x20 - bits)); + + if ((w1 ^ w2) & mask) + return 1; + } + + return 0; +} + +int __iproute2_hz_internal; + +int __get_hz(void) +{ + int hz = 0; + FILE *fp = fopen("/proc/net/psched", "r"); + + if (fp) { + unsigned nom, denom; + + if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2) + if (nom == 1000000) + hz = denom; + fclose(fp); + } + if (hz) + return hz; + return sysconf(_SC_CLK_TCK); +} + +const char *rt_addr_n2a(int af, int ATTRIBUTE_UNUSED len, + void *addr, char *buf, int buflen) +{ + switch (af) { + case AF_INET: + case AF_INET6: + return inet_ntop(af, addr, buf, buflen); + default: + return "???"; + } +} + + +const char *format_host(int af, int len, void *addr, char *buf, int buflen) +{ +#ifdef RESOLVE_HOSTNAMES + if (resolve_hosts) { + struct hostent *h_ent; + + if (len <= 0) { + switch (af) { + case AF_INET: + len = 4; + break; + case AF_INET6: + len = 16; + break; + default:; + } + } + if (len > 0 && (h_ent = gethostbyaddr(addr, len, af)) != NULL) { + snprintf(buf, buflen - 1, "%s", h_ent->h_name); + return buf; + } + } +#endif + return rt_addr_n2a(af, len, addr, buf, buflen); +} diff --git a/networking/libiproute/utils.h b/networking/libiproute/utils.h new file mode 100644 index 000000000..0b0d7545a --- /dev/null +++ b/networking/libiproute/utils.h @@ -0,0 +1,103 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __UTILS_H__ +#define __UTILS_H__ 1 + +#include "libbb.h" +#include +#include + +#include "libnetlink.h" +#include "ll_map.h" +#include "rtm_map.h" + +extern int preferred_family; +extern int show_stats; +extern int show_details; +extern int show_raw; +extern int resolve_hosts; +extern int oneline; +extern char * _SL_; + +#ifndef IPPROTO_ESP +#define IPPROTO_ESP 50 +#endif +#ifndef IPPROTO_AH +#define IPPROTO_AH 51 +#endif + +#define SPRINT_BSIZE 64 +#define SPRINT_BUF(x) char x[SPRINT_BSIZE] + +extern void incomplete_command(void) ATTRIBUTE_NORETURN; + +#define NEXT_ARG() do { argv++; if (--argc <= 0) incomplete_command(); } while(0) + +typedef struct +{ + __u8 family; + __u8 bytelen; + __s16 bitlen; + __u32 data[4]; +} inet_prefix; + +#define DN_MAXADDL 20 +#ifndef AF_DECnet +#define AF_DECnet 12 +#endif + +struct dn_naddr +{ + unsigned short a_len; + unsigned char a_addr[DN_MAXADDL]; +}; + +#define IPX_NODE_LEN 6 + +struct ipx_addr { + uint32_t ipx_net; + uint8_t ipx_node[IPX_NODE_LEN]; +}; + +extern __u32 get_addr32(char *name); +extern int get_addr_1(inet_prefix *dst, char *arg, int family); +extern int get_prefix_1(inet_prefix *dst, char *arg, int family); +extern int get_addr(inet_prefix *dst, char *arg, int family); +extern int get_prefix(inet_prefix *dst, char *arg, int family); + +extern int get_integer(int *val, char *arg, int base); +extern int get_unsigned(unsigned *val, char *arg, int base); +#define get_byte get_u8 +#define get_ushort get_u16 +#define get_short get_s16 +extern int get_u32(__u32 *val, char *arg, int base); +extern int get_u16(__u16 *val, char *arg, int base); +extern int get_s16(__s16 *val, char *arg, int base); +extern int get_u8(__u8 *val, char *arg, int base); +extern int get_s8(__s8 *val, char *arg, int base); + +extern const char *format_host(int af, int len, void *addr, char *buf, int buflen); +extern const char *rt_addr_n2a(int af, int len, void *addr, char *buf, int buflen); + +void invarg(const char * const, const char * const) ATTRIBUTE_NORETURN; +void duparg(char *, char *) ATTRIBUTE_NORETURN; +void duparg2(char *, char *) ATTRIBUTE_NORETURN; +int matches(char *arg, char *pattern); +extern int inet_addr_match(inet_prefix *a, inet_prefix *b, int bits); + +const char *dnet_ntop(int af, const void *addr, char *str, size_t len); +int dnet_pton(int af, const char *src, void *addr); + +const char *ipx_ntop(int af, const void *addr, char *str, size_t len); +int ipx_pton(int af, const char *src, void *addr); + +extern int __iproute2_hz_internal; +extern int __get_hz(void); + +static __inline__ int get_hz(void) +{ + if (__iproute2_hz_internal == 0) + __iproute2_hz_internal = __get_hz(); + return __iproute2_hz_internal; +} + +#endif /* __UTILS_H__ */ diff --git a/networking/nameif.c b/networking/nameif.c new file mode 100644 index 000000000..52aad2873 --- /dev/null +++ b/networking/nameif.c @@ -0,0 +1,170 @@ +/* vi: set sw=4 ts=4: */ +/* + * nameif.c - Naming Interfaces based on MAC address for busybox. + * + * Written 2000 by Andi Kleen. + * Busybox port 2002 by Nick Fedchik + * Glenn McGrath + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include +#include +#include + + +/* Older versions of net/if.h do not appear to define IF_NAMESIZE. */ +#ifndef IF_NAMESIZE +# ifdef IFNAMSIZ +# define IF_NAMESIZE IFNAMSIZ +# else +# define IF_NAMESIZE 16 +# endif +#endif + +/* take from linux/sockios.h */ +#define SIOCSIFNAME 0x8923 /* set interface name */ + +/* Octets in one Ethernet addr, from */ +#define ETH_ALEN 6 + +#ifndef ifr_newname +#define ifr_newname ifr_ifru.ifru_slave +#endif + +typedef struct mactable_s { + struct mactable_s *next; + struct mactable_s *prev; + char *ifname; + struct ether_addr *mac; +} mactable_t; + +/* Check ascii str_macaddr, convert and copy to *mac */ +static struct ether_addr *cc_macaddr(const char *str_macaddr) +{ + struct ether_addr *lmac, *mac; + + lmac = ether_aton(str_macaddr); + if (lmac == NULL) + bb_error_msg_and_die("cannot parse MAC %s", str_macaddr); + mac = xmalloc(ETH_ALEN); + memcpy(mac, lmac, ETH_ALEN); + + return mac; +} + +int nameif_main(int argc, char **argv) +{ + mactable_t *clist = NULL; + FILE *ifh; + const char *fname = "/etc/mactab"; + char *line; + int ctl_sk; + int if_index = 1; + mactable_t *ch; + + if (1 & getopt32(argc, argv, "sc:", &fname)) { + openlog(applet_name, 0, LOG_LOCAL0); + logmode = LOGMODE_SYSLOG; + } + + if ((argc - optind) & 1) + bb_show_usage(); + + if (optind < argc) { + char **a = argv + optind; + + while (*a) { + if (strlen(*a) > IF_NAMESIZE) + bb_error_msg_and_die("interface name '%s' " + "too long", *a); + ch = xzalloc(sizeof(mactable_t)); + ch->ifname = xstrdup(*a++); + ch->mac = cc_macaddr(*a++); + if (clist) + clist->prev = ch; + ch->next = clist; + clist = ch; + } + } else { + ifh = xfopen(fname, "r"); + + while ((line = xmalloc_fgets(ifh)) != NULL) { + char *line_ptr; + size_t name_length; + + line_ptr = line + strspn(line, " \t"); + if ((line_ptr[0] == '#') || (line_ptr[0] == '\n')) { + free(line); + continue; + } + name_length = strcspn(line_ptr, " \t"); + ch = xzalloc(sizeof(mactable_t)); + ch->ifname = xstrndup(line_ptr, name_length); + if (name_length > IF_NAMESIZE) + bb_error_msg_and_die("interface name '%s' " + "too long", ch->ifname); + line_ptr += name_length; + line_ptr += strspn(line_ptr, " \t"); + name_length = strspn(line_ptr, "0123456789ABCDEFabcdef:"); + line_ptr[name_length] = '\0'; + ch->mac = cc_macaddr(line_ptr); + if (clist) + clist->prev = ch; + ch->next = clist; + clist = ch; + free(line); + } + fclose(ifh); + } + + ctl_sk = xsocket(PF_INET, SOCK_DGRAM, 0); + + while (clist) { + struct ifreq ifr; + + memset(&ifr, 0, sizeof(struct ifreq)); + if_index++; + ifr.ifr_ifindex = if_index; + + /* Get ifname by index or die */ + if (ioctl(ctl_sk, SIOCGIFNAME, &ifr)) + break; + + /* Has this device hwaddr? */ + if (ioctl(ctl_sk, SIOCGIFHWADDR, &ifr)) + continue; + + /* Search for mac like in ifr.ifr_hwaddr.sa_data */ + for (ch = clist; ch; ch = ch->next) + if (!memcmp(ch->mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN)) + break; + + /* Nothing found for current ifr.ifr_hwaddr.sa_data */ + if (ch == NULL) + continue; + + strcpy(ifr.ifr_newname, ch->ifname); + if (ioctl(ctl_sk, SIOCSIFNAME, &ifr) < 0) + bb_perror_msg_and_die("cannot change ifname %s to %s", + ifr.ifr_name, ch->ifname); + + /* Remove list entry of renamed interface */ + if (ch->prev != NULL) { + (ch->prev)->next = ch->next; + } else { + clist = ch->next; + } + if (ch->next != NULL) + (ch->next)->prev = ch->prev; + if (ENABLE_FEATURE_CLEAN_UP) { + free(ch->ifname); + free(ch->mac); + free(ch); + } + } + + return 0; +} diff --git a/networking/nc.c b/networking/nc.c new file mode 100644 index 000000000..5fd9242cc --- /dev/null +++ b/networking/nc.c @@ -0,0 +1,201 @@ +/* vi: set sw=4 ts=4: */ +/* nc: mini-netcat - built from the ground up for LRP + * + * Copyright (C) 1998, 1999 Charles P. Wright + * Copyright (C) 1998 Dave Cinege + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +static void timeout(int signum) +{ + bb_error_msg_and_die("timed out"); +} + +int nc_main(int argc, char **argv) +{ + int sfd = 0; + int cfd = 0; + SKIP_NC_SERVER(const) unsigned do_listen = 0; + SKIP_NC_SERVER(const) unsigned lport = 0; + SKIP_NC_EXTRA (const) unsigned wsecs = 0; + SKIP_NC_EXTRA (const) unsigned delay = 0; + SKIP_NC_EXTRA (const int execparam = 0;) + USE_NC_EXTRA (char **execparam = NULL;) + struct sockaddr_in address; + fd_set readfds, testfds; + int opt; /* must be signed (getopt returns -1) */ + + memset(&address, 0, sizeof(address)); + + if (ENABLE_NC_SERVER || ENABLE_NC_EXTRA) { + /* getopt32 is _almost_ usable: + ** it cannot handle "... -e prog -prog-opt" */ + while ((opt = getopt(argc, argv, + "" USE_NC_SERVER("lp:") USE_NC_EXTRA("w:i:f:e:") )) > 0 + ) { + if (ENABLE_NC_SERVER && opt=='l') USE_NC_SERVER(do_listen++); + else if (ENABLE_NC_SERVER && opt=='p') USE_NC_SERVER(lport = bb_lookup_port(optarg, "tcp", 0)); + else if (ENABLE_NC_EXTRA && opt=='w') USE_NC_EXTRA( wsecs = xatou(optarg)); + else if (ENABLE_NC_EXTRA && opt=='i') USE_NC_EXTRA( delay = xatou(optarg)); + else if (ENABLE_NC_EXTRA && opt=='f') USE_NC_EXTRA( cfd = xopen(optarg, O_RDWR)); + else if (ENABLE_NC_EXTRA && opt=='e' && optind<=argc) { + /* We cannot just 'break'. We should let getopt finish. + ** Or else we won't be able to find where + ** 'host' and 'port' params are + ** (think "nc -w 60 host port -e prog"). */ + USE_NC_EXTRA( + char **p; + // +2: one for progname (optarg) and one for NULL + execparam = xzalloc(sizeof(char*) * (argc - optind + 2)); + p = execparam; + *p++ = optarg; + while (optind < argc) { + *p++ = argv[optind++]; + } + ) + /* optind points to argv[arvc] (NULL) now. + ** FIXME: we assume that getopt will not count options + ** possibly present on "-e prog args" and will not + ** include them into final value of optind + ** which is to be used ... */ + } else bb_show_usage(); + } + argv += optind; /* ... here! */ + argc -= optind; + // -l and -f don't mix + if (do_listen && cfd) bb_show_usage(); + // Listen or file modes need zero arguments, client mode needs 2 + opt = ((do_listen || cfd) ? 0 : 2); + if (argc != opt) + bb_show_usage(); + } else { + if (argc != 3) bb_show_usage(); + argc--; + argv++; + } + + if (wsecs) { + signal(SIGALRM, timeout); + alarm(wsecs); + } + + if (!cfd) { + sfd = xsocket(AF_INET, SOCK_STREAM, 0); + fcntl(sfd, F_SETFD, FD_CLOEXEC); + setsockopt_reuseaddr(sfd); + address.sin_family = AF_INET; + + // Set local port. + + if (lport != 0) { + address.sin_port = lport; + xbind(sfd, (struct sockaddr *) &address, sizeof(address)); + } + + if (do_listen) { + socklen_t addrlen = sizeof(address); + + xlisten(sfd, do_listen); + + // If we didn't specify a port number, query and print it to stderr. + + if (!lport) { + socklen_t len = sizeof(address); + getsockname(sfd, &address, &len); + fdprintf(2, "%d\n", SWAP_BE16(address.sin_port)); + } + repeatyness: + cfd = accept(sfd, (struct sockaddr *) &address, &addrlen); + if (cfd < 0) + bb_perror_msg_and_die("accept"); + + if (!execparam) close(sfd); + } else { + struct hostent *hostinfo; + hostinfo = xgethostbyname(argv[0]); + + address.sin_addr = *(struct in_addr *) *hostinfo->h_addr_list; + address.sin_port = bb_lookup_port(argv[1], "tcp", 0); + + if (connect(sfd, (struct sockaddr *) &address, sizeof(address)) < 0) + bb_perror_msg_and_die("connect"); + cfd = sfd; + } + } + + if (wsecs) { + alarm(0); + signal(SIGALRM, SIG_DFL); + } + + /* -e given? */ + if (execparam) { + if (cfd) { + signal(SIGCHLD, SIG_IGN); + dup2(cfd, 0); + close(cfd); + } + dup2(0, 1); + dup2(0, 2); + + // With more than one -l, repeatedly act as server. + + if (do_listen > 1 && vfork()) { + // This is a bit weird as cleanup goes, since we wind up with no + // stdin/stdout/stderr. But it's small and shouldn't hurt anything. + // We check for cfd == 0 above. + logmode = LOGMODE_NONE; + close(0); + close(1); + close(2); + + goto repeatyness; + } + USE_NC_EXTRA(execvp(execparam[0], execparam);) + /* Don't print stuff or it will go over the wire.... */ + _exit(127); + } + + // Select loop copying stdin to cfd, and cfd to stdout. + + FD_ZERO(&readfds); + FD_SET(cfd, &readfds); + FD_SET(STDIN_FILENO, &readfds); + + for (;;) { + int fd; + int ofd; + int nread; + + testfds = readfds; + + if (select(FD_SETSIZE, &testfds, NULL, NULL, NULL) < 0) + bb_perror_msg_and_die("select"); + + for (fd = 0; fd < FD_SETSIZE; fd++) { + if (FD_ISSET(fd, &testfds)) { + nread = safe_read(fd, bb_common_bufsiz1, + sizeof(bb_common_bufsiz1)); + + if (fd == cfd) { + if (nread<1) exit(0); + ofd = STDOUT_FILENO; + } else { + if (nread<1) { + // Close outgoing half-connection so they get EOF, but + // leave incoming alone so we can see response. + shutdown(cfd, 1); + FD_CLR(STDIN_FILENO, &readfds); + } + ofd = cfd; + } + + xwrite(ofd, bb_common_bufsiz1, nread); + if (delay > 0) sleep(delay); + } + } + } +} diff --git a/networking/netstat.c b/networking/netstat.c new file mode 100644 index 000000000..3dad57a40 --- /dev/null +++ b/networking/netstat.c @@ -0,0 +1,613 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini netstat implementation(s) for busybox + * based in part on the netstat implementation from net-tools. + * + * Copyright (C) 2002 by Bart Visscher + * + * 2002-04-20 + * IPV6 support added by Bart Visscher + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "inet_common.h" + +#ifdef CONFIG_ROUTE +extern void displayroutes(int noresolve, int netstatfmt); +#endif + +#define NETSTAT_CONNECTED 0x01 +#define NETSTAT_LISTENING 0x02 +#define NETSTAT_NUMERIC 0x04 +/* Must match getopt32 option string */ +#define NETSTAT_TCP 0x10 +#define NETSTAT_UDP 0x20 +#define NETSTAT_RAW 0x40 +#define NETSTAT_UNIX 0x80 +#define NETSTAT_ALLPROTO (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW|NETSTAT_UNIX) + +static int flags = NETSTAT_CONNECTED | NETSTAT_ALLPROTO; + +#define PROGNAME_WIDTHs PROGNAME_WIDTH1(PROGNAME_WIDTH) +#define PROGNAME_WIDTH1(s) PROGNAME_WIDTH2(s) +#define PROGNAME_WIDTH2(s) #s + +#define PRG_HASH_SIZE 211 + +enum { + TCP_ESTABLISHED = 1, + TCP_SYN_SENT, + TCP_SYN_RECV, + TCP_FIN_WAIT1, + TCP_FIN_WAIT2, + TCP_TIME_WAIT, + TCP_CLOSE, + TCP_CLOSE_WAIT, + TCP_LAST_ACK, + TCP_LISTEN, + TCP_CLOSING /* now a valid state */ +}; + +static const char * const tcp_state[] = +{ + "", + "ESTABLISHED", + "SYN_SENT", + "SYN_RECV", + "FIN_WAIT1", + "FIN_WAIT2", + "TIME_WAIT", + "CLOSE", + "CLOSE_WAIT", + "LAST_ACK", + "LISTEN", + "CLOSING" +}; + +typedef enum { + SS_FREE = 0, /* not allocated */ + SS_UNCONNECTED, /* unconnected to any socket */ + SS_CONNECTING, /* in process of connecting */ + SS_CONNECTED, /* connected to socket */ + SS_DISCONNECTING /* in process of disconnecting */ +} socket_state; + +#define SO_ACCEPTCON (1<<16) /* performed a listen */ +#define SO_WAITDATA (1<<17) /* wait data to read */ +#define SO_NOSPACE (1<<18) /* no space to write */ + +static char *get_sname(int port, const char *proto, int num) +{ + char *str=itoa(ntohs(port)); + if (num) { + } else { + struct servent *se=getservbyport(port,proto); + if (se) + str=se->s_name; + } + if (!port) { + str="*"; + } + return str; +} + +static void snprint_ip_port(char *ip_port, int size, struct sockaddr *addr, int port, char *proto, int numeric) +{ + char *port_name; + +#ifdef CONFIG_FEATURE_IPV6 + if (addr->sa_family == AF_INET6) { + INET6_rresolve(ip_port, size, (struct sockaddr_in6 *)addr, + (numeric&NETSTAT_NUMERIC) ? 0x0fff : 0); + } else +#endif + { + INET_rresolve(ip_port, size, (struct sockaddr_in *)addr, + 0x4000 | ((numeric&NETSTAT_NUMERIC) ? 0x0fff : 0), + 0xffffffff); + } + port_name=get_sname(htons(port), proto, numeric); + if ((strlen(ip_port) + strlen(port_name)) > 22) + ip_port[22 - strlen(port_name)] = '\0'; + ip_port+=strlen(ip_port); + strcat(ip_port, ":"); + strcat(ip_port, port_name); +} + +static void tcp_do_one(int lnr, const char *line) +{ + char local_addr[64], rem_addr[64]; + const char *state_str; + char more[512]; + int num, local_port, rem_port, d, state, timer_run, uid, timeout; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 localaddr, remaddr; + char addr6[INET6_ADDRSTRLEN]; + struct in6_addr in6; +#else + struct sockaddr_in localaddr, remaddr; +#endif + unsigned long rxq, txq, time_len, retr, inode; + + if (lnr == 0) + return; + + more[0] = '\0'; + num = sscanf(line, + "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n", + &d, local_addr, &local_port, + rem_addr, &rem_port, &state, + &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more); + + if (strlen(local_addr) > 8) { +#ifdef CONFIG_FEATURE_IPV6 + sscanf(local_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &localaddr.sin6_addr); + sscanf(rem_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &remaddr.sin6_addr); + localaddr.sin6_family = AF_INET6; + remaddr.sin6_family = AF_INET6; +#endif + } else { + sscanf(local_addr, "%X", + &((struct sockaddr_in *) &localaddr)->sin_addr.s_addr); + sscanf(rem_addr, "%X", + &((struct sockaddr_in *) &remaddr)->sin_addr.s_addr); + ((struct sockaddr *) &localaddr)->sa_family = AF_INET; + ((struct sockaddr *) &remaddr)->sa_family = AF_INET; + } + + if (num < 10) { + bb_error_msg("warning, got bogus tcp line"); + return; + } + state_str = tcp_state[state]; + if ((rem_port && (flags&NETSTAT_CONNECTED)) || + (!rem_port && (flags&NETSTAT_LISTENING))) + { + snprint_ip_port(local_addr, sizeof(local_addr), + (struct sockaddr *) &localaddr, local_port, + "tcp", flags&NETSTAT_NUMERIC); + + snprint_ip_port(rem_addr, sizeof(rem_addr), + (struct sockaddr *) &remaddr, rem_port, + "tcp", flags&NETSTAT_NUMERIC); + + printf("tcp %6ld %6ld %-23s %-23s %-12s\n", + rxq, txq, local_addr, rem_addr, state_str); + + } +} + +static void udp_do_one(int lnr, const char *line) +{ + char local_addr[64], rem_addr[64]; + char *state_str, more[512]; + int num, local_port, rem_port, d, state, timer_run, uid, timeout; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 localaddr, remaddr; + char addr6[INET6_ADDRSTRLEN]; + struct in6_addr in6; +#else + struct sockaddr_in localaddr, remaddr; +#endif + unsigned long rxq, txq, time_len, retr, inode; + + if (lnr == 0) + return; + + more[0] = '\0'; + num = sscanf(line, + "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n", + &d, local_addr, &local_port, + rem_addr, &rem_port, &state, + &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more); + + if (strlen(local_addr) > 8) { +#ifdef CONFIG_FEATURE_IPV6 + /* Demangle what the kernel gives us */ + sscanf(local_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &localaddr.sin6_addr); + sscanf(rem_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &remaddr.sin6_addr); + localaddr.sin6_family = AF_INET6; + remaddr.sin6_family = AF_INET6; +#endif + } else { + sscanf(local_addr, "%X", + &((struct sockaddr_in *) &localaddr)->sin_addr.s_addr); + sscanf(rem_addr, "%X", + &((struct sockaddr_in *) &remaddr)->sin_addr.s_addr); + ((struct sockaddr *) &localaddr)->sa_family = AF_INET; + ((struct sockaddr *) &remaddr)->sa_family = AF_INET; + } + + if (num < 10) { + bb_error_msg("warning, got bogus udp line"); + return; + } + switch (state) { + case TCP_ESTABLISHED: + state_str = "ESTABLISHED"; + break; + + case TCP_CLOSE: + state_str = ""; + break; + + default: + state_str = "UNKNOWN"; + break; + } + +#ifdef CONFIG_FEATURE_IPV6 +# define notnull(A) (((A.sin6_family == AF_INET6) && \ + ((A.sin6_addr.s6_addr32[0]) || \ + (A.sin6_addr.s6_addr32[1]) || \ + (A.sin6_addr.s6_addr32[2]) || \ + (A.sin6_addr.s6_addr32[3]))) || \ + ((A.sin6_family == AF_INET) && \ + ((struct sockaddr_in *) &A)->sin_addr.s_addr)) +#else +# define notnull(A) (A.sin_addr.s_addr) +#endif + if ((notnull(remaddr) && (flags&NETSTAT_CONNECTED)) || + (!notnull(remaddr) && (flags&NETSTAT_LISTENING))) + { + snprint_ip_port(local_addr, sizeof(local_addr), + (struct sockaddr *) &localaddr, local_port, + "udp", flags&NETSTAT_NUMERIC); + + snprint_ip_port(rem_addr, sizeof(rem_addr), + (struct sockaddr *) &remaddr, rem_port, + "udp", flags&NETSTAT_NUMERIC); + + printf("udp %6ld %6ld %-23s %-23s %-12s\n", + rxq, txq, local_addr, rem_addr, state_str); + + } +} + +static void raw_do_one(int lnr, const char *line) +{ + char local_addr[64], rem_addr[64]; + char *state_str, more[512]; + int num, local_port, rem_port, d, state, timer_run, uid, timeout; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 localaddr, remaddr; + char addr6[INET6_ADDRSTRLEN]; + struct in6_addr in6; +#else + struct sockaddr_in localaddr, remaddr; +#endif + unsigned long rxq, txq, time_len, retr, inode; + + if (lnr == 0) + return; + + more[0] = '\0'; + num = sscanf(line, + "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n", + &d, local_addr, &local_port, + rem_addr, &rem_port, &state, + &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more); + + if (strlen(local_addr) > 8) { +#ifdef CONFIG_FEATURE_IPV6 + sscanf(local_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &localaddr.sin6_addr); + sscanf(rem_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &remaddr.sin6_addr); + localaddr.sin6_family = AF_INET6; + remaddr.sin6_family = AF_INET6; +#endif + } else { + sscanf(local_addr, "%X", + &((struct sockaddr_in *) &localaddr)->sin_addr.s_addr); + sscanf(rem_addr, "%X", + &((struct sockaddr_in *) &remaddr)->sin_addr.s_addr); + ((struct sockaddr *) &localaddr)->sa_family = AF_INET; + ((struct sockaddr *) &remaddr)->sa_family = AF_INET; + } + + if (num < 10) { + bb_error_msg("warning, got bogus raw line"); + return; + } + state_str=itoa(state); + +#ifdef CONFIG_FEATURE_IPV6 +# define notnull(A) (((A.sin6_family == AF_INET6) && \ + ((A.sin6_addr.s6_addr32[0]) || \ + (A.sin6_addr.s6_addr32[1]) || \ + (A.sin6_addr.s6_addr32[2]) || \ + (A.sin6_addr.s6_addr32[3]))) || \ + ((A.sin6_family == AF_INET) && \ + ((struct sockaddr_in *) &A)->sin_addr.s_addr)) +#else +# define notnull(A) (A.sin_addr.s_addr) +#endif + if ((notnull(remaddr) && (flags&NETSTAT_CONNECTED)) || + (!notnull(remaddr) && (flags&NETSTAT_LISTENING))) + { + snprint_ip_port(local_addr, sizeof(local_addr), + (struct sockaddr *) &localaddr, local_port, + "raw", flags&NETSTAT_NUMERIC); + + snprint_ip_port(rem_addr, sizeof(rem_addr), + (struct sockaddr *) &remaddr, rem_port, + "raw", flags&NETSTAT_NUMERIC); + + printf("raw %6ld %6ld %-23s %-23s %-12s\n", + rxq, txq, local_addr, rem_addr, state_str); + + } +} + +#define HAS_INODE 1 + +static void unix_do_one(int nr, const char *line) +{ + static int has = 0; + char path[PATH_MAX], ss_flags[32]; + char *ss_proto, *ss_state, *ss_type; + int num, state, type, inode; + void *d; + unsigned long refcnt, proto, unix_flags; + + if (nr == 0) { + if (strstr(line, "Inode")) + has |= HAS_INODE; + return; + } + path[0] = '\0'; + num = sscanf(line, "%p: %lX %lX %lX %X %X %d %s", + &d, &refcnt, &proto, &unix_flags, &type, &state, &inode, path); + if (num < 6) { + bb_error_msg("warning, got bogus unix line"); + return; + } + if (!(has & HAS_INODE)) + snprintf(path,sizeof(path),"%d",inode); + + if ((flags&(NETSTAT_LISTENING|NETSTAT_CONNECTED))!=(NETSTAT_LISTENING|NETSTAT_CONNECTED)) { + if ((state == SS_UNCONNECTED) && (unix_flags & SO_ACCEPTCON)) { + if (!(flags&NETSTAT_LISTENING)) + return; + } else { + if (!(flags&NETSTAT_CONNECTED)) + return; + } + } + + switch (proto) { + case 0: + ss_proto = "unix"; + break; + + default: + ss_proto = "??"; + } + + switch (type) { + case SOCK_STREAM: + ss_type = "STREAM"; + break; + + case SOCK_DGRAM: + ss_type = "DGRAM"; + break; + + case SOCK_RAW: + ss_type = "RAW"; + break; + + case SOCK_RDM: + ss_type = "RDM"; + break; + + case SOCK_SEQPACKET: + ss_type = "SEQPACKET"; + break; + + default: + ss_type = "UNKNOWN"; + } + + switch (state) { + case SS_FREE: + ss_state = "FREE"; + break; + + case SS_UNCONNECTED: + /* + * Unconnected sockets may be listening + * for something. + */ + if (unix_flags & SO_ACCEPTCON) { + ss_state = "LISTENING"; + } else { + ss_state = ""; + } + break; + + case SS_CONNECTING: + ss_state = "CONNECTING"; + break; + + case SS_CONNECTED: + ss_state = "CONNECTED"; + break; + + case SS_DISCONNECTING: + ss_state = "DISCONNECTING"; + break; + + default: + ss_state = "UNKNOWN"; + } + + strcpy(ss_flags, "[ "); + if (unix_flags & SO_ACCEPTCON) + strcat(ss_flags, "ACC "); + if (unix_flags & SO_WAITDATA) + strcat(ss_flags, "W "); + if (unix_flags & SO_NOSPACE) + strcat(ss_flags, "N "); + + strcat(ss_flags, "]"); + + printf("%-5s %-6ld %-11s %-10s %-13s ", + ss_proto, refcnt, ss_flags, ss_type, ss_state); + if (has & HAS_INODE) + printf("%-6d ",inode); + else + printf("- "); + puts(path); +} + +#define _PATH_PROCNET_UDP "/proc/net/udp" +#define _PATH_PROCNET_UDP6 "/proc/net/udp6" +#define _PATH_PROCNET_TCP "/proc/net/tcp" +#define _PATH_PROCNET_TCP6 "/proc/net/tcp6" +#define _PATH_PROCNET_RAW "/proc/net/raw" +#define _PATH_PROCNET_RAW6 "/proc/net/raw6" +#define _PATH_PROCNET_UNIX "/proc/net/unix" + +static void do_info(const char *file, const char *name, void (*proc)(int, const char *)) +{ + int lnr = 0; + FILE *procinfo; + + procinfo = fopen(file, "r"); + if (procinfo == NULL) { + if (errno != ENOENT) { + perror(file); + } else { + bb_error_msg("no support for '%s' on this system", name); + } + } else { + do { + char *buffer = xmalloc_fgets(procinfo); + if (buffer) { + (proc)(lnr++, buffer); + free(buffer); + } + } while (!feof(procinfo)); + fclose(procinfo); + } +} + +/* + * Our main function. + */ + +int netstat_main(int argc, char **argv) +{ + enum { + OPT_extended = 0x4, + OPT_showroute = 0x100, + }; + unsigned opt; +#ifdef CONFIG_FEATURE_IPV6 + int inet = 1; + int inet6 = 1; +#else +# define inet 1 +# define inet6 0 +#endif + + /* Option string must match NETSTAT_xxx constants */ + opt = getopt32(argc, argv, "laentuwxr"); + if (opt & 0x1) { // -l + flags &= ~NETSTAT_CONNECTED; + flags |= NETSTAT_LISTENING; + } + if (opt & 0x2) flags |= NETSTAT_LISTENING | NETSTAT_CONNECTED; // -a + //if (opt & 0x4) // -e + if (opt & 0x8) flags |= NETSTAT_NUMERIC; // -n + //if (opt & 0x10) // -t: NETSTAT_TCP + //if (opt & 0x20) // -u: NETSTAT_UDP + //if (opt & 0x40) // -w: NETSTAT_RAW + //if (opt & 0x80) // -x: NETSTAT_UNIX + if (opt & OPT_showroute) { // -r +#ifdef CONFIG_ROUTE + displayroutes(flags & NETSTAT_NUMERIC, !(opt & OPT_extended)); + return 0; +#else + bb_error_msg_and_die("-r (display routing table) is not compiled in"); +#endif + } + + opt &= NETSTAT_ALLPROTO; + if (opt) { + flags &= ~NETSTAT_ALLPROTO; + flags |= opt; + } + if (flags & (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW)) { + printf("Active Internet connections "); /* xxx */ + + if ((flags&(NETSTAT_LISTENING|NETSTAT_CONNECTED))==(NETSTAT_LISTENING|NETSTAT_CONNECTED)) + printf("(servers and established)"); + else { + if (flags & NETSTAT_LISTENING) + printf("(only servers)"); + else + printf("(w/o servers)"); + } + printf("\nProto Recv-Q Send-Q Local Address Foreign Address State\n"); + } + if (inet && flags&NETSTAT_TCP) + do_info(_PATH_PROCNET_TCP,"AF INET (tcp)",tcp_do_one); +#ifdef CONFIG_FEATURE_IPV6 + if (inet6 && flags&NETSTAT_TCP) + do_info(_PATH_PROCNET_TCP6,"AF INET6 (tcp)",tcp_do_one); +#endif + if (inet && flags&NETSTAT_UDP) + do_info(_PATH_PROCNET_UDP,"AF INET (udp)",udp_do_one); +#ifdef CONFIG_FEATURE_IPV6 + if (inet6 && flags&NETSTAT_UDP) + do_info(_PATH_PROCNET_UDP6,"AF INET6 (udp)",udp_do_one); +#endif + if (inet && flags&NETSTAT_RAW) + do_info(_PATH_PROCNET_RAW,"AF INET (raw)",raw_do_one); +#ifdef CONFIG_FEATURE_IPV6 + if (inet6 && flags&NETSTAT_RAW) + do_info(_PATH_PROCNET_RAW6,"AF INET6 (raw)",raw_do_one); +#endif + if (flags&NETSTAT_UNIX) { + printf("Active UNIX domain sockets "); + if ((flags&(NETSTAT_LISTENING|NETSTAT_CONNECTED))==(NETSTAT_LISTENING|NETSTAT_CONNECTED)) + printf("(servers and established)"); + else { + if (flags&NETSTAT_LISTENING) + printf("(only servers)"); + else + printf("(w/o servers)"); + } + + printf("\nProto RefCnt Flags Type State I-Node Path\n"); + do_info(_PATH_PROCNET_UNIX,"AF UNIX",unix_do_one); + } + return 0; +} diff --git a/networking/nslookup.c b/networking/nslookup.c new file mode 100644 index 000000000..cc5ff95d6 --- /dev/null +++ b/networking/nslookup.c @@ -0,0 +1,149 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini nslookup implementation for busybox + * + * Copyright (C) 1999,2000 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * + * Correct default name server display and explicit name server option + * added by Ben Zeckel June 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "busybox.h" + +/* + * I'm only implementing non-interactive mode; + * I totally forgot nslookup even had an interactive mode. + */ + +/* Examples of 'standard' nslookup output + * $ nslookup yahoo.com + * Server: 128.193.0.10 + * Address: 128.193.0.10#53 + * + * Non-authoritative answer: + * Name: yahoo.com + * Address: 216.109.112.135 + * Name: yahoo.com + * Address: 66.94.234.13 + * + * $ nslookup 204.152.191.37 + * Server: 128.193.4.20 + * Address: 128.193.4.20#53 + * + * Non-authoritative answer: + * 37.191.152.204.in-addr.arpa canonical name = 37.32-27.191.152.204.in-addr.arpa. + * 37.32-27.191.152.204.in-addr.arpa name = zeus-pub2.kernel.org. + * + * Authoritative answers can be found from: + * 32-27.191.152.204.in-addr.arpa nameserver = ns1.kernel.org. + * 32-27.191.152.204.in-addr.arpa nameserver = ns2.kernel.org. + * 32-27.191.152.204.in-addr.arpa nameserver = ns3.kernel.org. + * ns1.kernel.org internet address = 140.211.167.34 + * ns2.kernel.org internet address = 204.152.191.4 + * ns3.kernel.org internet address = 204.152.191.36 + */ + +static int sockaddr_to_dotted(struct sockaddr *saddr, char *buf, int buflen) +{ + if (buflen <= 0) return -1; + buf[0] = '\0'; + if (saddr->sa_family == AF_INET) { + inet_ntop(AF_INET, &((struct sockaddr_in*)saddr)->sin_addr, buf, buflen); + return 0; + } + if (saddr->sa_family == AF_INET6) { + inet_ntop(AF_INET6, &((struct sockaddr_in6*)saddr)->sin6_addr, buf, buflen); + return 0; + } + return -1; +} + +static int print_host(const char *hostname, const char *header) +{ + char str[128]; /* IPv6 address will fit, hostnames hopefully too */ + struct addrinfo *result = NULL; + int rc; + struct addrinfo hint; + + memset(&hint, 0 , sizeof(hint)); + /* hint.ai_family = AF_UNSPEC; - zero anyway */ + /* Needed. Or else we will get each address thrice (or more) + * for each possible socket type (tcp,udp,raw...): */ + hint.ai_socktype = SOCK_STREAM; + // hint.ai_flags = AI_CANONNAME; + rc = getaddrinfo(hostname, NULL /*service*/, &hint, &result); + if (!rc) { + struct addrinfo *cur = result; + // printf("%s\n", cur->ai_canonname); ? + while (cur) { + sockaddr_to_dotted(cur->ai_addr, str, sizeof(str)); + printf("%s %s\nAddress: %s", header, hostname, str); + str[0] = ' '; + if (getnameinfo(cur->ai_addr, cur->ai_addrlen, str+1, sizeof(str)-1, NULL, 0, NI_NAMEREQD)) + str[0] = '\0'; + puts(str); + cur = cur->ai_next; + } + } else { + bb_error_msg("getaddrinfo('%s') failed: %s", hostname, gai_strerror(rc)); + } + freeaddrinfo(result); + return (rc != 0); +} + + +/* alter the global _res nameserver structure to use + an explicit dns server instead of what is in /etc/resolv.h */ +static void set_default_dns(char *server) +{ + struct in_addr server_in_addr; + + if (inet_pton(AF_INET, server, &server_in_addr) > 0) { + _res.nscount = 1; + _res.nsaddr_list[0].sin_addr = server_in_addr; + } +} + + +/* lookup the default nameserver and display it */ +static void server_print(void) +{ + char str[INET6_ADDRSTRLEN]; + + sockaddr_to_dotted((struct sockaddr*)&_res.nsaddr_list[0], str, sizeof(str)); + print_host(str, "Server:"); + puts(""); +} + + +int nslookup_main(int argc, char **argv) +{ + /* + * initialize DNS structure _res used in printing the default + * name server and in the explicit name server option feature. + */ + + res_init(); + + /* + * We allow 1 or 2 arguments. + * The first is the name to be looked up and the second is an + * optional DNS server with which to do the lookup. + * More than 3 arguments is an error to follow the pattern of the + * standard nslookup + */ + + if (argc < 2 || *argv[1] == '-' || argc > 3) + bb_show_usage(); + else if(argc == 3) + set_default_dns(argv[2]); + + server_print(); + return print_host(argv[1], "Name: "); +} + +/* $Id: nslookup.c,v 1.33 2004/10/13 07:25:01 andersen Exp $ */ diff --git a/networking/ping.c b/networking/ping.c new file mode 100644 index 000000000..658c01518 --- /dev/null +++ b/networking/ping.c @@ -0,0 +1,440 @@ +/* vi: set sw=4 ts=4: */ +/* + * $Id: ping.c,v 1.56 2004/03/15 08:28:48 andersen Exp $ + * Mini ping implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung + * + * Adapted from the ping in netkit-base 0.10: + * Copyright (c) 1989 The Regents of the University of California. + * Derived from software contributed to Berkeley by Mike Muuss. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +enum { + DEFDATALEN = 56, + MAXIPLEN = 60, + MAXICMPLEN = 76, + MAXPACKET = 65468, + MAX_DUP_CHK = (8 * 128), + MAXWAIT = 10, + PINGINTERVAL = 1 /* second */ +}; + +static void ping(const char *host); + +/* common routines */ + +static int in_cksum(unsigned short *buf, int sz) +{ + int nleft = sz; + int sum = 0; + unsigned short *w = buf; + unsigned short ans = 0; + + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + if (nleft == 1) { + *(unsigned char *) (&ans) = *(unsigned char *) w; + sum += ans; + } + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + ans = ~sum; + return ans; +} + +#ifndef CONFIG_FEATURE_FANCY_PING + +/* simple version */ + +static char *hostname; + +static void noresp(int ign) +{ + printf("No response from %s\n", hostname); + exit(EXIT_FAILURE); +} + +static void ping(const char *host) +{ + struct hostent *h; + struct sockaddr_in pingaddr; + struct icmp *pkt; + int pingsock, c; + char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN]; + + pingsock = create_icmp_socket(); + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin_family = AF_INET; + h = xgethostbyname(host); + memcpy(&pingaddr.sin_addr, h->h_addr, sizeof(pingaddr.sin_addr)); + hostname = h->h_name; + + pkt = (struct icmp *) packet; + memset(pkt, 0, sizeof(packet)); + pkt->icmp_type = ICMP_ECHO; + pkt->icmp_cksum = in_cksum((unsigned short *) pkt, sizeof(packet)); + + c = sendto(pingsock, packet, DEFDATALEN + ICMP_MINLEN, 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in)); + + if (c < 0) { + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + bb_perror_msg_and_die("sendto"); + } + + signal(SIGALRM, noresp); + alarm(5); /* give the host 5000ms to respond */ + /* listen for replies */ + while (1) { + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + + if ((c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + if (c >= 76) { /* ip + icmp */ + struct iphdr *iphdr = (struct iphdr *) packet; + + pkt = (struct icmp *) (packet + (iphdr->ihl << 2)); /* skip ip hdr */ + if (pkt->icmp_type == ICMP_ECHOREPLY) + break; + } + } + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + printf("%s is alive!\n", hostname); + return; +} + +int ping_main(int argc, char **argv) +{ + argc--; + argv++; + if (argc < 1) + bb_show_usage(); + ping(*argv); + return EXIT_SUCCESS; +} + +#else /* ! CONFIG_FEATURE_FANCY_PING */ + +/* full(er) version */ + +#define OPT_STRING "qc:s:I:" +enum { + OPT_QUIET = 1 << 0, +}; + +static struct sockaddr_in pingaddr; +static struct sockaddr_in sourceaddr; +static int pingsock = -1; +static unsigned datalen; /* intentionally uninitialized to work around gcc bug */ + +static unsigned long ntransmitted, nreceived, nrepeats, pingcount; +static int myid; +static unsigned long tmin = ULONG_MAX, tmax, tsum; +static char rcvd_tbl[MAX_DUP_CHK / 8]; + +static struct hostent *hostent; + +static void sendping(int); +static void pingstats(int); +static void unpack(char *, int, struct sockaddr_in *); + +#define A(bit) rcvd_tbl[(bit)>>3] /* identify byte in array */ +#define B(bit) (1 << ((bit) & 0x07)) /* identify bit in byte */ +#define SET(bit) (A(bit) |= B(bit)) +#define CLR(bit) (A(bit) &= (~B(bit))) +#define TST(bit) (A(bit) & B(bit)) + +/**************************************************************************/ + +static void pingstats(int junk) +{ + int status; + + signal(SIGINT, SIG_IGN); + + printf("\n--- %s ping statistics ---\n", hostent->h_name); + printf("%lu packets transmitted, ", ntransmitted); + printf("%lu packets received, ", nreceived); + if (nrepeats) + printf("%lu duplicates, ", nrepeats); + if (ntransmitted) + printf("%lu%% packet loss\n", + (ntransmitted - nreceived) * 100 / ntransmitted); + if (nreceived) + printf("round-trip min/avg/max = %lu.%lu/%lu.%lu/%lu.%lu ms\n", + tmin / 10, tmin % 10, + (tsum / (nreceived + nrepeats)) / 10, + (tsum / (nreceived + nrepeats)) % 10, tmax / 10, tmax % 10); + if (nreceived != 0) + status = EXIT_SUCCESS; + else + status = EXIT_FAILURE; + exit(status); +} + +static void sendping(int junk) +{ + struct icmp *pkt; + int i; + char packet[datalen + ICMP_MINLEN]; + + pkt = (struct icmp *) packet; + + pkt->icmp_type = ICMP_ECHO; + pkt->icmp_code = 0; + pkt->icmp_cksum = 0; + pkt->icmp_seq = htons(ntransmitted++); + pkt->icmp_id = myid; + CLR(ntohs(pkt->icmp_seq) % MAX_DUP_CHK); + + gettimeofday((struct timeval *) &pkt->icmp_dun, NULL); + pkt->icmp_cksum = in_cksum((unsigned short *) pkt, sizeof(packet)); + + i = sendto(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in)); + + if (i < 0) + bb_perror_msg_and_die("sendto"); + else if ((size_t)i != sizeof(packet)) + bb_error_msg_and_die("ping wrote %d chars; %d expected", i, + (int)sizeof(packet)); + + signal(SIGALRM, sendping); + if (pingcount == 0 || ntransmitted < pingcount) { /* schedule next in 1s */ + alarm(PINGINTERVAL); + } else { /* done, wait for the last ping to come back */ + /* todo, don't necessarily need to wait so long... */ + signal(SIGALRM, pingstats); + alarm(MAXWAIT); + } +} + +static char *icmp_type_name(int id) +{ + switch (id) { + case ICMP_ECHOREPLY: return "Echo Reply"; + case ICMP_DEST_UNREACH: return "Destination Unreachable"; + case ICMP_SOURCE_QUENCH: return "Source Quench"; + case ICMP_REDIRECT: return "Redirect (change route)"; + case ICMP_ECHO: return "Echo Request"; + case ICMP_TIME_EXCEEDED: return "Time Exceeded"; + case ICMP_PARAMETERPROB: return "Parameter Problem"; + case ICMP_TIMESTAMP: return "Timestamp Request"; + case ICMP_TIMESTAMPREPLY: return "Timestamp Reply"; + case ICMP_INFO_REQUEST: return "Information Request"; + case ICMP_INFO_REPLY: return "Information Reply"; + case ICMP_ADDRESS: return "Address Mask Request"; + case ICMP_ADDRESSREPLY: return "Address Mask Reply"; + default: return "unknown ICMP type"; + } +} + +static void unpack(char *buf, int sz, struct sockaddr_in *from) +{ + struct icmp *icmppkt; + struct iphdr *iphdr; + struct timeval tv, *tp; + int hlen, dupflag; + unsigned long triptime; + + gettimeofday(&tv, NULL); + + /* discard if too short */ + if (sz < (datalen + ICMP_MINLEN)) + return; + + /* check IP header */ + iphdr = (struct iphdr *) buf; + hlen = iphdr->ihl << 2; + sz -= hlen; + icmppkt = (struct icmp *) (buf + hlen); + if (icmppkt->icmp_id != myid) + return; /* not our ping */ + + if (icmppkt->icmp_type == ICMP_ECHOREPLY) { + u_int16_t recv_seq = ntohs(icmppkt->icmp_seq); + ++nreceived; + tp = (struct timeval *) icmppkt->icmp_data; + + if ((tv.tv_usec -= tp->tv_usec) < 0) { + --tv.tv_sec; + tv.tv_usec += 1000000; + } + tv.tv_sec -= tp->tv_sec; + + triptime = tv.tv_sec * 10000 + (tv.tv_usec / 100); + tsum += triptime; + if (triptime < tmin) + tmin = triptime; + if (triptime > tmax) + tmax = triptime; + + if (TST(recv_seq % MAX_DUP_CHK)) { + ++nrepeats; + --nreceived; + dupflag = 1; + } else { + SET(recv_seq % MAX_DUP_CHK); + dupflag = 0; + } + + if (option_mask32 & OPT_QUIET) + return; + + printf("%d bytes from %s: icmp_seq=%u", sz, + inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr), + recv_seq); + printf(" ttl=%d", iphdr->ttl); + printf(" time=%lu.%lu ms", triptime / 10, triptime % 10); + if (dupflag) + printf(" (DUP!)"); + puts(""); + } else + if (icmppkt->icmp_type != ICMP_ECHO) + bb_error_msg("warning: got ICMP %d (%s)", + icmppkt->icmp_type, icmp_type_name(icmppkt->icmp_type)); + fflush(stdout); +} + +static void ping(const char *host) +{ + char packet[datalen + MAXIPLEN + MAXICMPLEN]; + int sockopt; + + pingsock = create_icmp_socket(); + + if (sourceaddr.sin_addr.s_addr) { + xbind(pingsock, (struct sockaddr*)&sourceaddr, sizeof(sourceaddr)); + } + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin_family = AF_INET; + hostent = xgethostbyname(host); + if (hostent->h_addrtype != AF_INET) + bb_error_msg_and_die("unknown address type; only AF_INET is currently supported"); + + memcpy(&pingaddr.sin_addr, hostent->h_addr, sizeof(pingaddr.sin_addr)); + + /* enable broadcast pings */ + setsockopt_broadcast(pingsock); + + /* set recv buf for broadcast pings */ + sockopt = 48 * 1024; + setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, (char *) &sockopt, + sizeof(sockopt)); + + printf("PING %s (%s)", + hostent->h_name, + inet_ntoa(*(struct in_addr *) &pingaddr.sin_addr.s_addr)); + if (sourceaddr.sin_addr.s_addr) { + printf(" from %s", + inet_ntoa(*(struct in_addr *) &sourceaddr.sin_addr.s_addr)); + } + printf(": %d data bytes\n", datalen); + + signal(SIGINT, pingstats); + + /* start the ping's going ... */ + sendping(0); + + /* listen for replies */ + while (1) { + struct sockaddr_in from; + socklen_t fromlen = (socklen_t) sizeof(from); + int c; + + if ((c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + unpack(packet, c, &from); + if (pingcount > 0 && nreceived >= pingcount) + break; + } + pingstats(0); +} + +/* TODO: consolidate ether-wake.c, dnsd.c, ifupdown.c, nslookup.c + * versions of below thing. BTW we have far too many "%u.%u.%u.%u" too... +*/ +static int parse_nipquad(const char *str, struct sockaddr_in* addr) +{ + char dummy; + unsigned i1, i2, i3, i4; + if (sscanf(str, "%u.%u.%u.%u%c", + &i1, &i2, &i3, &i4, &dummy) == 4 + && ( (i1|i2|i3|i4) <= 0xff ) + ) { + uint8_t* ptr = (uint8_t*)&addr->sin_addr; + ptr[0] = i1; + ptr[1] = i2; + ptr[2] = i3; + ptr[3] = i4; + return 0; + } + return 1; /* error */ +} + +int ping_main(int argc, char **argv) +{ + char *opt_c, *opt_s, *opt_I; + + datalen = DEFDATALEN; /* initialized here rather than in global scope to work around gcc bug */ + + /* exactly one argument needed */ + opt_complementary = "=1"; + getopt32(argc, argv, OPT_STRING, &opt_c, &opt_s, &opt_I); + if (option_mask32 & 2) pingcount = xatoul(opt_c); // -c + if (option_mask32 & 4) datalen = xatou16(opt_s); // -s + if (option_mask32 & 8) { // -I +/* TODO: ping6 accepts iface too: + if_index = if_nametoindex(*argv); + if (!if_index) ... +make it true for ping. */ + if (parse_nipquad(opt_I, &sourceaddr)) + bb_show_usage(); + } + + myid = (int16_t) getpid(); + ping(argv[optind]); + return EXIT_SUCCESS; +} +#endif /* ! CONFIG_FEATURE_FANCY_PING */ diff --git a/networking/ping6.c b/networking/ping6.c new file mode 100644 index 000000000..9f0509e66 --- /dev/null +++ b/networking/ping6.c @@ -0,0 +1,480 @@ +/* vi: set sw=4 ts=4: */ +/* + * $Id: ping6.c,v 1.6 2004/03/15 08:28:48 andersen Exp $ + * Mini ping implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * This version of ping is adapted from the ping in netkit-base 0.10, + * which is: + * + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Muuss. + * + * Original copyright notice is retained at the end of this file. + * + * This version is an adaptation of ping.c from busybox. + * The code was modified by Bart Visscher + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* offsetof */ +#include "busybox.h" + +enum { + DEFDATALEN = 56, + MAXIPLEN = 60, + MAXICMPLEN = 76, + MAXPACKET = 65468, + MAX_DUP_CHK = (8 * 128), + MAXWAIT = 10, + PINGINTERVAL = 1 /* second */ +}; + +static void ping(const char *host); + +#ifndef CONFIG_FEATURE_FANCY_PING6 + +/* simple version */ + +static struct hostent *h; + +static void noresp(int ign) +{ + printf("No response from %s\n", h->h_name); + exit(EXIT_FAILURE); +} + +static void ping(const char *host) +{ + struct sockaddr_in6 pingaddr; + struct icmp6_hdr *pkt; + int pingsock, c; + int sockopt; + char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN]; + + pingsock = create_icmp6_socket(); + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin6_family = AF_INET6; + h = xgethostbyname2(host, AF_INET6); + memcpy(&pingaddr.sin6_addr, h->h_addr, sizeof(pingaddr.sin6_addr)); + + pkt = (struct icmp6_hdr *) packet; + memset(pkt, 0, sizeof(packet)); + pkt->icmp6_type = ICMP6_ECHO_REQUEST; + + sockopt = offsetof(struct icmp6_hdr, icmp6_cksum); + setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, (char *) &sockopt, + sizeof(sockopt)); + + c = sendto(pingsock, packet, DEFDATALEN + sizeof (struct icmp6_hdr), 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in6)); + + if (c < 0 || c != sizeof(packet)) { + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + bb_perror_msg_and_die("sendto"); + } + + signal(SIGALRM, noresp); + alarm(5); /* give the host 5000ms to respond */ + /* listen for replies */ + while (1) { + struct sockaddr_in6 from; + size_t fromlen = sizeof(from); + + if ((c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + if (c >= 8) { /* icmp6_hdr */ + pkt = (struct icmp6_hdr *) packet; + if (pkt->icmp6_type == ICMP6_ECHO_REPLY) + break; + } + } + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + printf("%s is alive!\n", h->h_name); + return; +} + +int ping6_main(int argc, char **argv) +{ + argc--; + argv++; + if (argc < 1) + bb_show_usage(); + ping(*argv); + return EXIT_SUCCESS; +} + +#else /* ! CONFIG_FEATURE_FANCY_PING6 */ + +/* full(er) version */ + +#define OPT_STRING "qvc:s:I:" +enum { + OPT_QUIET = 1 << 0, + OPT_VERBOSE = 1 << 1, +}; + +static struct sockaddr_in6 pingaddr; +static int pingsock = -1; +static unsigned datalen; /* intentionally uninitialized to work around gcc bug */ +static int if_index; + +static unsigned long ntransmitted, nreceived, nrepeats, pingcount; +static int myid; +static unsigned long tmin = ULONG_MAX, tmax, tsum; +static char rcvd_tbl[MAX_DUP_CHK / 8]; + +static struct hostent *hostent; + +static void sendping(int); +static void pingstats(int); +static void unpack(char *, int, struct sockaddr_in6 *, int); + +#define A(bit) rcvd_tbl[(bit)>>3] /* identify byte in array */ +#define B(bit) (1 << ((bit) & 0x07)) /* identify bit in byte */ +#define SET(bit) (A(bit) |= B(bit)) +#define CLR(bit) (A(bit) &= (~B(bit))) +#define TST(bit) (A(bit) & B(bit)) + +/**************************************************************************/ + +static void pingstats(int junk) +{ + int status; + + signal(SIGINT, SIG_IGN); + + printf("\n--- %s ping statistics ---\n", hostent->h_name); + printf("%lu packets transmitted, ", ntransmitted); + printf("%lu packets received, ", nreceived); + if (nrepeats) + printf("%lu duplicates, ", nrepeats); + if (ntransmitted) + printf("%lu%% packet loss\n", + (ntransmitted - nreceived) * 100 / ntransmitted); + if (nreceived) + printf("round-trip min/avg/max = %lu.%lu/%lu.%lu/%lu.%lu ms\n", + tmin / 10, tmin % 10, + (tsum / (nreceived + nrepeats)) / 10, + (tsum / (nreceived + nrepeats)) % 10, tmax / 10, tmax % 10); + if (nreceived != 0) + status = EXIT_SUCCESS; + else + status = EXIT_FAILURE; + exit(status); +} + +static void sendping(int junk) +{ + struct icmp6_hdr *pkt; + int i; + char packet[datalen + sizeof (struct icmp6_hdr)]; + + pkt = (struct icmp6_hdr *) packet; + + pkt->icmp6_type = ICMP6_ECHO_REQUEST; + pkt->icmp6_code = 0; + pkt->icmp6_cksum = 0; + pkt->icmp6_seq = htons(ntransmitted++); + pkt->icmp6_id = myid; + CLR(pkt->icmp6_seq % MAX_DUP_CHK); + + gettimeofday((struct timeval *) &pkt->icmp6_data8[4], NULL); + + i = sendto(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in6)); + + if (i < 0) + bb_perror_msg_and_die("sendto"); + else if ((size_t)i != sizeof(packet)) + bb_error_msg_and_die("ping wrote %d chars; %d expected", i, + (int)sizeof(packet)); + + signal(SIGALRM, sendping); + if (pingcount == 0 || ntransmitted < pingcount) { /* schedule next in 1s */ + alarm(PINGINTERVAL); + } else { /* done, wait for the last ping to come back */ + /* todo, don't necessarily need to wait so long... */ + signal(SIGALRM, pingstats); + alarm(MAXWAIT); + } +} + +/* RFC3542 changed some definitions from RFC2292 for no good reason, whee ! + * the newer 3542 uses a MLD_ prefix where as 2292 uses ICMP6_ prefix */ +#ifndef MLD_LISTENER_QUERY +# define MLD_LISTENER_QUERY ICMP6_MEMBERSHIP_QUERY +#endif +#ifndef MLD_LISTENER_REPORT +# define MLD_LISTENER_REPORT ICMP6_MEMBERSHIP_REPORT +#endif +#ifndef MLD_LISTENER_REDUCTION +# define MLD_LISTENER_REDUCTION ICMP6_MEMBERSHIP_REDUCTION +#endif +static char *icmp6_type_name(int id) +{ + switch (id) { + case ICMP6_DST_UNREACH: return "Destination Unreachable"; + case ICMP6_PACKET_TOO_BIG: return "Packet too big"; + case ICMP6_TIME_EXCEEDED: return "Time Exceeded"; + case ICMP6_PARAM_PROB: return "Parameter Problem"; + case ICMP6_ECHO_REPLY: return "Echo Reply"; + case ICMP6_ECHO_REQUEST: return "Echo Request"; + case MLD_LISTENER_QUERY: return "Listener Query"; + case MLD_LISTENER_REPORT: return "Listener Report"; + case MLD_LISTENER_REDUCTION: return "Listener Reduction"; + default: return "unknown ICMP type"; + } +} + +static void unpack(char *packet, int sz, struct sockaddr_in6 *from, int hoplimit) +{ + struct icmp6_hdr *icmppkt; + struct timeval tv, *tp; + int dupflag; + unsigned long triptime; + char buf[INET6_ADDRSTRLEN]; + + gettimeofday(&tv, NULL); + + /* discard if too short */ + if (sz < (datalen + sizeof(struct icmp6_hdr))) + return; + + icmppkt = (struct icmp6_hdr *) packet; + if (icmppkt->icmp6_id != myid) + return; /* not our ping */ + + if (icmppkt->icmp6_type == ICMP6_ECHO_REPLY) { + ++nreceived; + tp = (struct timeval *) &icmppkt->icmp6_data8[4]; + + if ((tv.tv_usec -= tp->tv_usec) < 0) { + --tv.tv_sec; + tv.tv_usec += 1000000; + } + tv.tv_sec -= tp->tv_sec; + + triptime = tv.tv_sec * 10000 + (tv.tv_usec / 100); + tsum += triptime; + if (triptime < tmin) + tmin = triptime; + if (triptime > tmax) + tmax = triptime; + + if (TST(icmppkt->icmp6_seq % MAX_DUP_CHK)) { + ++nrepeats; + --nreceived; + dupflag = 1; + } else { + SET(icmppkt->icmp6_seq % MAX_DUP_CHK); + dupflag = 0; + } + + if (option_mask32 & OPT_QUIET) + return; + + printf("%d bytes from %s: icmp6_seq=%u", sz, + inet_ntop(AF_INET6, &pingaddr.sin6_addr, + buf, sizeof(buf)), + icmppkt->icmp6_seq); + printf(" ttl=%d time=%lu.%lu ms", hoplimit, + triptime / 10, triptime % 10); + if (dupflag) + printf(" (DUP!)"); + puts(""); + } else + if (icmppkt->icmp6_type != ICMP6_ECHO_REQUEST) + bb_error_msg("warning: got ICMP %d (%s)", + icmppkt->icmp6_type, icmp6_type_name(icmppkt->icmp6_type)); +} + +static void ping(const char *host) +{ + char packet[datalen + MAXIPLEN + MAXICMPLEN]; + char buf[INET6_ADDRSTRLEN]; + int sockopt; + struct msghdr msg; + struct sockaddr_in6 from; + struct iovec iov; + char control_buf[CMSG_SPACE(36)]; + + pingsock = create_icmp6_socket(); + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin6_family = AF_INET6; + hostent = xgethostbyname2(host, AF_INET6); + if (hostent->h_addrtype != AF_INET6) + bb_error_msg_and_die("unknown address type; only AF_INET6 is currently supported"); + + memcpy(&pingaddr.sin6_addr, hostent->h_addr, sizeof(pingaddr.sin6_addr)); + +#ifdef ICMP6_FILTER + { + struct icmp6_filter filt; + if (!(option_mask32 & OPT_VERBOSE)) { + ICMP6_FILTER_SETBLOCKALL(&filt); + ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filt); + } else { + ICMP6_FILTER_SETPASSALL(&filt); + } + if (setsockopt(pingsock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, + sizeof(filt)) < 0) + bb_error_msg_and_die("setsockopt(ICMP6_FILTER)"); + } +#endif /*ICMP6_FILTER*/ + + /* enable broadcast pings */ + setsockopt_broadcast(pingsock); + + /* set recv buf for broadcast pings */ + sockopt = 48 * 1024; + setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, (char *) &sockopt, + sizeof(sockopt)); + + sockopt = offsetof(struct icmp6_hdr, icmp6_cksum); + setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, (char *) &sockopt, + sizeof(sockopt)); + + sockopt = 1; + setsockopt(pingsock, SOL_IPV6, IPV6_HOPLIMIT, (char *) &sockopt, + sizeof(sockopt)); + + if (if_index) + pingaddr.sin6_scope_id = if_index; + + printf("PING %s (%s): %d data bytes\n", + hostent->h_name, + inet_ntop(AF_INET6, &pingaddr.sin6_addr, + buf, sizeof(buf)), + datalen); + + signal(SIGINT, pingstats); + + /* start the ping's going ... */ + sendping(0); + + /* listen for replies */ + msg.msg_name = &from; + msg.msg_namelen = sizeof(from); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control_buf; + iov.iov_base = packet; + iov.iov_len = sizeof(packet); + while (1) { + int c; + struct cmsghdr *cmsgptr = NULL; + int hoplimit = -1; + msg.msg_controllen = sizeof(control_buf); + + if ((c = recvmsg(pingsock, &msg, 0)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; + cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) { + if (cmsgptr->cmsg_level == SOL_IPV6 && + cmsgptr->cmsg_type == IPV6_HOPLIMIT ) { + hoplimit = *(int*)CMSG_DATA(cmsgptr); + } + } + unpack(packet, c, &from, hoplimit); + if (pingcount > 0 && nreceived >= pingcount) + break; + } + pingstats(0); +} + +int ping6_main(int argc, char **argv) +{ + char *opt_c, *opt_s, *opt_I; + + datalen = DEFDATALEN; /* initialized here rather than in global scope to work around gcc bug */ + + /* exactly one argument needed, -v and -q don't mix */ + opt_complementary = "=1:q--v:v--q"; + getopt32(argc, argv, OPT_STRING, &opt_c, &opt_s, &opt_I); + if (option_mask32 & 4) pingcount = xatoul(opt_c); // -c + if (option_mask32 & 8) datalen = xatou16(opt_s); // -s + if (option_mask32 & 0x10) { // -I + if_index = if_nametoindex(opt_I); + if (!if_index) + bb_error_msg_and_die( + "%s: invalid interface name", opt_I); + } + + myid = (int16_t)getpid(); + ping(argv[optind]); + return EXIT_SUCCESS; +} +#endif /* ! CONFIG_FEATURE_FANCY_PING6 */ + +/* + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Muuss. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/networking/route.c b/networking/route.c new file mode 100644 index 000000000..a8926f44e --- /dev/null +++ b/networking/route.c @@ -0,0 +1,703 @@ +/* vi: set sw=4 ts=4: */ +/* route + * + * Similar to the standard Unix route, but with only the necessary + * parts for AF_INET and AF_INET6 + * + * Bjorn Wesen, Axis Communications AB + * + * Author of the original route: + * Fred N. van Kempen, + * (derived from FvK's 'route.c 1.70 01/04/94') + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * $Id: route.c,v 1.26 2004/03/19 23:27:08 mjn3 Exp $ + * + * displayroute() code added by Vladimir N. Oleynik + * adjustments by Larry Doolittle + * + * IPV6 support added by Bart Visscher + */ + +/* 2004/03/09 Manuel Novoa III + * + * Rewritten to fix several bugs, add additional error checking, and + * remove ridiculous amounts of bloat. + */ + +#include "busybox.h" +#include "inet_common.h" +#include +#include +#include + + +#ifndef RTF_UP +/* Keep this in sync with /usr/src/linux/include/linux/route.h */ +#define RTF_UP 0x0001 /* route usable */ +#define RTF_GATEWAY 0x0002 /* destination is a gateway */ +#define RTF_HOST 0x0004 /* host entry (net otherwise) */ +#define RTF_REINSTATE 0x0008 /* reinstate route after tmout */ +#define RTF_DYNAMIC 0x0010 /* created dyn. (by redirect) */ +#define RTF_MODIFIED 0x0020 /* modified dyn. (by redirect) */ +#define RTF_MTU 0x0040 /* specific MTU for this route */ +#ifndef RTF_MSS +#define RTF_MSS RTF_MTU /* Compatibility :-( */ +#endif +#define RTF_WINDOW 0x0080 /* per route window clamping */ +#define RTF_IRTT 0x0100 /* Initial round trip time */ +#define RTF_REJECT 0x0200 /* Reject route */ +#endif + +#if defined (SIOCADDRTOLD) || defined (RTF_IRTT) /* route */ +#define HAVE_NEW_ADDRT 1 +#endif + +#if HAVE_NEW_ADDRT +#define mask_in_addr(x) (((struct sockaddr_in *)&((x).rt_genmask))->sin_addr.s_addr) +#define full_mask(x) (x) +#else +#define mask_in_addr(x) ((x).rt_genmask) +#define full_mask(x) (((struct sockaddr_in *)&(x))->sin_addr.s_addr) +#endif + +/* The RTACTION entries must agree with tbl_verb[] below! */ +#define RTACTION_ADD 1 +#define RTACTION_DEL 2 + +/* For the various tbl_*[] arrays, the 1st byte is the offset to + * the next entry and the 2nd byte is return value. */ + +#define NET_FLAG 1 +#define HOST_FLAG 2 + +/* We remap '-' to '#' to avoid problems with getopt. */ +static const char tbl_hash_net_host[] = + "\007\001#net\0" +/* "\010\002#host\0" */ + "\007\002#host" /* Since last, we can save a byte. */ +; + +#define KW_TAKES_ARG 020 +#define KW_SETS_FLAG 040 + +#define KW_IPVx_METRIC 020 +#define KW_IPVx_NETMASK 021 +#define KW_IPVx_GATEWAY 022 +#define KW_IPVx_MSS 023 +#define KW_IPVx_WINDOW 024 +#define KW_IPVx_IRTT 025 +#define KW_IPVx_DEVICE 026 + +#define KW_IPVx_FLAG_ONLY 040 +#define KW_IPVx_REJECT 040 +#define KW_IPVx_MOD 041 +#define KW_IPVx_DYN 042 +#define KW_IPVx_REINSTATE 043 + +static const char tbl_ipvx[] = + /* 020 is the "takes an arg" bit */ +#if HAVE_NEW_ADDRT + "\011\020metric\0" +#endif + "\012\021netmask\0" + "\005\022gw\0" + "\012\022gateway\0" + "\006\023mss\0" + "\011\024window\0" +#ifdef RTF_IRTT + "\007\025irtt\0" +#endif + "\006\026dev\0" + "\011\026device\0" + /* 040 is the "sets a flag" bit - MUST match flags_ipvx[] values below. */ +#ifdef RTF_REJECT + "\011\040reject\0" +#endif + "\006\041mod\0" + "\006\042dyn\0" +/* "\014\043reinstate\0" */ + "\013\043reinstate" /* Since last, we can save a byte. */ +; + +static const int flags_ipvx[] = { /* MUST match tbl_ipvx[] values above. */ +#ifdef RTF_REJECT + RTF_REJECT, +#endif + RTF_MODIFIED, + RTF_DYNAMIC, + RTF_REINSTATE +}; + +static int kw_lookup(const char *kwtbl, char ***pargs) +{ + if (**pargs) { + do { + if (strcmp(kwtbl+2, **pargs) == 0) { /* Found a match. */ + *pargs += 1; + if (kwtbl[1] & KW_TAKES_ARG) { + if (!**pargs) { /* No more args! */ + bb_show_usage(); + } + *pargs += 1; /* Calling routine will use args[-1]. */ + } + return kwtbl[1]; + } + kwtbl += *kwtbl; + } while (*kwtbl); + } + return 0; +} + +/* Add or delete a route, depending on action. */ + +static void INET_setroute(int action, char **args) +{ + struct rtentry rt; + const char *netmask = NULL; + int skfd, isnet, xflag; + + /* Grab the -net or -host options. Remember they were transformed. */ + xflag = kw_lookup(tbl_hash_net_host, &args); + + /* If we did grab -net or -host, make sure we still have an arg left. */ + if (*args == NULL) { + bb_show_usage(); + } + + /* Clean out the RTREQ structure. */ + memset((char *) &rt, 0, sizeof(struct rtentry)); + + { + const char *target = *args++; + char *prefix; + + /* recognize x.x.x.x/mask format. */ + prefix = strchr(target, '/'); + if(prefix) { + int prefix_len; + + prefix_len = xatoul_range(prefix+1, 0, 32); + mask_in_addr(rt) = htonl( ~ (0xffffffffUL >> prefix_len)); + *prefix = '\0'; +#if HAVE_NEW_ADDRT + rt.rt_genmask.sa_family = AF_INET; +#endif + } else { + /* Default netmask. */ + netmask = bb_str_default; + } + /* Prefer hostname lookup is -host flag (xflag==1) was given. */ + isnet = INET_resolve(target, (struct sockaddr_in *) &rt.rt_dst, + (xflag & HOST_FLAG)); + if (isnet < 0) { + bb_error_msg_and_die("resolving %s", target); + } + if(prefix) { + /* do not destroy prefix for process args */ + *prefix = '/'; + } + } + + if (xflag) { /* Reinit isnet if -net or -host was specified. */ + isnet = (xflag & NET_FLAG); + } + + /* Fill in the other fields. */ + rt.rt_flags = ((isnet) ? RTF_UP : (RTF_UP | RTF_HOST)); + + while (*args) { + int k = kw_lookup(tbl_ipvx, &args); + const char *args_m1 = args[-1]; + + if (k & KW_IPVx_FLAG_ONLY) { + rt.rt_flags |= flags_ipvx[k & 3]; + continue; + } + +#if HAVE_NEW_ADDRT + if (k == KW_IPVx_METRIC) { + rt.rt_metric = xatoul(args_m1) + 1; + continue; + } +#endif + + if (k == KW_IPVx_NETMASK) { + struct sockaddr mask; + + if (mask_in_addr(rt)) { + bb_show_usage(); + } + + netmask = args_m1; + isnet = INET_resolve(netmask, (struct sockaddr_in *) &mask, 0); + if (isnet < 0) { + bb_error_msg_and_die("resolving %s", netmask); + } + rt.rt_genmask = full_mask(mask); + continue; + } + + if (k == KW_IPVx_GATEWAY) { + if (rt.rt_flags & RTF_GATEWAY) { + bb_show_usage(); + } + + isnet = INET_resolve(args_m1, + (struct sockaddr_in *) &rt.rt_gateway, 1); + rt.rt_flags |= RTF_GATEWAY; + + if (isnet) { + if (isnet < 0) { + bb_error_msg_and_die("resolving %s", args_m1); + } + bb_error_msg_and_die("gateway %s is a NETWORK", args_m1); + } + continue; + } + + if (k == KW_IPVx_MSS) { /* Check valid MSS bounds. */ + rt.rt_flags |= RTF_MSS; + rt.rt_mss = xatoul_range(args_m1, 64, 32768); + continue; + } + + if (k == KW_IPVx_WINDOW) { /* Check valid window bounds. */ + rt.rt_flags |= RTF_WINDOW; + rt.rt_window = xatoul_range(args_m1, 128, INT_MAX); + continue; + } + +#ifdef RTF_IRTT + if (k == KW_IPVx_IRTT) { + rt.rt_flags |= RTF_IRTT; + rt.rt_irtt = xatoul(args_m1); + rt.rt_irtt *= (sysconf(_SC_CLK_TCK) / 100); /* FIXME */ +#if 0 /* FIXME: do we need to check anything of this? */ + if (rt.rt_irtt < 1 || rt.rt_irtt > (120 * HZ)) { + bb_error_msg_and_die("bad irtt"); + } +#endif + continue; + } +#endif + + /* Device is special in that it can be the last arg specified + * and doesn't requre the dev/device keyword in that case. */ + if (!rt.rt_dev && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) { + /* Don't use args_m1 here since args may have changed! */ + rt.rt_dev = args[-1]; + continue; + } + + /* Nothing matched. */ + bb_show_usage(); + } + +#ifdef RTF_REJECT + if ((rt.rt_flags & RTF_REJECT) && !rt.rt_dev) { + rt.rt_dev = "lo"; + } +#endif + + /* sanity checks.. */ + if (mask_in_addr(rt)) { + unsigned long mask = mask_in_addr(rt); + + mask = ~ntohl(mask); + if ((rt.rt_flags & RTF_HOST) && mask != 0xffffffff) { + bb_error_msg_and_die("netmask %.8x and host route conflict", + (unsigned int) mask); + } + if (mask & (mask + 1)) { + bb_error_msg_and_die("bogus netmask %s", netmask); + } + mask = ((struct sockaddr_in *) &rt.rt_dst)->sin_addr.s_addr; + if (mask & ~mask_in_addr(rt)) { + bb_error_msg_and_die("netmask and route address conflict"); + } + } + + /* Fill out netmask if still unset */ + if ((action == RTACTION_ADD) && (rt.rt_flags & RTF_HOST)) { + mask_in_addr(rt) = 0xffffffff; + } + + /* Create a socket to the INET kernel. */ + skfd = xsocket(AF_INET, SOCK_DGRAM, 0); + + if (ioctl(skfd, ((action==RTACTION_ADD) ? SIOCADDRT : SIOCDELRT), &rt)<0) { + bb_perror_msg_and_die("SIOC[ADD|DEL]RT"); + } + + if (ENABLE_FEATURE_CLEAN_UP) close(skfd); +} + +#ifdef CONFIG_FEATURE_IPV6 + +static void INET6_setroute(int action, char **args) +{ + struct sockaddr_in6 sa6; + struct in6_rtmsg rt; + int prefix_len, skfd; + const char *devname; + + /* We know args isn't NULL from the check in route_main. */ + const char *target = *args++; + + if (strcmp(target, bb_str_default) == 0) { + prefix_len = 0; + memset(&sa6, 0, sizeof(sa6)); + } else { + char *cp; + if ((cp = strchr(target, '/'))) { /* Yes... const to non is ok. */ + *cp = 0; + prefix_len = xatoul_range(cp+1, 0, 128); + } else { + prefix_len = 128; + } + if (INET6_resolve(target, (struct sockaddr_in6 *) &sa6) < 0) { + bb_error_msg_and_die("resolving %s", target); + } + } + + /* Clean out the RTREQ structure. */ + memset((char *) &rt, 0, sizeof(struct in6_rtmsg)); + + memcpy(&rt.rtmsg_dst, sa6.sin6_addr.s6_addr, sizeof(struct in6_addr)); + + /* Fill in the other fields. */ + rt.rtmsg_dst_len = prefix_len; + rt.rtmsg_flags = ((prefix_len == 128) ? (RTF_UP|RTF_HOST) : RTF_UP); + rt.rtmsg_metric = 1; + + devname = NULL; + + while (*args) { + int k = kw_lookup(tbl_ipvx, &args); + const char *args_m1 = args[-1]; + + if ((k == KW_IPVx_MOD) || (k == KW_IPVx_DYN)) { + rt.rtmsg_flags |= flags_ipvx[k & 3]; + continue; + } + + if (k == KW_IPVx_METRIC) { + rt.rtmsg_metric = xatoul(args_m1); + continue; + } + + if (k == KW_IPVx_GATEWAY) { + if (rt.rtmsg_flags & RTF_GATEWAY) { + bb_show_usage(); + } + + if (INET6_resolve(args_m1, (struct sockaddr_in6 *) &sa6) < 0) { + bb_error_msg_and_die("resolving %s", args_m1); + } + memcpy(&rt.rtmsg_gateway, sa6.sin6_addr.s6_addr, + sizeof(struct in6_addr)); + rt.rtmsg_flags |= RTF_GATEWAY; + continue; + } + + /* Device is special in that it can be the last arg specified + * and doesn't requre the dev/device keyword in that case. */ + if (!devname && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) { + /* Don't use args_m1 here since args may have changed! */ + devname = args[-1]; + continue; + } + + /* Nothing matched. */ + bb_show_usage(); + } + + /* Create a socket to the INET6 kernel. */ + skfd = xsocket(AF_INET6, SOCK_DGRAM, 0); + + rt.rtmsg_ifindex = 0; + + if (devname) { + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, devname, sizeof(ifr.ifr_name)); + + if (ioctl(skfd, SIOGIFINDEX, &ifr) < 0) { + bb_perror_msg_and_die("SIOGIFINDEX"); + } + rt.rtmsg_ifindex = ifr.ifr_ifindex; + } + + /* Tell the kernel to accept this route. */ + if (ioctl(skfd, ((action==RTACTION_ADD) ? SIOCADDRT : SIOCDELRT), &rt)<0) { + bb_perror_msg_and_die("SIOC[ADD|DEL]RT"); + } + + if (ENABLE_FEATURE_CLEAN_UP) close(skfd); +} +#endif + +static const unsigned int flagvals[] = { /* Must agree with flagchars[]. */ + RTF_GATEWAY, + RTF_HOST, + RTF_REINSTATE, + RTF_DYNAMIC, + RTF_MODIFIED, +#ifdef CONFIG_FEATURE_IPV6 + RTF_DEFAULT, + RTF_ADDRCONF, + RTF_CACHE +#endif +}; + +#define IPV4_MASK (RTF_GATEWAY|RTF_HOST|RTF_REINSTATE|RTF_DYNAMIC|RTF_MODIFIED) +#define IPV6_MASK (RTF_GATEWAY|RTF_HOST|RTF_DEFAULT|RTF_ADDRCONF|RTF_CACHE) + +static const char flagchars[] = /* Must agree with flagvals[]. */ + "GHRDM" +#ifdef CONFIG_FEATURE_IPV6 + "DAC" +#endif +; + +static +#ifndef CONFIG_FEATURE_IPV6 +__inline +#endif +void set_flags(char *flagstr, int flags) +{ + int i; + + *flagstr++ = 'U'; + + for (i=0 ; (*flagstr = flagchars[i]) != 0 ; i++) { + if (flags & flagvals[i]) { + ++flagstr; + } + } +} + +/* also used in netstat */ +void displayroutes(int noresolve, int netstatfmt); +void displayroutes(int noresolve, int netstatfmt) +{ + char devname[64], flags[16], sdest[16], sgw[16]; + unsigned long int d, g, m; + int flgs, ref, use, metric, mtu, win, ir; + struct sockaddr_in s_addr; + struct in_addr mask; + + FILE *fp = xfopen("/proc/net/route", "r"); + + printf("Kernel IP routing table\n" + "Destination Gateway Genmask" + " Flags %s Iface\n", + netstatfmt ? " MSS Window irtt" : "Metric Ref Use"); + + if (fscanf(fp, "%*[^\n]\n") < 0) { /* Skip the first line. */ + goto ERROR; /* Empty or missing line, or read error. */ + } + while (1) { + int r; + r = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n", + devname, &d, &g, &flgs, &ref, &use, &metric, &m, + &mtu, &win, &ir); + if (r != 11) { + if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */ + break; + } + ERROR: + bb_error_msg_and_die("fscanf"); + } + + if (!(flgs & RTF_UP)) { /* Skip interfaces that are down. */ + continue; + } + + set_flags(flags, (flgs & IPV4_MASK)); +#ifdef RTF_REJECT + if (flgs & RTF_REJECT) { + flags[0] = '!'; + } +#endif + + memset(&s_addr, 0, sizeof(struct sockaddr_in)); + s_addr.sin_family = AF_INET; + s_addr.sin_addr.s_addr = d; + INET_rresolve(sdest, sizeof(sdest), &s_addr, + (noresolve | 0x8000), m); /* Default instead of *. */ + + s_addr.sin_addr.s_addr = g; + INET_rresolve(sgw, sizeof(sgw), &s_addr, + (noresolve | 0x4000), m); /* Host instead of net. */ + + mask.s_addr = m; + printf("%-16s%-16s%-16s%-6s", sdest, sgw, inet_ntoa(mask), flags); + if (netstatfmt) { + printf("%5d %-5d %6d %s\n", mtu, win, ir, devname); + } else { + printf("%-6d %-2d %7d %s\n", metric, ref, use, devname); + } + } +} + +#ifdef CONFIG_FEATURE_IPV6 + +static void INET6_displayroutes(int noresolve) +{ + char addr6[128], naddr6[128]; + /* In addr6x, we store both 40-byte ':'-delimited ipv6 addresses. + * We read the non-delimited strings into the tail of the buffer + * using fscanf and then modify the buffer by shifting forward + * while inserting ':'s and the nul terminator for the first string. + * Hence the strings are at addr6x and addr6x+40. This generates + * _much_ less code than the previous (upstream) approach. */ + char addr6x[80]; + char iface[16], flags[16]; + int iflags, metric, refcnt, use, prefix_len, slen; + struct sockaddr_in6 snaddr6; + + FILE *fp = xfopen("/proc/net/ipv6_route", "r"); + + printf("Kernel IPv6 routing table\n%-44s%-40s" + "Flags Metric Ref Use Iface\n", + "Destination", "Next Hop"); + + while (1) { + int r; + r = fscanf(fp, "%32s%x%*s%x%32s%x%x%x%x%s\n", + addr6x+14, &prefix_len, &slen, addr6x+40+7, + &metric, &use, &refcnt, &iflags, iface); + if (r != 9) { + if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */ + break; + } + ERROR: + bb_error_msg_and_die("fscanf"); + } + + /* Do the addr6x shift-and-insert changes to ':'-delimit addresses. + * For now, always do this to validate the proc route format, even + * if the interface is down. */ + { + int i = 0; + char *p = addr6x+14; + + do { + if (!*p) { + if (i==40) { /* nul terminator for 1st address? */ + addr6x[39] = 0; /* Fixup... need 0 instead of ':'. */ + ++p; /* Skip and continue. */ + continue; + } + goto ERROR; + } + addr6x[i++] = *p++; + if (!((i+1)%5)) { + addr6x[i++] = ':'; + } + } while (i < 40+28+7); + } + + if (!(iflags & RTF_UP)) { /* Skip interfaces that are down. */ + continue; + } + + set_flags(flags, (iflags & IPV6_MASK)); + + r = 0; + do { + inet_pton(AF_INET6, addr6x + r, + (struct sockaddr *) &snaddr6.sin6_addr); + snaddr6.sin6_family = AF_INET6; + INET6_rresolve(naddr6, sizeof(naddr6), + (struct sockaddr_in6 *) &snaddr6, + 0x0fff /* Apparently, upstream never resolves. */ + ); + + if (!r) { /* 1st pass */ + snprintf(addr6, sizeof(addr6), "%s/%d", naddr6, prefix_len); + r += 40; + } else { /* 2nd pass */ + /* Print the info. */ + printf("%-43s %-39s %-5s %-6d %-2d %7d %-8s\n", + addr6, naddr6, flags, metric, refcnt, use, iface); + break; + } + } while (1); + } +} + +#endif + +#define ROUTE_OPT_A 0x01 +#define ROUTE_OPT_n 0x02 +#define ROUTE_OPT_e 0x04 +#define ROUTE_OPT_INET6 0x08 /* Not an actual option. See below. */ + +/* 1st byte is offset to next entry offset. 2nd byte is return value. */ +static const char tbl_verb[] = /* 2nd byte matches RTACTION_* code */ + "\006\001add\0" + "\006\002del\0" +/* "\011\002delete\0" */ + "\010\002delete" /* Since last, we can save a byte. */ +; + +int route_main(int argc, char **argv) +{ + unsigned opt; + int what; + char *family; + char **p; + + /* First, remap '-net' and '-host' to avoid getopt problems. */ + p = argv; + while (*++p) { + if ((strcmp(*p, "-net") == 0) || (strcmp(*p, "-host") == 0)) { + p[0][0] = '#'; + } + } + + opt = getopt32(argc, argv, "A:ne", &family); + + if ((opt & ROUTE_OPT_A) && strcmp(family, "inet")) { +#ifdef CONFIG_FEATURE_IPV6 + if (strcmp(family, "inet6") == 0) { + opt |= ROUTE_OPT_INET6; /* Set flag for ipv6. */ + } else +#endif + bb_show_usage(); + } + + argv += optind; + + /* No more args means display the routing table. */ + if (!*argv) { + int noresolve = (opt & ROUTE_OPT_n) ? 0x0fff : 0; +#ifdef CONFIG_FEATURE_IPV6 + if (opt & ROUTE_OPT_INET6) + INET6_displayroutes(noresolve); + else +#endif + displayroutes(noresolve, opt & ROUTE_OPT_e); + + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + /* Check verb. At the moment, must be add, del, or delete. */ + what = kw_lookup(tbl_verb, &argv); + if (!what || !*argv) { /* Unknown verb or no more args. */ + bb_show_usage(); + } + +#ifdef CONFIG_FEATURE_IPV6 + if (opt & ROUTE_OPT_INET6) + INET6_setroute(what, argv); + else +#endif + INET_setroute(what, argv); + + return EXIT_SUCCESS; +} diff --git a/networking/telnet.c b/networking/telnet.c new file mode 100644 index 000000000..6085d885a --- /dev/null +++ b/networking/telnet.c @@ -0,0 +1,714 @@ +/* vi: set sw=4 ts=4: */ +/* + * telnet implementation for busybox + * + * Author: Tomi Ollila + * Copyright (C) 1994-2000 by Tomi Ollila + * + * Created: Thu Apr 7 13:29:41 1994 too + * Last modified: Fri Jun 9 14:34:24 2000 too + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * HISTORY + * Revision 3.1 1994/04/17 11:31:54 too + * initial revision + * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen + * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan + * + * Modified 2004/02/11 to add ability to pass the USER variable to remote host + * by Fernando Silveira + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +#ifdef DOTRACE +#include /* for inet_ntoa()... */ +#define TRACE(x, y) do { if (x) printf y; } while (0) +#else +#define TRACE(x, y) +#endif + +#define DATABUFSIZE 128 +#define IACBUFSIZE 128 + +enum { + CHM_TRY = 0, + CHM_ON = 1, + CHM_OFF = 2, + + UF_ECHO = 0x01, + UF_SGA = 0x02, + + TS_0 = 1, + TS_IAC = 2, + TS_OPT = 3, + TS_SUB1 = 4, + TS_SUB2 = 5, +}; + +#define WriteCS(fd, str) write(fd, str, sizeof str -1) + +typedef unsigned char byte; + +/* use globals to reduce size ??? */ /* test this hypothesis later */ +static struct Globalvars { + int netfd; /* console fd:s are 0 and 1 (and 2) */ + /* same buffer used both for network and console read/write */ + char buf[DATABUFSIZE]; /* allocating so static size is smaller */ + byte telstate; /* telnet negotiation state from network input */ + byte telwish; /* DO, DONT, WILL, WONT */ + byte charmode; + byte telflags; + byte gotsig; + byte do_termios; + /* buffer to handle telnet negotiations */ + char iacbuf[IACBUFSIZE]; + short iaclen; /* could even use byte */ + struct termios termios_def; + struct termios termios_raw; +} G; + +#define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */ + +#ifdef USE_GLOBALVAR_PTR +struct Globalvars * Gptr; +#define G (*Gptr) +#endif + +static void iacflush(void) +{ + write(G.netfd, G.iacbuf, G.iaclen); + G.iaclen = 0; +} + +/* Function prototypes */ +static void rawmode(void); +static void cookmode(void); +static void do_linemode(void); +static void will_charmode(void); +static void telopt(byte c); +static int subneg(byte c); + +/* Some globals */ +static const int one = 1; + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static char *ttype; +#endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static const char *autologin; +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH +static int win_width, win_height; +#endif + +static void doexit(int ev) +{ + cookmode(); + exit(ev); +} + +static void conescape(void) +{ + char b; + + if (G.gotsig) /* came from line mode... go raw */ + rawmode(); + + WriteCS(1, "\r\nConsole escape. Commands are:\r\n\n" + " l go to line mode\r\n" + " c go to character mode\r\n" + " z suspend telnet\r\n" + " e exit telnet\r\n"); + + if (read(0, &b, 1) <= 0) + doexit(1); + + switch (b) + { + case 'l': + if (!G.gotsig) + { + do_linemode(); + goto rrturn; + } + break; + case 'c': + if (G.gotsig) + { + will_charmode(); + goto rrturn; + } + break; + case 'z': + cookmode(); + kill(0, SIGTSTP); + rawmode(); + break; + case 'e': + doexit(0); + } + + WriteCS(1, "continuing...\r\n"); + + if (G.gotsig) + cookmode(); + + rrturn: + G.gotsig = 0; + +} +static void handlenetoutput(int len) +{ + /* here we could do smart tricks how to handle 0xFF:s in output + * stream like writing twice every sequence of FF:s (thus doing + * many write()s. But I think interactive telnet application does + * not need to be 100% 8-bit clean, so changing every 0xff:s to + * 0x7f:s + * + * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com) + * I don't agree. + * first - I cannot use programs like sz/rz + * second - the 0x0D is sent as one character and if the next + * char is 0x0A then it's eaten by a server side. + * third - whay doy you have to make 'many write()s'? + * I don't understand. + * So I implemented it. It's realy useful for me. I hope that + * others people will find it interesting to. + */ + + int i, j; + byte * p = (byte*)G.buf; + byte outbuf[4*DATABUFSIZE]; + + for (i = len, j = 0; i > 0; i--, p++) + { + if (*p == 0x1d) + { + conescape(); + return; + } + outbuf[j++] = *p; + if (*p == 0xff) + outbuf[j++] = 0xff; + else if (*p == 0x0d) + outbuf[j++] = 0x00; + } + if (j > 0 ) + write(G.netfd, outbuf, j); +} + + +static void handlenetinput(int len) +{ + int i; + int cstart = 0; + + for (i = 0; i < len; i++) + { + byte c = G.buf[i]; + + if (G.telstate == 0) /* most of the time state == 0 */ + { + if (c == IAC) + { + cstart = i; + G.telstate = TS_IAC; + } + } + else + switch (G.telstate) + { + case TS_0: + if (c == IAC) + G.telstate = TS_IAC; + else + G.buf[cstart++] = c; + break; + + case TS_IAC: + if (c == IAC) /* IAC IAC -> 0xFF */ + { + G.buf[cstart++] = c; + G.telstate = TS_0; + break; + } + /* else */ + switch (c) + { + case SB: + G.telstate = TS_SUB1; + break; + case DO: + case DONT: + case WILL: + case WONT: + G.telwish = c; + G.telstate = TS_OPT; + break; + default: + G.telstate = TS_0; /* DATA MARK must be added later */ + } + break; + case TS_OPT: /* WILL, WONT, DO, DONT */ + telopt(c); + G.telstate = TS_0; + break; + case TS_SUB1: /* Subnegotiation */ + case TS_SUB2: /* Subnegotiation */ + if (subneg(c)) + G.telstate = TS_0; + break; + } + } + if (G.telstate) + { + if (G.iaclen) iacflush(); + if (G.telstate == TS_0) G.telstate = 0; + + len = cstart; + } + + if (len) + write(1, G.buf, len); +} + + +/* ******************************* */ + +static void putiac(int c) +{ + G.iacbuf[G.iaclen++] = c; +} + + +static void putiac2(byte wwdd, byte c) +{ + if (G.iaclen + 3 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(wwdd); + putiac(c); +} + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static void putiac_subopt(byte c, char *str) +{ + int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) + + if (G.iaclen + len > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(c); + putiac(0); + + while(*str) + putiac(*str++); + + putiac(IAC); + putiac(SE); +} +#endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static void putiac_subopt_autologin(void) +{ + int len = strlen(autologin) + 6; // (2 + 1 + 1 + strlen + 2) + char *user = "USER"; + + if (G.iaclen + len > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(TELOPT_NEW_ENVIRON); + putiac(TELQUAL_IS); + putiac(NEW_ENV_VAR); + + while(*user) + putiac(*user++); + + putiac(NEW_ENV_VALUE); + + while(*autologin) + putiac(*autologin++); + + putiac(IAC); + putiac(SE); +} +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH +static void putiac_naws(byte c, int x, int y) +{ + if (G.iaclen + 9 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(c); + + putiac((x >> 8) & 0xff); + putiac(x & 0xff); + putiac((y >> 8) & 0xff); + putiac(y & 0xff); + + putiac(IAC); + putiac(SE); +} +#endif + +/* void putiacstring (subneg strings) */ + +/* ******************************* */ + +static char const escapecharis[] = "\r\nEscape character is "; + +static void setConMode(void) +{ + if (G.telflags & UF_ECHO) + { + if (G.charmode == CHM_TRY) { + G.charmode = CHM_ON; + printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis); + rawmode(); + } + } + else + { + if (G.charmode != CHM_OFF) { + G.charmode = CHM_OFF; + printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis); + cookmode(); + } + } +} + +/* ******************************* */ + +static void will_charmode(void) +{ + G.charmode = CHM_TRY; + G.telflags |= (UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DO, TELOPT_ECHO); + putiac2(DO, TELOPT_SGA); + iacflush(); +} + +static void do_linemode(void) +{ + G.charmode = CHM_TRY; + G.telflags &= ~(UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DONT, TELOPT_ECHO); + putiac2(DONT, TELOPT_SGA); + iacflush(); +} + +/* ******************************* */ + +static void to_notsup(char c) +{ + if (G.telwish == WILL) putiac2(DONT, c); + else if (G.telwish == DO) putiac2(WONT, c); +} + +static void to_echo(void) +{ + /* if server requests ECHO, don't agree */ + if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; } + else if (G.telwish == DONT) return; + + if (G.telflags & UF_ECHO) + { + if (G.telwish == WILL) + return; + } + else + if (G.telwish == WONT) + return; + + if (G.charmode != CHM_OFF) + G.telflags ^= UF_ECHO; + + if (G.telflags & UF_ECHO) + putiac2(DO, TELOPT_ECHO); + else + putiac2(DONT, TELOPT_ECHO); + + setConMode(); + WriteCS(1, "\r\n"); /* sudden modec */ +} + +static void to_sga(void) +{ + /* daemon always sends will/wont, client do/dont */ + + if (G.telflags & UF_SGA) + { + if (G.telwish == WILL) + return; + } + else + if (G.telwish == WONT) + return; + + if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */ + putiac2(DO, TELOPT_SGA); + else + putiac2(DONT, TELOPT_SGA); + + return; +} + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static void to_ttype(void) +{ + /* Tell server we will (or won't) do TTYPE */ + + if(ttype) + putiac2(WILL, TELOPT_TTYPE); + else + putiac2(WONT, TELOPT_TTYPE); + + return; +} +#endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static void to_new_environ(void) +{ + /* Tell server we will (or will not) do AUTOLOGIN */ + + if (autologin) + putiac2(WILL, TELOPT_NEW_ENVIRON); + else + putiac2(WONT, TELOPT_NEW_ENVIRON); + + return; +} +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH +static void to_naws(void) +{ + /* Tell server we will do NAWS */ + putiac2(WILL, TELOPT_NAWS); + return; +} +#endif + +static void telopt(byte c) +{ + switch (c) + { + case TELOPT_ECHO: to_echo(); break; + case TELOPT_SGA: to_sga(); break; +#ifdef CONFIG_FEATURE_TELNET_TTYPE + case TELOPT_TTYPE: to_ttype();break; +#endif +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + case TELOPT_NEW_ENVIRON: to_new_environ(); break; +#endif +#ifdef CONFIG_FEATURE_AUTOWIDTH + case TELOPT_NAWS: to_naws(); + putiac_naws(c, win_width, win_height); + break; +#endif + default: to_notsup(c); + break; + } +} + + +/* ******************************* */ + +/* subnegotiation -- ignore all (except TTYPE,NAWS) */ + +static int subneg(byte c) +{ + switch (G.telstate) + { + case TS_SUB1: + if (c == IAC) + G.telstate = TS_SUB2; +#ifdef CONFIG_FEATURE_TELNET_TTYPE + else + if (c == TELOPT_TTYPE) + putiac_subopt(TELOPT_TTYPE,ttype); +#endif +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + else + if (c == TELOPT_NEW_ENVIRON) + putiac_subopt_autologin(); +#endif + break; + case TS_SUB2: + if (c == SE) + return TRUE; + G.telstate = TS_SUB1; + /* break; */ + } + return FALSE; +} + +/* ******************************* */ + +static void fgotsig(int sig) +{ + G.gotsig = sig; +} + + +static void rawmode(void) +{ + if (G.do_termios) tcsetattr(0, TCSADRAIN, &G.termios_raw); +} + +static void cookmode(void) +{ + if (G.do_termios) tcsetattr(0, TCSADRAIN, &G.termios_def); +} + +int telnet_main(int argc, char** argv) +{ + int len; + struct sockaddr_in s_in; +#ifdef USE_POLL + struct pollfd ufds[2]; +#else + fd_set readfds; + int maxfd; +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH + get_terminal_width_height(0, &win_width, &win_height); +#endif + +#ifdef CONFIG_FEATURE_TELNET_TTYPE + ttype = getenv("TERM"); +#endif + + memset(&G, 0, sizeof G); + + if (tcgetattr(0, &G.termios_def) >= 0) { + G.do_termios = 1; + + G.termios_raw = G.termios_def; + cfmakeraw(&G.termios_raw); + } + + if (argc < 2) + bb_show_usage(); + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + if (1 & getopt32(argc, argv, "al:", &autologin)) + autologin = getenv("USER"); + + if (optind < argc) { + bb_lookup_host(&s_in, argv[optind++]); + s_in.sin_port = bb_lookup_port((optind < argc) ? argv[optind++] : + "telnet", "tcp", 23); + if (optind < argc) + bb_show_usage(); + } else + bb_show_usage(); +#else + bb_lookup_host(&s_in, argv[1]); + s_in.sin_port = bb_lookup_port((argc == 3) ? argv[2] : "telnet", "tcp", 23); +#endif + + G.netfd = xconnect_tcp_v4(&s_in); + + setsockopt(G.netfd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); + + signal(SIGINT, fgotsig); + +#ifdef USE_POLL + ufds[0].fd = 0; ufds[1].fd = G.netfd; + ufds[0].events = ufds[1].events = POLLIN; +#else + FD_ZERO(&readfds); + FD_SET(0, &readfds); + FD_SET(G.netfd, &readfds); + maxfd = G.netfd + 1; +#endif + + while (1) + { +#ifndef USE_POLL + fd_set rfds = readfds; + + switch (select(maxfd, &rfds, NULL, NULL, NULL)) +#else + switch (poll(ufds, 2, -1)) +#endif + { + case 0: + /* timeout */ + case -1: + /* error, ignore and/or log something, bay go to loop */ + if (G.gotsig) + conescape(); + else + sleep(1); + break; + default: + +#ifdef USE_POLL + if (ufds[0].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(0, &rfds)) +#endif + { + len = read(0, G.buf, DATABUFSIZE); + + if (len <= 0) + doexit(0); + + TRACE(0, ("Read con: %d\n", len)); + + handlenetoutput(len); + } + +#ifdef USE_POLL + if (ufds[1].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(G.netfd, &rfds)) +#endif + { + len = read(G.netfd, G.buf, DATABUFSIZE); + + if (len <= 0) + { + WriteCS(1, "Connection closed by foreign host.\r\n"); + doexit(1); + } + TRACE(0, ("Read netfd (%d): %d\n", G.netfd, len)); + + handlenetinput(len); + } + } + } +} diff --git a/networking/telnetd.c b/networking/telnetd.c new file mode 100644 index 000000000..604f65c91 --- /dev/null +++ b/networking/telnetd.c @@ -0,0 +1,578 @@ +/* vi: set sw=4 ts=4: */ +/* + * Simple telnet server + * Bjorn Wesen, Axis Communications AB (bjornw@axis.com) + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * --------------------------------------------------------------------------- + * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN + **************************************************************************** + * + * The telnetd manpage says it all: + * + * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for + * a client, then creating a login process which has the slave side of the + * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the + * master side of the pseudo-terminal, implementing the telnet protocol and + * passing characters between the remote client and the login process. + * + * Vladimir Oleynik 2001 + * Set process group corrections, initial busybox port + */ + +/*#define DEBUG 1 */ +#define DEBUG 0 + +#include "busybox.h" + +#if DEBUG +#define TELCMDS +#define TELOPTS +#endif +#include +#include + + +#define BUFSIZE 4000 + +#if ENABLE_FEATURE_IPV6 +typedef struct sockaddr_in6 sockaddr_type; +#else +typedef struct sockaddr_in sockaddr_type; +#endif + +#if ENABLE_LOGIN +static const char *loginpath = "/bin/login"; +#else +static const char *loginpath = DEFAULT_SHELL; +#endif + +static const char *issuefile = "/etc/issue.net"; + +/* shell name and arguments */ + +static const char *argv_init[2]; + +/* structure that describes a session */ + +struct tsession { + struct tsession *next; + int sockfd_read, sockfd_write, ptyfd; + int shell_pid; + /* two circular buffers */ + char *buf1, *buf2; + int rdidx1, wridx1, size1; + int rdidx2, wridx2, size2; +}; + +/* + This is how the buffers are used. The arrows indicate the movement + of data. + + +-------+ wridx1++ +------+ rdidx1++ +----------+ + | | <-------------- | buf1 | <-------------- | | + | | size1-- +------+ size1++ | | + | pty | | socket | + | | rdidx2++ +------+ wridx2++ | | + | | --------------> | buf2 | --------------> | | + +-------+ size2++ +------+ size2-- +----------+ + + Each session has got two buffers. +*/ + +static int maxfd; + +static struct tsession *sessions; + + +/* + Remove all IAC's from the buffer pointed to by bf (received IACs are ignored + and must be removed so as to not be interpreted by the terminal). Make an + uninterrupted string of characters fit for the terminal. Do this by packing + all characters meant for the terminal sequentially towards the end of bf. + + Return a pointer to the beginning of the characters meant for the terminal. + and make *num_totty the number of characters that should be sent to + the terminal. + + Note - If an IAC (3 byte quantity) starts before (bf + len) but extends + past (bf + len) then that IAC will be left unprocessed and *processed will be + less than len. + + FIXME - if we mean to send 0xFF to the terminal then it will be escaped, + what is the escape character? We aren't handling that situation here. + + CR-LF ->'s CR mapping is also done here, for convenience + */ +static char * +remove_iacs(struct tsession *ts, int *pnum_totty) +{ + unsigned char *ptr0 = (unsigned char *)ts->buf1 + ts->wridx1; + unsigned char *ptr = ptr0; + unsigned char *totty = ptr; + unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1); + int processed; + int num_totty; + + while (ptr < end) { + if (*ptr != IAC) { + int c = *ptr; + *totty++ = *ptr++; + /* We now map \r\n ==> \r for pragmatic reasons. + * Many client implementations send \r\n when + * the user hits the CarriageReturn key. + */ + if (c == '\r' && (*ptr == '\n' || *ptr == 0) && ptr < end) + ptr++; + } else { + /* + * TELOPT_NAWS support! + */ + if ((ptr+2) >= end) { + /* only the beginning of the IAC is in the + buffer we were asked to process, we can't + process this char. */ + break; + } + + /* + * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE + */ + else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) { + struct winsize ws; + if ((ptr+8) >= end) + break; /* incomplete, can't process */ + ws.ws_col = (ptr[3] << 8) | ptr[4]; + ws.ws_row = (ptr[5] << 8) | ptr[6]; + ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); + ptr += 9; + } else { + /* skip 3-byte IAC non-SB cmd */ +#if DEBUG + fprintf(stderr, "Ignoring IAC %s,%s\n", + TELCMD(ptr[1]), TELOPT(ptr[2])); +#endif + ptr += 3; + } + } + } + + processed = ptr - ptr0; + num_totty = totty - ptr0; + /* the difference between processed and num_to tty + is all the iacs we removed from the stream. + Adjust buf1 accordingly. */ + ts->wridx1 += processed - num_totty; + ts->size1 -= processed - num_totty; + *pnum_totty = num_totty; + /* move the chars meant for the terminal towards the end of the + buffer. */ + return memmove(ptr - num_totty, ptr0, num_totty); +} + + +static int +getpty(char *line, int size) +{ + int p; +#if ENABLE_FEATURE_DEVPTS + p = open("/dev/ptmx", O_RDWR); + if (p > 0) { + const char *name; + grantpt(p); + unlockpt(p); + name = ptsname(p); + if (!name) { + bb_perror_msg("ptsname error (is /dev/pts mounted?)"); + return -1; + } + safe_strncpy(line, name, size); + return p; + } +#else + struct stat stb; + int i; + int j; + + strcpy(line, "/dev/ptyXX"); + + for (i = 0; i < 16; i++) { + line[8] = "pqrstuvwxyzabcde"[i]; + line[9] = '0'; + if (stat(line, &stb) < 0) { + continue; + } + for (j = 0; j < 16; j++) { + line[9] = j < 10 ? j + '0' : j - 10 + 'a'; + if (DEBUG) + fprintf(stderr, "Trying to open device: %s\n", line); + p = open(line, O_RDWR | O_NOCTTY); + if (p >= 0) { + line[5] = 't'; + return p; + } + } + } +#endif /* FEATURE_DEVPTS */ + return -1; +} + + +static void +send_iac(struct tsession *ts, unsigned char command, int option) +{ + /* We rely on that there is space in the buffer for now. */ + char *b = ts->buf2 + ts->rdidx2; + *b++ = IAC; + *b++ = command; + *b++ = option; + ts->rdidx2 += 3; + ts->size2 += 3; +} + + +static struct tsession * +make_new_session( + USE_FEATURE_TELNETD_STANDALONE(int sock_r, int sock_w) + SKIP_FEATURE_TELNETD_STANDALONE(void) +) { + struct termios termbuf; + int fd, pid; + char tty_name[32]; + struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2); + + ts->buf1 = (char *)(&ts[1]); + ts->buf2 = ts->buf1 + BUFSIZE; + + /* Got a new connection, set up a tty. */ + fd = getpty(tty_name, 32); + if (fd < 0) { + bb_error_msg("all terminals in use"); + return NULL; + } + if (fd > maxfd) maxfd = fd; + ndelay_on(ts->ptyfd = fd); +#if ENABLE_FEATURE_TELNETD_STANDALONE + if (sock_w > maxfd) maxfd = sock_w; + if (sock_r > maxfd) maxfd = sock_r; + ndelay_on(ts->sockfd_write = sock_w); + ndelay_on(ts->sockfd_read = sock_r); +#else + ts->sockfd_write = 1; + /* xzalloc: ts->sockfd_read = 0; */ + ndelay_on(0); + ndelay_on(1); +#endif + /* Make the telnet client understand we will echo characters so it + * should not do it locally. We don't tell the client to run linemode, + * because we want to handle line editing and tab completion and other + * stuff that requires char-by-char support. */ + send_iac(ts, DO, TELOPT_ECHO); + send_iac(ts, DO, TELOPT_NAWS); + send_iac(ts, DO, TELOPT_LFLOW); + send_iac(ts, WILL, TELOPT_ECHO); + send_iac(ts, WILL, TELOPT_SGA); + + pid = fork(); + if (pid < 0) { + free(ts); + close(fd); + bb_perror_msg("fork"); + return NULL; + } + if (pid > 0) { + /* parent */ + ts->shell_pid = pid; + return ts; + } + + /* child */ + + /* open the child's side of the tty. */ + fd = xopen(tty_name, O_RDWR /*| O_NOCTTY*/); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + while (fd > 2) close(fd--); + /* make new process group */ + setsid(); + tcsetpgrp(0, getpid()); + + /* The pseudo-terminal allocated to the client is configured to operate in + * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */ + tcgetattr(0, &termbuf); + termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */ + termbuf.c_oflag |= ONLCR|XTABS; + termbuf.c_iflag |= ICRNL; + termbuf.c_iflag &= ~IXOFF; + /*termbuf.c_lflag &= ~ICANON;*/ + tcsetattr(0, TCSANOW, &termbuf); + + print_login_issue(issuefile, NULL); + + /* exec shell, with correct argv and env */ + execv(loginpath, (char *const *)argv_init); + bb_perror_msg_and_die("execv"); +} + +#if ENABLE_FEATURE_TELNETD_STANDALONE + +static void +free_session(struct tsession *ts) +{ + struct tsession *t = sessions; + + /* unlink this telnet session from the session list */ + if (t == ts) + sessions = ts->next; + else { + while (t->next != ts) + t = t->next; + t->next = ts->next; + } + + kill(ts->shell_pid, SIGKILL); + wait4(ts->shell_pid, NULL, 0, NULL); + close(ts->ptyfd); + close(ts->sockfd_read); + /* error if ts->sockfd_read == ts->sockfd_write. So what? ;) */ + close(ts->sockfd_write); + free(ts); + + /* scan all sessions and find new maxfd */ + ts = sessions; + maxfd = 0; + while (ts) { + if (maxfd < ts->ptyfd) + maxfd = ts->ptyfd; + if (maxfd < ts->sockfd_read) + maxfd = ts->sockfd_read; + if (maxfd < ts->sockfd_write) + maxfd = ts->sockfd_write; + ts = ts->next; + } +} + +#else /* !FEATURE_TELNETD_STANDALONE */ + +/* Never actually called */ +void free_session(struct tsession *ts); + +#endif + + +int +telnetd_main(int argc, char **argv) +{ + fd_set rdfdset, wrfdset; + unsigned opt; + int selret, maxlen, w, r; + struct tsession *ts; +#if ENABLE_FEATURE_TELNETD_STANDALONE +#define IS_INETD (opt & OPT_INETD) + int master_fd = -1; /* be happy, gcc */ + unsigned portnbr = 23; + char *opt_bindaddr = NULL; + char *opt_portnbr; +#else + enum { + IS_INETD = 1, + master_fd = -1, + portnbr = 23, + }; +#endif + enum { + OPT_PORT = 4 * ENABLE_FEATURE_TELNETD_STANDALONE, + OPT_FOREGROUND = 0x10 * ENABLE_FEATURE_TELNETD_STANDALONE, + OPT_INETD = 0x20 * ENABLE_FEATURE_TELNETD_STANDALONE, + }; + + opt = getopt32(argc, argv, "f:l:" USE_FEATURE_TELNETD_STANDALONE("p:b:Fi"), + &issuefile, &loginpath + USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)); + /* Redirect log to syslog early, if needed */ + if (IS_INETD || !(opt & OPT_FOREGROUND)) { + openlog(applet_name, 0, LOG_USER); + logmode = LOGMODE_SYSLOG; + } + //if (opt & 1) // -f + //if (opt & 2) // -l + USE_FEATURE_TELNETD_STANDALONE( + if (opt & OPT_PORT) // -p + portnbr = xatou16(opt_portnbr); + //if (opt & 8) // -b + //if (opt & 0x10) // -F + //if (opt & 0x20) // -i + ); + + /* Used to check access(loginpath, X_OK) here. Pointless. + * exec will do this for us for free later. */ + argv_init[0] = loginpath; + +#if ENABLE_FEATURE_TELNETD_STANDALONE + if (IS_INETD) { + sessions = make_new_session(0, 1); + } else { + master_fd = create_and_bind_socket_ip4or6(opt_bindaddr, portnbr); + xlisten(master_fd, 1); + if (!(opt & OPT_FOREGROUND)) + xdaemon(0, 0); + } +#else + sessions = make_new_session(); +#endif + + /* We don't want to die if just one session is broken */ + signal(SIGPIPE, SIG_IGN); + + again: + FD_ZERO(&rdfdset); + FD_ZERO(&wrfdset); + if (!IS_INETD) { + FD_SET(master_fd, &rdfdset); + /* This is needed because free_session() does not + * take into account master_fd when it finds new + * maxfd among remaining fd's: */ + if (master_fd > maxfd) + maxfd = master_fd; + } + + /* select on the master socket, all telnet sockets and their + * ptys if there is room in their session buffers. */ + ts = sessions; + while (ts) { + /* buf1 is used from socket to pty + * buf2 is used from pty to socket */ + if (ts->size1 > 0) /* can write to pty */ + FD_SET(ts->ptyfd, &wrfdset); + if (ts->size1 < BUFSIZE) /* can read from socket */ + FD_SET(ts->sockfd_read, &rdfdset); + if (ts->size2 > 0) /* can write to socket */ + FD_SET(ts->sockfd_write, &wrfdset); + if (ts->size2 < BUFSIZE) /* can read from pty */ + FD_SET(ts->ptyfd, &rdfdset); + ts = ts->next; + } + + selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0); + if (!selret) + return 0; + +#if ENABLE_FEATURE_TELNETD_STANDALONE + /* First check for and accept new sessions. */ + if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) { + sockaddr_type sa; + int fd; + socklen_t salen; + struct tsession *new_ts; + + salen = sizeof(sa); + fd = accept(master_fd, (struct sockaddr *)&sa, &salen); + if (fd < 0) + goto again; + /* Create a new session and link it into our active list */ + new_ts = make_new_session(fd, fd); + if (new_ts) { + new_ts->next = sessions; + sessions = new_ts; + } else { + close(fd); + } + } +#endif + + /* Then check for data tunneling. */ + ts = sessions; + while (ts) { /* For all sessions... */ + struct tsession *next = ts->next; /* in case we free ts. */ + + if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) { + int num_totty; + char *ptr; + /* Write to pty from buffer 1. */ + ptr = remove_iacs(ts, &num_totty); + w = safe_write(ts->ptyfd, ptr, num_totty); + /* needed? if (w < 0 && errno == EAGAIN) continue; */ + if (w < 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + ts->wridx1 += w; + ts->size1 -= w; + if (ts->wridx1 == BUFSIZE) + ts->wridx1 = 0; + } + + if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) { + /* Write to socket from buffer 2. */ + maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2); + w = safe_write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen); + /* needed? if (w < 0 && errno == EAGAIN) continue; */ + if (w < 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + ts->wridx2 += w; + ts->size2 -= w; + if (ts->wridx2 == BUFSIZE) + ts->wridx2 = 0; + } + + if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) { + /* Read from socket to buffer 1. */ + maxlen = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1); + r = safe_read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen); + if (r < 0 && errno == EAGAIN) continue; + if (r <= 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + if (!ts->buf1[ts->rdidx1 + r - 1]) + if (!--r) + continue; + ts->rdidx1 += r; + ts->size1 += r; + if (ts->rdidx1 == BUFSIZE) + ts->rdidx1 = 0; + } + + if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) { + /* Read from pty to buffer 2. */ + maxlen = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2); + r = safe_read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen); + if (r < 0 && errno == EAGAIN) continue; + if (r <= 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + ts->rdidx2 += r; + ts->size2 += r; + if (ts->rdidx2 == BUFSIZE) + ts->rdidx2 = 0; + } + + if (ts->size1 == 0) { + ts->rdidx1 = 0; + ts->wridx1 = 0; + } + if (ts->size2 == 0) { + ts->rdidx2 = 0; + ts->wridx2 = 0; + } + ts = next; + } + goto again; +} diff --git a/networking/tftp.c b/networking/tftp.c new file mode 100644 index 000000000..64d376fa7 --- /dev/null +++ b/networking/tftp.c @@ -0,0 +1,561 @@ +/* vi: set sw=4 ts=4: */ +/* ------------------------------------------------------------------------- + * tftp.c + * + * A simple tftp client for busybox. + * Tries to follow RFC1350. + * Only "octet" mode supported. + * Optional blocksize negotiation (RFC2347 + RFC2348) + * + * Copyright (C) 2001 Magnus Damm + * + * Parts of the code based on: + * + * atftp: Copyright (C) 2000 Jean-Pierre Lefebvre + * and Remi Lefebvre + * + * utftp: Copyright (C) 1999 Uwe Ohse + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * ------------------------------------------------------------------------- */ + +#include "busybox.h" + + +#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */ +#define TFTP_TIMEOUT 5 /* seconds */ +#define TFTP_NUM_RETRIES 5 /* number of retries */ + +static const char * const MODE_OCTET = "octet"; +#define MODE_OCTET_LEN 6 /* sizeof(MODE_OCTET)*/ + +static const char * const OPTION_BLOCKSIZE = "blksize"; +#define OPTION_BLOCKSIZE_LEN 8 /* sizeof(OPTION_BLOCKSIZE) */ + +/* opcodes we support */ +#define TFTP_RRQ 1 +#define TFTP_WRQ 2 +#define TFTP_DATA 3 +#define TFTP_ACK 4 +#define TFTP_ERROR 5 +#define TFTP_OACK 6 + +static const char *const tftp_bb_error_msg[] = { + "Undefined error", + "File not found", + "Access violation", + "Disk full or allocation error", + "Illegal TFTP operation", + "Unknown transfer ID", + "File already exists", + "No such user" +}; + +#define tftp_cmd_get ENABLE_FEATURE_TFTP_GET + +#if ENABLE_FEATURE_TFTP_PUT +# define tftp_cmd_put (tftp_cmd_get+ENABLE_FEATURE_TFTP_PUT) +#else +# define tftp_cmd_put 0 +#endif + + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + +static int tftp_blocksize_check(int blocksize, int bufsize) +{ + /* Check if the blocksize is valid: + * RFC2348 says between 8 and 65464, + * but our implementation makes it impossible + * to use blocksizes smaller than 22 octets. + */ + + if ((bufsize && (blocksize > bufsize)) || + (blocksize < 8) || (blocksize > 65564)) { + bb_error_msg("bad blocksize"); + return 0; + } + + return blocksize; +} + +static char *tftp_option_get(char *buf, int len, const char * const option) +{ + int opt_val = 0; + int opt_found = 0; + int k; + + while (len > 0) { + + /* Make sure the options are terminated correctly */ + + for (k = 0; k < len; k++) { + if (buf[k] == '\0') { + break; + } + } + + if (k >= len) { + break; + } + + if (opt_val == 0) { + if (strcasecmp(buf, option) == 0) { + opt_found = 1; + } + } else { + if (opt_found) { + return buf; + } + } + + k++; + + buf += k; + len -= k; + + opt_val ^= 1; + } + + return NULL; +} + +#endif + +static int tftp(const int cmd, const struct hostent *host, + const char *remotefile, const int localfd, + const unsigned short port, int tftp_bufsize) +{ + struct sockaddr_in sa; + struct sockaddr_in from; + struct timeval tv; + socklen_t fromlen; + fd_set rfds; + int socketfd; + int len; + int opcode = 0; + int finished = 0; + int timeout = TFTP_NUM_RETRIES; + unsigned short block_nr = 1; + unsigned short tmp; + char *cp; + + USE_FEATURE_TFTP_BLOCKSIZE(int want_option_ack = 0;) + + /* Can't use RESERVE_CONFIG_BUFFER here since the allocation + * size varies meaning BUFFERS_GO_ON_STACK would fail */ + char *buf=xmalloc(tftp_bufsize += 4); + + if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + /* need to unlink the localfile, so don't use xsocket here. */ + bb_perror_msg("socket"); + return EXIT_FAILURE; + } + + len = sizeof(sa); + + memset(&sa, 0, len); + xbind(socketfd, (struct sockaddr *)&sa, len); + + sa.sin_family = host->h_addrtype; + sa.sin_port = port; + memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr, + sizeof(sa.sin_addr)); + + /* build opcode */ + if (cmd & tftp_cmd_get) { + opcode = TFTP_RRQ; + } + if (cmd & tftp_cmd_put) { + opcode = TFTP_WRQ; + } + + while (1) { + + cp = buf; + + /* first create the opcode part */ + *((unsigned short *) cp) = htons(opcode); + cp += 2; + + /* add filename and mode */ + if (((cmd & tftp_cmd_get) && (opcode == TFTP_RRQ)) || + ((cmd & tftp_cmd_put) && (opcode == TFTP_WRQ))) + { + int too_long = 0; + + /* see if the filename fits into buf + * and fill in packet. */ + len = strlen(remotefile) + 1; + + if ((cp + len) >= &buf[tftp_bufsize - 1]) { + too_long = 1; + } else { + safe_strncpy(cp, remotefile, len); + cp += len; + } + + if (too_long || ((&buf[tftp_bufsize - 1] - cp) < MODE_OCTET_LEN)) { + bb_error_msg("remote filename too long"); + break; + } + + /* add "mode" part of the package */ + memcpy(cp, MODE_OCTET, MODE_OCTET_LEN); + cp += MODE_OCTET_LEN; + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + + len = tftp_bufsize - 4; /* data block size */ + + if (len != TFTP_BLOCKSIZE_DEFAULT) { + + if ((&buf[tftp_bufsize - 1] - cp) < 15) { + bb_error_msg("remote filename too long"); + break; + } + + /* add "blksize" + number of blocks */ + memcpy(cp, OPTION_BLOCKSIZE, OPTION_BLOCKSIZE_LEN); + cp += OPTION_BLOCKSIZE_LEN; + cp += snprintf(cp, 6, "%d", len) + 1; + + want_option_ack = 1; + } +#endif + } + + /* add ack and data */ + + if (((cmd & tftp_cmd_get) && (opcode == TFTP_ACK)) || + ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA))) { + + *((unsigned short *) cp) = htons(block_nr); + + cp += 2; + + block_nr++; + + if ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA)) { + len = full_read(localfd, cp, tftp_bufsize - 4); + + if (len < 0) { + bb_perror_msg(bb_msg_read_error); + break; + } + + if (len != (tftp_bufsize - 4)) { + finished++; + } + + cp += len; + } + } + + + /* send packet */ + + + timeout = TFTP_NUM_RETRIES; /* re-initialize */ + do { + + len = cp - buf; + +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "sending %u bytes\n", len); + for (cp = buf; cp < &buf[len]; cp++) + fprintf(stderr, "%02x ", (unsigned char) *cp); + fprintf(stderr, "\n"); +#endif + if (sendto(socketfd, buf, len, 0, + (struct sockaddr *) &sa, sizeof(sa)) < 0) { + bb_perror_msg("send"); + len = -1; + break; + } + + + if (finished && (opcode == TFTP_ACK)) { + break; + } + + /* receive packet */ + + memset(&from, 0, sizeof(from)); + fromlen = sizeof(from); + + tv.tv_sec = TFTP_TIMEOUT; + tv.tv_usec = 0; + + FD_ZERO(&rfds); + FD_SET(socketfd, &rfds); + + switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) { + case 1: + len = recvfrom(socketfd, buf, tftp_bufsize, 0, + (struct sockaddr *) &from, &fromlen); + + if (len < 0) { + bb_perror_msg("recvfrom"); + break; + } + + timeout = 0; + + if (sa.sin_port == port) { + sa.sin_port = from.sin_port; + } + if (sa.sin_port == from.sin_port) { + break; + } + + /* fall-through for bad packets! */ + /* discard the packet - treat as timeout */ + timeout = TFTP_NUM_RETRIES; + case 0: + bb_error_msg("timeout"); + + timeout--; + if (timeout == 0) { + len = -1; + bb_error_msg("last timeout"); + } + break; + default: + bb_perror_msg("select"); + len = -1; + } + + } while (timeout && (len >= 0)); + + if ((finished) || (len < 0)) { + break; + } + + /* process received packet */ + + opcode = ntohs(*((unsigned short *) buf)); + tmp = ntohs(*((unsigned short *) &buf[2])); + +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp); +#endif + + if (opcode == TFTP_ERROR) { + const char *msg = NULL; + + if (buf[4] != '\0') { + msg = &buf[4]; + buf[tftp_bufsize - 1] = '\0'; + } else if (tmp < (sizeof(tftp_bb_error_msg) + / sizeof(char *))) { + + msg = tftp_bb_error_msg[tmp]; + } + + if (msg) { + bb_error_msg("server says: %s", msg); + } + + break; + } +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + if (want_option_ack) { + + want_option_ack = 0; + + if (opcode == TFTP_OACK) { + + /* server seems to support options */ + + char *res; + + res = tftp_option_get(&buf[2], len - 2, OPTION_BLOCKSIZE); + + if (res) { + int blksize = xatoi_u(res); + + if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) { + + if (cmd & tftp_cmd_put) { + opcode = TFTP_DATA; + } else { + opcode = TFTP_ACK; + } +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "using %s %u\n", OPTION_BLOCKSIZE, + blksize); +#endif + tftp_bufsize = blksize + 4; + block_nr = 0; + continue; + } + } + /* FIXME: + * we should send ERROR 8 */ + bb_error_msg("bad server option"); + break; + } + + bb_error_msg("warning: blksize not supported by server" + " - reverting to 512"); + + tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4; + } +#endif + + if ((cmd & tftp_cmd_get) && (opcode == TFTP_DATA)) { + + if (tmp == block_nr) { + + len = full_write(localfd, &buf[4], len - 4); + + if (len < 0) { + bb_perror_msg(bb_msg_write_error); + break; + } + + if (len != (tftp_bufsize - 4)) { + finished++; + } + + opcode = TFTP_ACK; + continue; + } + /* in case the last ack disappeared into the ether */ + if (tmp == (block_nr - 1)) { + --block_nr; + opcode = TFTP_ACK; + continue; + } else if (tmp + 1 == block_nr) { + /* Server lost our TFTP_ACK. Resend it */ + block_nr = tmp; + opcode = TFTP_ACK; + continue; + } + } + + if ((cmd & tftp_cmd_put) && (opcode == TFTP_ACK)) { + + if (tmp == (unsigned short) (block_nr - 1)) { + if (finished) { + break; + } + + opcode = TFTP_DATA; + continue; + } + } + } + +#if ENABLE_FEATURE_CLEAN_UP + close(socketfd); + free(buf); +#endif + + return finished ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int tftp_main(int argc, char **argv) +{ + struct hostent *host = NULL; + const char *localfile = NULL; + const char *remotefile = NULL; + int port; + int cmd = 0; + int fd = -1; + int flags = 0; + int result; + int blocksize = TFTP_BLOCKSIZE_DEFAULT; + + /* figure out what to pass to getopt */ + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + char *sblocksize = NULL; + +#define BS "b:" +#define BS_ARG , &sblocksize +#else +#define BS +#define BS_ARG +#endif + +#if ENABLE_FEATURE_TFTP_GET +#define GET "g" +#define GET_COMPL ":g" +#else +#define GET +#define GET_COMPL +#endif + +#if ENABLE_FEATURE_TFTP_PUT +#define PUT "p" +#define PUT_COMPL ":p" +#else +#define PUT +#define PUT_COMPL +#endif + +#if defined(CONFIG_FEATURE_TFTP_GET) && defined(CONFIG_FEATURE_TFTP_PUT) + opt_complementary = GET_COMPL PUT_COMPL ":?g--p:p--g"; +#elif defined(CONFIG_FEATURE_TFTP_GET) || defined(CONFIG_FEATURE_TFTP_PUT) + opt_complementary = GET_COMPL PUT_COMPL; +#endif + + cmd = getopt32(argc, argv, GET PUT "l:r:" BS, &localfile, &remotefile BS_ARG); + + cmd &= (tftp_cmd_get | tftp_cmd_put); +#if ENABLE_FEATURE_TFTP_GET + if (cmd == tftp_cmd_get) + flags = O_WRONLY | O_CREAT | O_TRUNC; +#endif +#if ENABLE_FEATURE_TFTP_PUT + if (cmd == tftp_cmd_put) + flags = O_RDONLY; +#endif + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + if (sblocksize) { + blocksize = xatoi_u(sblocksize); + if (!tftp_blocksize_check(blocksize, 0)) { + return EXIT_FAILURE; + } + } +#endif + + if (localfile == NULL) + localfile = remotefile; + if (remotefile == NULL) + remotefile = localfile; + if ((localfile == NULL && remotefile == NULL) || (argv[optind] == NULL)) + bb_show_usage(); + + if (localfile == NULL || strcmp(localfile, "-") == 0) { + fd = (cmd == tftp_cmd_get) ? STDOUT_FILENO : STDIN_FILENO; + } else { + fd = open(localfile, flags, 0644); /* fail below */ + } + if (fd < 0) { + bb_perror_msg_and_die("local file"); + } + + host = xgethostbyname(argv[optind]); + port = bb_lookup_port(argv[optind + 1], "udp", 69); + +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "using server \"%s\", remotefile \"%s\", " + "localfile \"%s\".\n", + inet_ntoa(*((struct in_addr *) host->h_addr)), + remotefile, localfile); +#endif + + result = tftp(cmd, host, remotefile, fd, port, blocksize); + + if (!(fd == STDOUT_FILENO || fd == STDIN_FILENO)) { + if (ENABLE_FEATURE_CLEAN_UP) + close(fd); + if (cmd == tftp_cmd_get && result != EXIT_SUCCESS) + unlink(localfile); + } + return result; +} diff --git a/networking/traceroute.c b/networking/traceroute.c new file mode 100644 index 000000000..490076543 --- /dev/null +++ b/networking/traceroute.c @@ -0,0 +1,1350 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (c) 1988, 1989, 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000 + * The Regents of the University of California. All rights reserved. + * + * Busybox port by Vladimir Oleynik (C) 2005 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that: (1) source code distributions + * retain the above copyright notice and this paragraph in its entirety, (2) + * distributions including binary code include the above copyright notice and + * this paragraph in its entirety in the documentation or other materials + * provided with the distribution, and (3) all advertising materials mentioning + * features or use of this software display the following acknowledgement: + * ``This product includes software developed by the University of California, + * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of + * the University nor the names of its contributors may be used to endorse + * or promote products derived from this software without specific prior + * written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +//#define version "1.4a12" + + +/* + * traceroute host - trace the route ip packets follow going to "host". + * + * Attempt to trace the route an ip packet would follow to some + * internet host. We find out intermediate hops by launching probe + * packets with a small ttl (time to live) then listening for an + * icmp "time exceeded" reply from a gateway. We start our probes + * with a ttl of one and increase by one until we get an icmp "port + * unreachable" (which means we got to "host") or hit a max (which + * defaults to 30 hops & can be changed with the -m flag). Three + * probes (change with -q flag) are sent at each ttl setting and a + * line is printed showing the ttl, address of the gateway and + * round trip time of each probe. If the probe answers come from + * different gateways, the address of each responding system will + * be printed. If there is no response within a 5 sec. timeout + * interval (changed with the -w flag), a "*" is printed for that + * probe. + * + * Probe packets are UDP format. We don't want the destination + * host to process them so the destination port is set to an + * unlikely value (if some clod on the destination is using that + * value, it can be changed with the -p flag). + * + * A sample use might be: + * + * [yak 71]% traceroute nis.nsf.net. + * traceroute to nis.nsf.net (35.1.1.48), 30 hops max, 56 byte packet + * 1 helios.ee.lbl.gov (128.3.112.1) 19 ms 19 ms 0 ms + * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 39 ms 19 ms + * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 39 ms 19 ms + * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 39 ms 40 ms 39 ms + * 5 ccn-nerif22.Berkeley.EDU (128.32.168.22) 39 ms 39 ms 39 ms + * 6 128.32.197.4 (128.32.197.4) 40 ms 59 ms 59 ms + * 7 131.119.2.5 (131.119.2.5) 59 ms 59 ms 59 ms + * 8 129.140.70.13 (129.140.70.13) 99 ms 99 ms 80 ms + * 9 129.140.71.6 (129.140.71.6) 139 ms 239 ms 319 ms + * 10 129.140.81.7 (129.140.81.7) 220 ms 199 ms 199 ms + * 11 nic.merit.edu (35.1.1.48) 239 ms 239 ms 239 ms + * + * Note that lines 2 & 3 are the same. This is due to a buggy + * kernel on the 2nd hop system -- lbl-csam.arpa -- that forwards + * packets with a zero ttl. + * + * A more interesting example is: + * + * [yak 72]% traceroute allspice.lcs.mit.edu. + * traceroute to allspice.lcs.mit.edu (18.26.0.115), 30 hops max + * 1 helios.ee.lbl.gov (128.3.112.1) 0 ms 0 ms 0 ms + * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 19 ms 19 ms 19 ms + * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 19 ms 19 ms + * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 19 ms 39 ms 39 ms + * 5 ccn-nerif22.Berkeley.EDU (128.32.168.22) 20 ms 39 ms 39 ms + * 6 128.32.197.4 (128.32.197.4) 59 ms 119 ms 39 ms + * 7 131.119.2.5 (131.119.2.5) 59 ms 59 ms 39 ms + * 8 129.140.70.13 (129.140.70.13) 80 ms 79 ms 99 ms + * 9 129.140.71.6 (129.140.71.6) 139 ms 139 ms 159 ms + * 10 129.140.81.7 (129.140.81.7) 199 ms 180 ms 300 ms + * 11 129.140.72.17 (129.140.72.17) 300 ms 239 ms 239 ms + * 12 * * * + * 13 128.121.54.72 (128.121.54.72) 259 ms 499 ms 279 ms + * 14 * * * + * 15 * * * + * 16 * * * + * 17 * * * + * 18 ALLSPICE.LCS.MIT.EDU (18.26.0.115) 339 ms 279 ms 279 ms + * + * (I start to see why I'm having so much trouble with mail to + * MIT.) Note that the gateways 12, 14, 15, 16 & 17 hops away + * either don't send ICMP "time exceeded" messages or send them + * with a ttl too small to reach us. 14 - 17 are running the + * MIT C Gateway code that doesn't send "time exceeded"s. God + * only knows what's going on with 12. + * + * The silent gateway 12 in the above may be the result of a bug in + * the 4.[23]BSD network code (and its derivatives): 4.x (x <= 3) + * sends an unreachable message using whatever ttl remains in the + * original datagram. Since, for gateways, the remaining ttl is + * zero, the icmp "time exceeded" is guaranteed to not make it back + * to us. The behavior of this bug is slightly more interesting + * when it appears on the destination system: + * + * 1 helios.ee.lbl.gov (128.3.112.1) 0 ms 0 ms 0 ms + * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 19 ms 39 ms + * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 19 ms 39 ms 19 ms + * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 39 ms 40 ms 19 ms + * 5 ccn-nerif35.Berkeley.EDU (128.32.168.35) 39 ms 39 ms 39 ms + * 6 csgw.Berkeley.EDU (128.32.133.254) 39 ms 59 ms 39 ms + * 7 * * * + * 8 * * * + * 9 * * * + * 10 * * * + * 11 * * * + * 12 * * * + * 13 rip.Berkeley.EDU (128.32.131.22) 59 ms ! 39 ms ! 39 ms ! + * + * Notice that there are 12 "gateways" (13 is the final + * destination) and exactly the last half of them are "missing". + * What's really happening is that rip (a Sun-3 running Sun OS3.5) + * is using the ttl from our arriving datagram as the ttl in its + * icmp reply. So, the reply will time out on the return path + * (with no notice sent to anyone since icmp's aren't sent for + * icmp's) until we probe with a ttl that's at least twice the path + * length. I.e., rip is really only 7 hops away. A reply that + * returns with a ttl of 1 is a clue this problem exists. + * Traceroute prints a "!" after the time if the ttl is <= 1. + * Since vendors ship a lot of obsolete (DEC's Ultrix, Sun 3.x) or + * non-standard (HPUX) software, expect to see this problem + * frequently and/or take care picking the target host of your + * probes. + * + * Other possible annotations after the time are !H, !N, !P (got a host, + * network or protocol unreachable, respectively), !S or !F (source + * route failed or fragmentation needed -- neither of these should + * ever occur and the associated gateway is busted if you see one). If + * almost all the probes result in some kind of unreachable, traceroute + * will give up and exit. + * + * Notes + * ----- + * This program must be run by root or be setuid. (I suggest that + * you *don't* make it setuid -- casual use could result in a lot + * of unnecessary traffic on our poor, congested nets.) + * + * This program requires a kernel mod that does not appear in any + * system available from Berkeley: A raw ip socket using proto + * IPPROTO_RAW must interpret the data sent as an ip datagram (as + * opposed to data to be wrapped in a ip datagram). See the README + * file that came with the source to this program for a description + * of the mods I made to /sys/netinet/raw_ip.c. Your mileage may + * vary. But, again, ANY 4.x (x < 4) BSD KERNEL WILL HAVE TO BE + * MODIFIED TO RUN THIS PROGRAM. + * + * The udp port usage may appear bizarre (well, ok, it is bizarre). + * The problem is that an icmp message only contains 8 bytes of + * data from the original datagram. 8 bytes is the size of a udp + * header so, if we want to associate replies with the original + * datagram, the necessary information must be encoded into the + * udp header (the ip id could be used but there's no way to + * interlock with the kernel's assignment of ip id's and, anyway, + * it would have taken a lot more kernel hacking to allow this + * code to set the ip id). So, to allow two or more users to + * use traceroute simultaneously, we use this task's pid as the + * source port (the high bit is set to move the port number out + * of the "likely" range). To keep track of which probe is being + * replied to (so times and/or hop counts don't get confused by a + * reply that was delayed in transit), we increment the destination + * port number before each probe. + * + * Don't use this as a coding example. I was trying to find a + * routing problem and this code sort-of popped out after 48 hours + * without sleep. I was amazed it ever compiled, much less ran. + * + * I stole the idea for this program from Steve Deering. Since + * the first release, I've learned that had I attended the right + * IETF working group meetings, I also could have stolen it from Guy + * Almes or Matt Mathis. I don't know (or care) who came up with + * the idea first. I envy the originators' perspicacity and I'm + * glad they didn't keep the idea a secret. + * + * Tim Seaver, Ken Adelman and C. Philip Wood provided bug fixes and/or + * enhancements to the original distribution. + * + * I've hacked up a round-trip-route version of this that works by + * sending a loose-source-routed udp datagram through the destination + * back to yourself. Unfortunately, SO many gateways botch source + * routing, the thing is almost worthless. Maybe one day... + * + * -- Van Jacobson (van@ee.lbl.gov) + * Tue Dec 20 03:50:13 PST 1988 + */ + +#define TRACEROUTE_SO_DEBUG 0 + +/* TODO: undefs were uncommented - ??! we have config system for that! */ +/* probably ok to remove altogether */ +//#undef CONFIG_FEATURE_TRACEROUTE_VERBOSE +//#define CONFIG_FEATURE_TRACEROUTE_VERBOSE +//#undef CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE +//#define CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE +//#undef CONFIG_FEATURE_TRACEROUTE_USE_ICMP +//#define CONFIG_FEATURE_TRACEROUTE_USE_ICMP + +#include "inet_common.h" + +#include +#include +#include +#include +#include +#include + +#include "busybox.h" + + +/* + * Definitions for internet protocol version 4. + * Per RFC 791, September 1981. + */ +#define IPVERSION 4 + +#ifndef IPPROTO_ICMP +/* Grrrr.... */ +#define IPPROTO_ICMP 1 +#endif +#ifndef IPPROTO_IP +#define IPPROTO_IP 0 +#endif + +/* + * Overlay for ip header used by other protocols (tcp, udp). + */ +struct ipovly { + unsigned char ih_x1[9]; /* (unused) */ + unsigned char ih_pr; /* protocol */ + short ih_len; /* protocol length */ + struct in_addr ih_src; /* source internet address */ + struct in_addr ih_dst; /* destination internet address */ +}; + +/* + * UDP kernel structures and variables. + */ +struct udpiphdr { + struct ipovly ui_i; /* overlaid ip structure */ + struct udphdr ui_u; /* udp header */ +}; +#define ui_next ui_i.ih_next +#define ui_prev ui_i.ih_prev +#define ui_x1 ui_i.ih_x1 +#define ui_pr ui_i.ih_pr +#define ui_len ui_i.ih_len +#define ui_src ui_i.ih_src +#define ui_dst ui_i.ih_dst +#define ui_sport ui_u.uh_sport +#define ui_dport ui_u.uh_dport +#define ui_ulen ui_u.uh_ulen +#define ui_sum ui_u.uh_sum + + +/* Host name and address list */ +struct hostinfo { + char *name; + int n; + u_int32_t *addrs; +}; + +/* Data section of the probe packet */ +struct outdata { + unsigned char seq; /* sequence number of this packet */ + unsigned char ttl; /* ttl packet left with */ + struct timeval tv ATTRIBUTE_PACKED; /* time packet left */ +}; + +struct IFADDRLIST { + u_int32_t addr; + char device[sizeof(struct ifreq)]; +}; + + +static const char route[] = "/proc/net/route"; + +/* last inbound (icmp) packet */ +static unsigned char packet[512] ATTRIBUTE_ALIGNED(32); + +static struct ip *outip; /* last output (udp) packet */ +static struct udphdr *outudp; /* last output (udp) packet */ +static struct outdata *outdata; /* last output (udp) packet */ + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP +static struct icmp *outicmp; /* last output (icmp) packet */ +#endif + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +/* Maximum number of gateways (include room for one noop) */ +#define NGATEWAYS ((int)((MAX_IPOPTLEN - IPOPT_MINOFF - 1) / sizeof(u_int32_t))) +/* loose source route gateway list (including room for final destination) */ +static u_int32_t gwlist[NGATEWAYS + 1]; +#endif + +static int s; /* receive (icmp) socket file descriptor */ +static int sndsock; /* send (udp/icmp) socket file descriptor */ + +static struct sockaddr_storage whereto; /* Who to try to reach */ +static struct sockaddr_storage wherefrom; /* Who we are */ +static int packlen; /* total length of packet */ +static int minpacket; /* min ip packet size */ +static int maxpacket = 32 * 1024; /* max ip packet size */ +static int pmtu; /* Path MTU Discovery (RFC1191) */ + +static char *hostname; + +static u_short ident; +static u_short port = 32768 + 666; /* start udp dest port # for probe packets */ + +static int waittime = 5; /* time to wait for response (in seconds) */ +static int nflag; /* print addresses numerically */ +static int doipcksum = 1; /* calculate ip checksums by default */ + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +static int optlen; /* length of ip options */ +#else +#define optlen 0 +#endif + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP +static int useicmp; /* use icmp echo instead of udp packets */ +#endif +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE +static int verbose; +#endif + +/* + * Return the interface list + */ +static int +ifaddrlist(struct IFADDRLIST **ipaddrp) +{ + int fd, nipaddr; +#ifdef HAVE_SOCKADDR_SA_LEN + int n; +#endif + struct ifreq *ifrp, *ifend, *ifnext; + struct sockaddr_in *addr_sin; + struct IFADDRLIST *al; + struct ifconf ifc; + struct ifreq ibuf[(32 * 1024) / sizeof(struct ifreq)], ifr; + struct IFADDRLIST *st_ifaddrlist; + + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + + ifc.ifc_len = sizeof(ibuf); + ifc.ifc_buf = (caddr_t)ibuf; + + if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0 || + ifc.ifc_len < sizeof(struct ifreq)) { + if (errno == EINVAL) + bb_error_msg_and_die( + "SIOCGIFCONF: ifreq struct too small (%d bytes)", + (int)sizeof(ibuf)); + else + bb_perror_msg_and_die("SIOCGIFCONF"); + } + ifrp = ibuf; + ifend = (struct ifreq *)((char *)ibuf + ifc.ifc_len); + + nipaddr = 1 + (ifc.ifc_len / sizeof(struct ifreq)); + st_ifaddrlist = xzalloc(nipaddr * sizeof(struct IFADDRLIST)); + al = st_ifaddrlist; + nipaddr = 0; + + for (; ifrp < ifend; ifrp = ifnext) { +#ifdef HAVE_SOCKADDR_SA_LEN + n = ifrp->ifr_addr.sa_len + sizeof(ifrp->ifr_name); + if (n < sizeof(*ifrp)) + ifnext = ifrp + 1; + else + ifnext = (struct ifreq *)((char *)ifrp + n); + if (ifrp->ifr_addr.sa_family != AF_INET) + continue; +#else + ifnext = ifrp + 1; +#endif + /* + * Need a template to preserve address info that is + * used below to locate the next entry. (Otherwise, + * SIOCGIFFLAGS stomps over it because the requests + * are returned in a union.) + */ + strncpy(ifr.ifr_name, ifrp->ifr_name, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFFLAGS, (char *)&ifr) < 0) { + if (errno == ENXIO) + continue; + bb_perror_msg_and_die("SIOCGIFFLAGS: %.*s", + (int)sizeof(ifr.ifr_name), ifr.ifr_name); + } + + /* Must be up */ + if ((ifr.ifr_flags & IFF_UP) == 0) + continue; + + safe_strncpy(al->device, ifr.ifr_name, sizeof(ifr.ifr_name) + 1); +#ifdef sun + /* Ignore sun virtual interfaces */ + if (strchr(al->device, ':') != NULL) + continue; +#endif + if (ioctl(fd, SIOCGIFADDR, (char *)&ifr) < 0) + bb_perror_msg_and_die("SIOCGIFADDR: %s", al->device); + + addr_sin = (struct sockaddr_in *)&ifr.ifr_addr; + al->addr = addr_sin->sin_addr.s_addr; + ++al; + ++nipaddr; + } + if (nipaddr == 0) + bb_error_msg_and_die ("can't find any network interfaces"); + (void)close(fd); + + *ipaddrp = st_ifaddrlist; + return nipaddr; +} + + +static void +setsin(struct sockaddr_in *addr_sin, u_int32_t addr) +{ + memset(addr_sin, 0, sizeof(*addr_sin)); +#ifdef HAVE_SOCKADDR_SA_LEN + addr_sin->sin_len = sizeof(*addr_sin); +#endif + addr_sin->sin_family = AF_INET; + addr_sin->sin_addr.s_addr = addr; +} + + +/* + * Return the source address for the given destination address + */ +static void +findsaddr(const struct sockaddr_in *to, struct sockaddr_in *from) +{ + int i, n; + FILE *f; + u_int32_t mask; + u_int32_t dest, tmask; + struct IFADDRLIST *al; + char buf[256], tdevice[256], device[256]; + + f = xfopen(route, "r"); + + /* Find the appropriate interface */ + n = 0; + mask = 0; + device[0] = '\0'; + while (fgets(buf, sizeof(buf), f) != NULL) { + ++n; + if (n == 1 && strncmp(buf, "Iface", 5) == 0) + continue; + if ((i = sscanf(buf, "%255s %x %*s %*s %*s %*s %*s %x", + tdevice, &dest, &tmask)) != 3) + bb_error_msg_and_die ("junk in buffer"); + if ((to->sin_addr.s_addr & tmask) == dest && + (tmask > mask || mask == 0)) { + mask = tmask; + strcpy(device, tdevice); + } + } + fclose(f); + + if (device[0] == '\0') + bb_error_msg_and_die ("can't find interface"); + + /* Get the interface address list */ + n = ifaddrlist(&al); + + /* Find our appropriate source address */ + for (i = n; i > 0; --i, ++al) + if (strcmp(device, al->device) == 0) + break; + if (i <= 0) + bb_error_msg_and_die("can't find interface %s", device); + + setsin(from, al->addr); +} + +/* +"Usage: %s [-dFIlnrvx] [-g gateway] [-i iface] [-f first_ttl]\n" +"\t[-m max_ttl] [ -p port] [-q nqueries] [-s src_addr] [-t tos]\n" +"\t[-w waittime] [-z pausemsecs] host [packetlen]" + +*/ + +/* + * Subtract 2 timeval structs: out = out - in. + * Out is assumed to be >= in. + */ +static inline void +tvsub(struct timeval *out, struct timeval *in) +{ + + if ((out->tv_usec -= in->tv_usec) < 0) { + --out->tv_sec; + out->tv_usec += 1000000; + } + out->tv_sec -= in->tv_sec; +} + +static int +wait_for_reply(int sock, struct sockaddr_in *fromp, const struct timeval *tp) +{ + fd_set fds; + struct timeval now, tvwait; + struct timezone tz; + int cc = 0; + socklen_t fromlen = sizeof(*fromp); + + FD_ZERO(&fds); + FD_SET(sock, &fds); + + tvwait.tv_sec = tp->tv_sec + waittime; + tvwait.tv_usec = tp->tv_usec; + (void)gettimeofday(&now, &tz); + tvsub(&tvwait, &now); + + if (select(sock + 1, &fds, NULL, NULL, &tvwait) > 0) + cc = recvfrom(sock, (char *)packet, sizeof(packet), 0, + (struct sockaddr *)fromp, &fromlen); + + return cc; +} + +/* + * Checksum routine for Internet Protocol family headers (C Version) + */ +static u_short +in_cksum(u_short *addr, int len) +{ + int nleft = len; + u_short *w = addr; + u_short answer; + int sum = 0; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), + * we add sequential 16 bit words to it, and at the end, fold + * back all the carry bits from the top 16 bits into the lower + * 16 bits. + */ + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) + sum += *(unsigned char *)w; + + /* + * add back carry outs from top 16 bits to low 16 bits + */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* truncate to 16 bits */ + return answer; +} + + +static void +send_probe(int seq, int ttl, struct timeval *tp) +{ + int cc; + struct udpiphdr *ui, *oui; + struct ip tip; + + outip->ip_ttl = ttl; + outip->ip_id = htons(ident + seq); + + /* + * In most cases, the kernel will recalculate the ip checksum. + * But we must do it anyway so that the udp checksum comes out + * right. + */ + if (doipcksum) { + outip->ip_sum = + in_cksum((u_short *)outip, sizeof(*outip) + optlen); + if (outip->ip_sum == 0) + outip->ip_sum = 0xffff; + } + + /* Payload */ + outdata->seq = seq; + outdata->ttl = ttl; + memcpy(&outdata->tv, tp, sizeof(outdata->tv)); + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) + outicmp->icmp_seq = htons(seq); + else +#endif + outudp->dest = htons(port + seq); + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) { + /* Always calculate checksum for icmp packets */ + outicmp->icmp_cksum = 0; + outicmp->icmp_cksum = in_cksum((u_short *)outicmp, + packlen - (sizeof(*outip) + optlen)); + if (outicmp->icmp_cksum == 0) + outicmp->icmp_cksum = 0xffff; + } else +#endif + if (doipcksum) { + /* Checksum (we must save and restore ip header) */ + tip = *outip; + ui = (struct udpiphdr *)outip; + oui = (struct udpiphdr *)&tip; + /* Easier to zero and put back things that are ok */ + memset((char *)ui, 0, sizeof(ui->ui_i)); + ui->ui_src = oui->ui_src; + ui->ui_dst = oui->ui_dst; + ui->ui_pr = oui->ui_pr; + ui->ui_len = outudp->len; + outudp->check = 0; + outudp->check = in_cksum((u_short *)ui, packlen); + if (outudp->check == 0) + outudp->check = 0xffff; + *outip = tip; + } + +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + /* XXX undocumented debugging hack */ + if (verbose > 1) { + const u_short *sp; + int nshorts, i; + + sp = (u_short *)outip; + nshorts = (u_int)packlen / sizeof(u_short); + i = 0; + printf("[ %d bytes", packlen); + while (--nshorts >= 0) { + if ((i++ % 8) == 0) + printf("\n\t"); + printf(" %04x", ntohs(*sp)); + sp++; + } + if (packlen & 1) { + if ((i % 8) == 0) + printf("\n\t"); + printf(" %02x", *(unsigned char *)sp); + } + printf("]\n"); + } +#endif + +#if !defined(IP_HDRINCL) && defined(IP_TTL) + if (setsockopt(sndsock, IPPROTO_IP, IP_TTL, + (char *)&ttl, sizeof(ttl)) < 0) { + bb_perror_msg_and_die("setsockopt ttl %d", ttl); + } +#endif + + cc = sendto(sndsock, (char *)outip, + packlen, 0, (struct sockaddr *)&whereto, sizeof(whereto)); + if (cc < 0 || cc != packlen) { + if (cc < 0) + bb_perror_msg_and_die("sendto"); + printf("%s: wrote %s %d chars, ret=%d\n", + applet_name, hostname, packlen, cc); + (void)fflush(stdout); + } +} + +static inline double +deltaT(struct timeval *t1p, struct timeval *t2p) +{ + double dt; + + dt = (double)(t2p->tv_sec - t1p->tv_sec) * 1000.0 + + (double)(t2p->tv_usec - t1p->tv_usec) / 1000.0; + return dt; +} + +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE +/* + * Convert an ICMP "type" field to a printable string. + */ +static inline const char * +pr_type(unsigned char t) +{ + static const char * const ttab[] = { + "Echo Reply", "ICMP 1", "ICMP 2", "Dest Unreachable", + "Source Quench", "Redirect", "ICMP 6", "ICMP 7", + "Echo", "Router Advert", "Router Solicit", "Time Exceeded", + "Param Problem", "Timestamp", "Timestamp Reply", "Info Request", + "Info Reply", "Mask Request", "Mask Reply" + }; + + if (t > 18) + return "OUT-OF-RANGE"; + + return ttab[t]; +} +#endif + +static int +packet_ok(unsigned char *buf, int cc, struct sockaddr_in *from, int seq) +{ + struct icmp *icp; + unsigned char type, code; + int hlen; + struct ip *ip; + + ip = (struct ip *) buf; + hlen = ip->ip_hl << 2; + if (cc < hlen + ICMP_MINLEN) { +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + if (verbose) + printf("packet too short (%d bytes) from %s\n", cc, + inet_ntoa(from->sin_addr)); +#endif + return 0; + } + cc -= hlen; + icp = (struct icmp *)(buf + hlen); + type = icp->icmp_type; + code = icp->icmp_code; + /* Path MTU Discovery (RFC1191) */ + if (code != ICMP_UNREACH_NEEDFRAG) + pmtu = 0; + else { + pmtu = ntohs(icp->icmp_nextmtu); + } + if ((type == ICMP_TIMXCEED && code == ICMP_TIMXCEED_INTRANS) || + type == ICMP_UNREACH || type == ICMP_ECHOREPLY) { + struct ip *hip; + struct udphdr *up; + + hip = &icp->icmp_ip; + hlen = hip->ip_hl << 2; +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) { + struct icmp *hicmp; + + /* XXX */ + if (type == ICMP_ECHOREPLY && + icp->icmp_id == htons(ident) && + icp->icmp_seq == htons(seq)) + return -2; + + hicmp = (struct icmp *)((unsigned char *)hip + hlen); + /* XXX 8 is a magic number */ + if (hlen + 8 <= cc && + hip->ip_p == IPPROTO_ICMP && + hicmp->icmp_id == htons(ident) && + hicmp->icmp_seq == htons(seq)) + return (type == ICMP_TIMXCEED ? -1 : code + 1); + } else +#endif + { + up = (struct udphdr *)((unsigned char *)hip + hlen); + /* XXX 8 is a magic number */ + if (hlen + 12 <= cc && + hip->ip_p == IPPROTO_UDP && + up->source == htons(ident) && + up->dest == htons(port + seq)) + return (type == ICMP_TIMXCEED ? -1 : code + 1); + } + } +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + if (verbose) { + int i; + u_int32_t *lp = (u_int32_t *)&icp->icmp_ip; + + printf("\n%d bytes from %s to " + "%s: icmp type %d (%s) code %d\n", + cc, inet_ntoa(from->sin_addr), + inet_ntoa(ip->ip_dst), type, pr_type(type), icp->icmp_code); + for (i = 4; i < cc ; i += sizeof(*lp)) + printf("%2d: x%8.8x\n", i, *lp++); + } +#endif + return 0; +} + + +/* + * Construct an Internet address representation. + * If the nflag has been supplied, give + * numeric value, otherwise try for symbolic name. + */ +static inline void +inetname(struct sockaddr_in *from) +{ + const char *n = NULL; + const char *ina; + char name[257]; + + if (!nflag && from->sin_addr.s_addr != INADDR_ANY) { + if (INET_rresolve(name, sizeof(name), from, 0x4000, 0xffffffff) >= 0) + n = name; + } + ina = inet_ntoa(from->sin_addr); + if (nflag) + printf(" %s", ina); + else + printf(" %s (%s)", (n ? n : ina), ina); +} + +static inline void +print(unsigned char *buf, int cc, struct sockaddr_in *from) +{ + struct ip *ip; + int hlen; + + ip = (struct ip *) buf; + hlen = ip->ip_hl << 2; + cc -= hlen; + + inetname(from); +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + if (verbose) + printf(" %d bytes to %s", cc, inet_ntoa (ip->ip_dst)); +#endif +} + + +static struct hostinfo * +gethostinfo(const char *host) +{ + int n; + struct hostent *hp; + struct hostinfo *hi; + char **p; + u_int32_t addr, *ap; + + hi = xzalloc(sizeof(*hi)); + addr = inet_addr(host); + if ((int32_t)addr != -1) { + hi->name = xstrdup(host); + hi->n = 1; + hi->addrs = xzalloc(sizeof(hi->addrs[0])); + hi->addrs[0] = addr; + return hi; + } + + hp = xgethostbyname(host); + if (hp->h_addrtype != AF_INET || hp->h_length != 4) + bb_perror_msg_and_die("bad host %s", host); + hi->name = xstrdup(hp->h_name); + for (n = 0, p = hp->h_addr_list; *p != NULL; ++n, ++p) + continue; + hi->n = n; + hi->addrs = xzalloc(n * sizeof(hi->addrs[0])); + for (ap = hi->addrs, p = hp->h_addr_list; *p != NULL; ++ap, ++p) + memcpy(ap, *p, sizeof(*ap)); + return hi; +} + +static void +freehostinfo(struct hostinfo *hi) +{ + free(hi->name); + hi->name = NULL; + free((char *)hi->addrs); + free((char *)hi); +} + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +static void +getaddr(u_int32_t *ap, const char *host) +{ + struct hostinfo *hi; + + hi = gethostinfo(host); + *ap = hi->addrs[0]; + freehostinfo(hi); +} +#endif + + +int +traceroute_main(int argc, char *argv[]) +{ + static const int on = 1; + + int code, n; + unsigned char *outp; + u_int32_t *ap; + struct sockaddr_in *from = (struct sockaddr_in *)&wherefrom; + struct sockaddr_in *to = (struct sockaddr_in *)&whereto; + struct hostinfo *hi; + int ttl, probe, i; + int seq = 0; + int tos = 0; + char *tos_str = NULL; + char *source = NULL; + unsigned long op; + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + int lsrr = 0; +#endif + u_short off = 0; + struct IFADDRLIST *al; + char *device = NULL; + int max_ttl = 30; + char *max_ttl_str = NULL; + char *port_str = NULL; + int nprobes = 3; + char *nprobes_str = NULL; + char *waittime_str = NULL; + u_int pausemsecs = 0; + char *pausemsecs_str = NULL; + int first_ttl = 1; + char *first_ttl_str = NULL; +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + llist_t *sourse_route_list = NULL; +#endif + + opterr = 0; +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + opt_complementary = "x-x:g::"; +#else + opt_complementary = "x-x"; +#endif + + op = getopt32(argc, argv, "FIlnrdvxt:i:m:p:q:s:w:z:f:" +#define USAGE_OP_DONT_FRAGMNT (1<<0) /* F */ +#define USAGE_OP_USE_ICMP (1<<1) /* I */ +#define USAGE_OP_TTL_FLAG (1<<2) /* l */ +#define USAGE_OP_ADDR_NUM (1<<3) /* n */ +#define USAGE_OP_BYPASS_ROUTE (1<<4) /* r */ +#define USAGE_OP_DEBUG (1<<5) /* d */ +#define USAGE_OP_VERBOSE (1<<6) /* v */ +#define USAGE_OP_IP_CHKSUM (1<<7) /* x */ + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + "g:" +#endif + , &tos_str, &device, &max_ttl_str, &port_str, &nprobes_str, + &source, &waittime_str, &pausemsecs_str, &first_ttl_str +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + , &sourse_route_list +#endif + ); + + if (op & USAGE_OP_DONT_FRAGMNT) + off = IP_DF; +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + useicmp = op & USAGE_OP_USE_ICMP; +#endif + nflag = op & USAGE_OP_ADDR_NUM; +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + verbose = op & USAGE_OP_VERBOSE; +#endif + if (op & USAGE_OP_IP_CHKSUM) { + doipcksum = 0; + bb_error_msg("warning: ip checksums disabled"); + } + if (tos_str) + tos = xatoul_range(tos_str, 0, 255); + if (max_ttl_str) + max_ttl = xatoul_range(max_ttl_str, 1, 255); + if (port_str) + port = xatou16(port_str); + if (nprobes_str) + nprobes = xatoul_range(nprobes_str, 1, INT_MAX); + if (source) { + /* + * set the ip source address of the outbound + * probe (e.g., on a multi-homed host). + */ + if (getuid()) bb_error_msg_and_die("-s %s: permission denied", source); + } + if (waittime_str) + waittime = xatoul_range(waittime_str, 2, 24 * 60 * 60); + if (pausemsecs_str) + pausemsecs = xatoul_range(pausemsecs_str, 0, 60 * 60 * 1000); + if (first_ttl_str) + first_ttl = xatoul_range(first_ttl_str, 1, 255); + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + if (sourse_route_list) { + llist_t *l_sr; + + for(l_sr = sourse_route_list; l_sr; ) { + if (lsrr >= NGATEWAYS) + bb_error_msg_and_die("no more than %d gateways", NGATEWAYS); + getaddr(gwlist + lsrr, l_sr->data); + ++lsrr; + l_sr = l_sr->link; + free(sourse_route_list); + sourse_route_list = l_sr; + } + optlen = (lsrr + 1) * sizeof(gwlist[0]); + } +#endif + + if (first_ttl > max_ttl) { + bb_error_msg_and_die( + "first ttl (%d) may not be greater than max ttl (%d)", + first_ttl, max_ttl); + } + + minpacket = sizeof(*outip) + sizeof(*outdata) + optlen; + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) + minpacket += 8; /* XXX magic number */ + else +#endif + minpacket += sizeof(*outudp); + packlen = minpacket; /* minimum sized packet */ + + /* Process destination and optional packet size */ + switch (argc - optind) { + + case 2: + packlen = xatoul_range(argv[optind + 1], minpacket, maxpacket); + /* Fall through */ + + case 1: + hostname = argv[optind]; + hi = gethostinfo(hostname); + setsin(to, hi->addrs[0]); + if (hi->n > 1) + bb_error_msg("warning: %s has multiple addresses; using %s", + hostname, inet_ntoa(to->sin_addr)); + hostname = hi->name; + hi->name = NULL; + freehostinfo(hi); + break; + + default: + bb_show_usage(); + } + + /* Insure the socket fds won't be 0, 1 or 2 */ + do n = xopen(bb_dev_null, O_RDONLY); while (n < 2); + if (n > 2) + close(n); + + s = xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + +#if TRACEROUTE_SO_DEBUG + if (op & USAGE_OP_DEBUG) + (void)setsockopt(s, SOL_SOCKET, SO_DEBUG, (char *)&on, + sizeof(on)); +#endif + if (op & USAGE_OP_BYPASS_ROUTE) + (void)setsockopt(s, SOL_SOCKET, SO_DONTROUTE, (char *)&on, + sizeof(on)); + + sndsock = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW); + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +#if defined(IP_OPTIONS) + if (lsrr > 0) { + unsigned char optlist[MAX_IPOPTLEN]; + + /* final hop */ + gwlist[lsrr] = to->sin_addr.s_addr; + ++lsrr; + + /* force 4 byte alignment */ + optlist[0] = IPOPT_NOP; + /* loose source route option */ + optlist[1] = IPOPT_LSRR; + i = lsrr * sizeof(gwlist[0]); + optlist[2] = i + 3; + /* Pointer to LSRR addresses */ + optlist[3] = IPOPT_MINOFF; + memcpy(optlist + 4, gwlist, i); + + if ((setsockopt(sndsock, IPPROTO_IP, IP_OPTIONS, + (char *)optlist, i + sizeof(gwlist[0]))) < 0) { + bb_perror_msg_and_die("IP_OPTIONS"); + } + } +#endif /* IP_OPTIONS */ +#endif /* CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE */ + +#ifdef SO_SNDBUF + if (setsockopt(sndsock, SOL_SOCKET, SO_SNDBUF, (char *)&packlen, + sizeof(packlen)) < 0) { + bb_perror_msg_and_die("SO_SNDBUF"); + } +#endif +#ifdef IP_HDRINCL + if (setsockopt(sndsock, IPPROTO_IP, IP_HDRINCL, (char *)&on, + sizeof(on)) < 0 && errno != ENOPROTOOPT) { + bb_perror_msg_and_die("IP_HDRINCL"); + } +#else +#ifdef IP_TOS + if (tos_str && setsockopt(sndsock, IPPROTO_IP, IP_TOS, + (char *)&tos, sizeof(tos)) < 0) { + bb_perror_msg_and_die("setsockopt tos %d", tos); + } +#endif +#endif +#if TRACEROUTE_SO_DEBUG + if (op & USAGE_OP_DEBUG) + (void)setsockopt(sndsock, SOL_SOCKET, SO_DEBUG, (char *)&on, + sizeof(on)); +#endif + if (op & USAGE_OP_BYPASS_ROUTE) + (void)setsockopt(sndsock, SOL_SOCKET, SO_DONTROUTE, (char *)&on, + sizeof(on)); + + /* Revert to non-privileged user after opening sockets */ + xsetgid(getgid()); + xsetuid(getuid()); + + outip = (struct ip *)xzalloc(packlen); + + outip->ip_v = IPVERSION; + if (tos_str) + outip->ip_tos = tos; + outip->ip_len = htons(packlen); + outip->ip_off = htons(off); + outp = (unsigned char *)(outip + 1); + outip->ip_dst = to->sin_addr; + + outip->ip_hl = (outp - (unsigned char *)outip) >> 2; + ident = (getpid() & 0xffff) | 0x8000; +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) { + outip->ip_p = IPPROTO_ICMP; + + outicmp = (struct icmp *)outp; + outicmp->icmp_type = ICMP_ECHO; + outicmp->icmp_id = htons(ident); + + outdata = (struct outdata *)(outp + 8); /* XXX magic number */ + } else +#endif + { + outip->ip_p = IPPROTO_UDP; + + outudp = (struct udphdr *)outp; + outudp->source = htons(ident); + outudp->len = + htons((u_short)(packlen - (sizeof(*outip) + optlen))); + outdata = (struct outdata *)(outudp + 1); + } + + /* Get the interface address list */ + n = ifaddrlist(&al); + + /* Look for a specific device */ + if (device != NULL) { + for (i = n; i > 0; --i, ++al) + if (strcmp(device, al->device) == 0) + break; + if (i <= 0) { + bb_error_msg_and_die("can't find interface %s", device); + } + } + + /* Determine our source address */ + if (source == NULL) { + /* + * If a device was specified, use the interface address. + * Otherwise, try to determine our source address. + */ + if (device != NULL) + setsin(from, al->addr); + findsaddr(to, from); + } else { + hi = gethostinfo(source); + source = hi->name; + hi->name = NULL; + /* + * If the device was specified make sure it + * corresponds to the source address specified. + * Otherwise, use the first address (and warn if + * there are more than one). + */ + if (device != NULL) { + for (i = hi->n, ap = hi->addrs; i > 0; --i, ++ap) + if (*ap == al->addr) + break; + if (i <= 0) { + bb_error_msg_and_die( + "%s is not on interface %s", + source, device); + } + setsin(from, *ap); + } else { + setsin(from, hi->addrs[0]); + if (hi->n > 1) + bb_error_msg( + "Warning: %s has multiple addresses; using %s", + source, inet_ntoa(from->sin_addr)); + } + freehostinfo(hi); + } + + outip->ip_src = from->sin_addr; +#ifndef IP_HDRINCL + xbind(sndsock, (struct sockaddr *)from, sizeof(*from)); +#endif + + fprintf(stderr, "traceroute to %s (%s)", hostname, inet_ntoa(to->sin_addr)); + if (source) + fprintf(stderr, " from %s", source); + fprintf(stderr, ", %d hops max, %d byte packets\n", max_ttl, packlen); + (void)fflush(stderr); + + for (ttl = first_ttl; ttl <= max_ttl; ++ttl) { + u_int32_t lastaddr = 0; + int gotlastaddr = 0; + int got_there = 0; + int unreachable = 0; + int sentfirst = 0; + + printf("%2d ", ttl); + for (probe = 0; probe < nprobes; ++probe) { + int cc; + struct timeval t1, t2; + struct timezone tz; + struct ip *ip; + + if (sentfirst && pausemsecs > 0) + usleep(pausemsecs * 1000); + (void)gettimeofday(&t1, &tz); + send_probe(++seq, ttl, &t1); + ++sentfirst; + while ((cc = wait_for_reply(s, from, &t1)) != 0) { + (void)gettimeofday(&t2, &tz); + i = packet_ok(packet, cc, from, seq); + /* Skip short packet */ + if (i == 0) + continue; + if (!gotlastaddr || + from->sin_addr.s_addr != lastaddr) { + print(packet, cc, from); + lastaddr = from->sin_addr.s_addr; + ++gotlastaddr; + } + printf(" %.3f ms", deltaT(&t1, &t2)); + ip = (struct ip *)packet; + if (op & USAGE_OP_TTL_FLAG) + printf(" (%d)", ip->ip_ttl); + if (i == -2) { + if (ip->ip_ttl <= 1) + printf(" !"); + ++got_there; + break; + } + /* time exceeded in transit */ + if (i == -1) + break; + code = i - 1; + switch (code) { + + case ICMP_UNREACH_PORT: + if (ip->ip_ttl <= 1) + printf(" !"); + ++got_there; + break; + + case ICMP_UNREACH_NET: + ++unreachable; + printf(" !N"); + break; + + case ICMP_UNREACH_HOST: + ++unreachable; + printf(" !H"); + break; + + case ICMP_UNREACH_PROTOCOL: + ++got_there; + printf(" !P"); + break; + + case ICMP_UNREACH_NEEDFRAG: + ++unreachable; + printf(" !F-%d", pmtu); + break; + + case ICMP_UNREACH_SRCFAIL: + ++unreachable; + printf(" !S"); + break; + + case ICMP_UNREACH_FILTER_PROHIB: + case ICMP_UNREACH_NET_PROHIB: /* misuse */ + ++unreachable; + printf(" !A"); + break; + + case ICMP_UNREACH_HOST_PROHIB: + ++unreachable; + printf(" !C"); + break; + + case ICMP_UNREACH_HOST_PRECEDENCE: + ++unreachable; + printf(" !V"); + break; + + case ICMP_UNREACH_PRECEDENCE_CUTOFF: + ++unreachable; + printf(" !C"); + break; + + case ICMP_UNREACH_NET_UNKNOWN: + case ICMP_UNREACH_HOST_UNKNOWN: + ++unreachable; + printf(" !U"); + break; + + case ICMP_UNREACH_ISOLATED: + ++unreachable; + printf(" !I"); + break; + + case ICMP_UNREACH_TOSNET: + case ICMP_UNREACH_TOSHOST: + ++unreachable; + printf(" !T"); + break; + + default: + ++unreachable; + printf(" !<%d>", code); + break; + } + break; + } + if (cc == 0) + printf(" *"); + (void)fflush(stdout); + } + putchar('\n'); + if (got_there || + (unreachable > 0 && unreachable >= nprobes - 1)) + break; + } + return 0; +} diff --git a/networking/udhcp/Config.in b/networking/udhcp/Config.in new file mode 100644 index 000000000..f633473eb --- /dev/null +++ b/networking/udhcp/Config.in @@ -0,0 +1,67 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +config APP_UDHCPD + bool "udhcp Server (udhcpd)" + default n + help + uDHCPd is a DHCP server geared primarily toward embedded systems, + while striving to be fully functional and RFC compliant. + + See http://udhcp.busybox.net for further details. + +config APP_DHCPRELAY + bool "dhcprelay" + default n + depends on APP_UDHCPD + help + dhcprelay listens for dhcp requests on one or more interfaces + and forwards these requests to a different interface or dhcp + server. + +config APP_DUMPLEASES + bool "Lease display utility (dumpleases)" + default n + depends on APP_UDHCPD + help + dumpleases displays the leases written out by the udhcpd server. + Lease times are stored in the file by time remaining in lease, or + by the absolute time that it expires in seconds from epoch. + + See http://udhcp.busybox.net for further details. + +config APP_UDHCPC + bool "udhcp Client (udhcpc)" + default n + help + uDHCPc is a DHCP client geared primarily toward embedded systems, + while striving to be fully functional and RFC compliant. + + The udhcp client negotiates a lease with the DHCP server and + notifies a set of scripts when a lease is obtained or lost. + + See http://udhcp.busybox.net for further details. + +config FEATURE_UDHCP_SYSLOG + bool "Log udhcp messages to syslog" + default n + depends on APP_UDHCPD || APP_UDHCPC + select FEATURE_SYSLOG + help + If not daemonized, udhcpd prints its messages to stdout/stderr. + If this option is selected, it will also log them to syslog. + + See http://udhcp.busybox.net for further details. + +config FEATURE_UDHCP_DEBUG + bool "Compile udhcp with noisy debugging messages" + default n + depends on APP_UDHCPD || APP_UDHCPC + help + If selected, udhcpd will output extra debugging output. If using + this option, compile uDHCP with "-g", and do not fork the daemon to + the background. + + See http://udhcp.busybox.net for further details. diff --git a/networking/udhcp/Kbuild b/networking/udhcp/Kbuild new file mode 100644 index 000000000..dc2c01f61 --- /dev/null +++ b/networking/udhcp/Kbuild @@ -0,0 +1,18 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2004 by Erik Andersen +# +# Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +# + +lib-y:= +lib-$(CONFIG_APP_UDHCPC) += common.o options.o packet.o pidfile.o \ + signalpipe.o socket.o +lib-$(CONFIG_APP_UDHCPD) += common.o options.o packet.o pidfile.o \ + signalpipe.o socket.o +lib-$(CONFIG_APP_UDHCPC) += dhcpc.o clientpacket.o clientsocket.o \ + script.o +lib-$(CONFIG_APP_UDHCPD) += dhcpd.o arpping.o files.o leases.o \ + serverpacket.o static_leases.o +lib-$(CONFIG_APP_DUMPLEASES) += dumpleases.o +lib-$(CONFIG_APP_DHCPRELAY) += dhcprelay.o diff --git a/networking/udhcp/arpping.c b/networking/udhcp/arpping.c new file mode 100644 index 000000000..9c8b9c562 --- /dev/null +++ b/networking/udhcp/arpping.c @@ -0,0 +1,114 @@ +/* vi: set sw=4 ts=4: */ +/* + * arpping.c + * + * Mostly stolen from: dhcpcd - DHCP client daemon + * by Yoichi Hariguchi + */ + +#include +#include + +#include "common.h" +#include "dhcpd.h" + + +struct arpMsg { + /* Ethernet header */ + u_char h_dest[6]; /* destination ether addr */ + u_char h_source[6]; /* source ether addr */ + u_short h_proto; /* packet type ID field */ + + /* ARP packet */ + uint16_t htype; /* hardware type (must be ARPHRD_ETHER) */ + uint16_t ptype; /* protocol type (must be ETH_P_IP) */ + uint8_t hlen; /* hardware address length (must be 6) */ + uint8_t plen; /* protocol address length (must be 4) */ + uint16_t operation; /* ARP opcode */ + uint8_t sHaddr[6]; /* sender's hardware address */ + uint8_t sInaddr[4]; /* sender's IP address */ + uint8_t tHaddr[6]; /* target's hardware address */ + uint8_t tInaddr[4]; /* target's IP address */ + uint8_t pad[18]; /* pad for min. Ethernet payload (60 bytes) */ +} ATTRIBUTE_PACKED; + +/* args: yiaddr - what IP to ping + * ip - our ip + * mac - our arp address + * interface - interface to use + * retn: 1 addr free + * 0 addr used + * -1 error + */ + +/* FIXME: match response against chaddr */ +int arpping(uint32_t yiaddr, uint32_t ip, uint8_t *mac, char *interface) +{ + int timeout = 2; + int s; /* socket */ + int rv = 1; /* return value */ + struct sockaddr addr; /* for interface name */ + struct arpMsg arp; + fd_set fdset; + struct timeval tm; + time_t prevTime; + + + s = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)); + if (s == -1) { + bb_perror_msg(bb_msg_can_not_create_raw_socket); + return -1; + } + + if (setsockopt_broadcast(s) == -1) { + bb_perror_msg("cannot setsocketopt on raw socket"); + close(s); + return -1; + } + + /* send arp request */ + memset(&arp, 0, sizeof(arp)); + memcpy(arp.h_dest, MAC_BCAST_ADDR, 6); /* MAC DA */ + memcpy(arp.h_source, mac, 6); /* MAC SA */ + arp.h_proto = htons(ETH_P_ARP); /* protocol type (Ethernet) */ + arp.htype = htons(ARPHRD_ETHER); /* hardware type */ + arp.ptype = htons(ETH_P_IP); /* protocol type (ARP message) */ + arp.hlen = 6; /* hardware address length */ + arp.plen = 4; /* protocol address length */ + arp.operation = htons(ARPOP_REQUEST); /* ARP op code */ + memcpy(arp.sInaddr, &ip, sizeof(ip)); /* source IP address */ + memcpy(arp.sHaddr, mac, 6); /* source hardware address */ + memcpy(arp.tInaddr, &yiaddr, sizeof(yiaddr)); /* target IP address */ + + memset(&addr, 0, sizeof(addr)); + strcpy(addr.sa_data, interface); + if (sendto(s, &arp, sizeof(arp), 0, &addr, sizeof(addr)) < 0) + rv = 0; + + /* wait arp reply, and check it */ + tm.tv_usec = 0; + prevTime = uptime(); + while (timeout > 0) { + FD_ZERO(&fdset); + FD_SET(s, &fdset); + tm.tv_sec = timeout; + if (select(s + 1, &fdset, (fd_set *) NULL, (fd_set *) NULL, &tm) < 0) { + bb_perror_msg("error on ARPING request"); + if (errno != EINTR) rv = 0; + } else if (FD_ISSET(s, &fdset)) { + if (recv(s, &arp, sizeof(arp), 0) < 0 ) rv = 0; + if (arp.operation == htons(ARPOP_REPLY) && + memcmp(arp.tHaddr, mac, 6) == 0 && + *((uint32_t *) arp.sInaddr) == yiaddr) { + DEBUG("Valid arp reply received for this address"); + rv = 0; + break; + } + } + timeout -= uptime() - prevTime; + prevTime = uptime(); + } + close(s); + DEBUG("%salid arp replies for this address", rv ? "No v" : "V"); + return rv; +} diff --git a/networking/udhcp/clientpacket.c b/networking/udhcp/clientpacket.c new file mode 100644 index 000000000..15cbda2f5 --- /dev/null +++ b/networking/udhcp/clientpacket.c @@ -0,0 +1,224 @@ +/* vi: set sw=4 ts=4: */ +/* clientpacket.c + * + * Packet generation and dispatching functions for the DHCP client. + * + * Russ Dill July 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION +#include +#include +#else +#include +#include +#include +#endif + +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" + + +/* Create a random xid */ +unsigned long random_xid(void) +{ + static int initialized; + if (!initialized) { + unsigned long seed; + + if (open_read_close("/dev/urandom", &seed, sizeof(seed)) < 0) { + bb_info_msg("Cannot load seed " + "from /dev/urandom: %s", strerror(errno)); + seed = time(0); + } + srand(seed); + initialized++; + } + return rand(); +} + + +/* initialize a packet with the proper defaults */ +static void init_packet(struct dhcpMessage *packet, char type) +{ + udhcp_init_header(packet, type); + memcpy(packet->chaddr, client_config.arp, 6); + if (client_config.clientid) + add_option_string(packet->options, client_config.clientid); + if (client_config.hostname) add_option_string(packet->options, client_config.hostname); + if (client_config.fqdn) add_option_string(packet->options, client_config.fqdn); + add_option_string(packet->options, client_config.vendorclass); +} + + +/* Add a parameter request list for stubborn DHCP servers. Pull the data + * from the struct in options.c. Don't do bounds checking here because it + * goes towards the head of the packet. */ +static void add_requests(struct dhcpMessage *packet) +{ + int end = end_option(packet->options); + int i, len = 0; + + packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; + for (i = 0; dhcp_options[i].code; i++) + if (dhcp_options[i].flags & OPTION_REQ) + packet->options[end + OPT_DATA + len++] = dhcp_options[i].code; + packet->options[end + OPT_LEN] = len; + packet->options[end + OPT_DATA + len] = DHCP_END; + +} + + +/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */ +int send_discover(unsigned long xid, unsigned long requested) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPDISCOVER); + packet.xid = xid; + if (requested) + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + + add_requests(&packet); + bb_info_msg("Sending discover..."); + return udhcp_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); +} + + +/* Broadcasts a DHCP request message */ +int send_selecting(unsigned long xid, unsigned long server, unsigned long requested) +{ + struct dhcpMessage packet; + struct in_addr addr; + + init_packet(&packet, DHCPREQUEST); + packet.xid = xid; + + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + add_requests(&packet); + addr.s_addr = requested; + bb_info_msg("Sending select for %s...", inet_ntoa(addr)); + return udhcp_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); +} + + +/* Unicasts or broadcasts a DHCP renew message */ +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr) +{ + struct dhcpMessage packet; + int ret = 0; + + init_packet(&packet, DHCPREQUEST); + packet.xid = xid; + packet.ciaddr = ciaddr; + + add_requests(&packet); + bb_info_msg("Sending renew..."); + if (server) + ret = udhcp_kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); + else ret = udhcp_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); + return ret; +} + + +/* Unicasts a DHCP release message */ +int send_release(unsigned long server, unsigned long ciaddr) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPRELEASE); + packet.xid = random_xid(); + packet.ciaddr = ciaddr; + + add_simple_option(packet.options, DHCP_REQUESTED_IP, ciaddr); + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + bb_info_msg("Sending release..."); + return udhcp_kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); +} + + +/* return -1 on errors that are fatal for the socket, -2 for those that aren't */ +int get_raw_packet(struct dhcpMessage *payload, int fd) +{ + int bytes; + struct udp_dhcp_packet packet; + uint32_t source, dest; + uint16_t check; + + memset(&packet, 0, sizeof(struct udp_dhcp_packet)); + bytes = read(fd, &packet, sizeof(struct udp_dhcp_packet)); + if (bytes < 0) { + DEBUG("Cannot read on raw listening socket - ignoring"); + usleep(500000); /* possible down interface, looping condition */ + return -1; + } + + if (bytes < (int) (sizeof(struct iphdr) + sizeof(struct udphdr))) { + DEBUG("Message too short, ignoring"); + return -2; + } + + if (bytes < ntohs(packet.ip.tot_len)) { + DEBUG("Truncated packet"); + return -2; + } + + /* ignore any extra garbage bytes */ + bytes = ntohs(packet.ip.tot_len); + + /* Make sure its the right packet for us, and that it passes sanity checks */ + if (packet.ip.protocol != IPPROTO_UDP || packet.ip.version != IPVERSION + || packet.ip.ihl != sizeof(packet.ip) >> 2 + || packet.udp.dest != htons(CLIENT_PORT) + || bytes > (int) sizeof(struct udp_dhcp_packet) + || ntohs(packet.udp.len) != (uint16_t)(bytes - sizeof(packet.ip)) + ) { + DEBUG("Unrelated/bogus packet"); + return -2; + } + + /* check IP checksum */ + check = packet.ip.check; + packet.ip.check = 0; + if (check != udhcp_checksum(&(packet.ip), sizeof(packet.ip))) { + DEBUG("bad IP header checksum, ignoring"); + return -1; + } + + /* verify the UDP checksum by replacing the header with a psuedo header */ + source = packet.ip.saddr; + dest = packet.ip.daddr; + check = packet.udp.check; + packet.udp.check = 0; + memset(&packet.ip, 0, sizeof(packet.ip)); + + packet.ip.protocol = IPPROTO_UDP; + packet.ip.saddr = source; + packet.ip.daddr = dest; + packet.ip.tot_len = packet.udp.len; /* cheat on the psuedo-header */ + if (check && check != udhcp_checksum(&packet, bytes)) { + bb_error_msg("packet with bad UDP checksum received, ignoring"); + return -2; + } + + memcpy(payload, &(packet.data), bytes - (sizeof(packet.ip) + sizeof(packet.udp))); + + if (ntohl(payload->cookie) != DHCP_MAGIC) { + bb_error_msg("received bogus message (bad magic) - ignoring"); + return -2; + } + DEBUG("oooooh!!! got some!"); + return bytes - (sizeof(packet.ip) + sizeof(packet.udp)); + +} diff --git a/networking/udhcp/clientsocket.c b/networking/udhcp/clientsocket.c new file mode 100644 index 000000000..852061968 --- /dev/null +++ b/networking/udhcp/clientsocket.c @@ -0,0 +1,59 @@ +/* vi: set sw=4 ts=4: */ +/* + * clientsocket.c -- DHCP client socket creation + * + * udhcp client + * + * Russ Dill July 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION) +#include +#include +#else +#include +#include +#include +#endif + +#include "common.h" + + +int raw_socket(int ifindex) +{ + int fd; + struct sockaddr_ll sock; + + DEBUG("Opening raw socket on ifindex %d", ifindex); + fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (fd < 0) { + bb_perror_msg("socket"); + return -1; + } + + sock.sll_family = AF_PACKET; + sock.sll_protocol = htons(ETH_P_IP); + sock.sll_ifindex = ifindex; + if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) < 0) { + bb_perror_msg("bind"); + close(fd); + return -1; + } + + return fd; +} diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c new file mode 100644 index 000000000..3e916f422 --- /dev/null +++ b/networking/udhcp/common.c @@ -0,0 +1,75 @@ +/* vi: set sw=4 ts=4: */ +/* common.c + * + * Functions for debugging and logging as well as some other + * simple helper functions. + * + * Russ Dill 2001-2003 + * Rewritten by Vladimir Oleynik (C) 2003 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include + +#include "common.h" + + +long uptime(void) +{ + struct sysinfo info; + sysinfo(&info); + return info.uptime; +} + +/* + * This function makes sure our first socket calls + * aren't going to fd 1 (printf badness...) and are + * not later closed by daemon() + */ +static inline void sanitize_fds(void) +{ + int fd = xopen(bb_dev_null, O_RDWR); + while (fd < 3) + fd = dup(fd); + close(fd); +} + + +void udhcp_background(const char *pidfile) +{ +#ifdef __uClinux__ + bb_error_msg("cannot background in uclinux (yet)"); +#else /* __uClinux__ */ + int pid_fd; + + /* hold lock during fork. */ + pid_fd = pidfile_acquire(pidfile); + setsid(); + xdaemon(0, 0); + logmode &= ~LOGMODE_STDIO; + pidfile_write_release(pid_fd); +#endif /* __uClinux__ */ +} + +void udhcp_start_log_and_pid(const char *pidfile) +{ + int pid_fd; + + /* Make sure our syslog fd isn't overwritten */ + sanitize_fds(); + + /* do some other misc startup stuff while we are here to save bytes */ + pid_fd = pidfile_acquire(pidfile); + pidfile_write_release(pid_fd); + + /* equivelent of doing a fflush after every \n */ + setlinebuf(stdout); + + if (ENABLE_FEATURE_UDHCP_SYSLOG) { + openlog(applet_name, LOG_PID, LOG_LOCAL0); + logmode |= LOGMODE_SYSLOG; + } + + bb_info_msg("%s (v%s) started", applet_name, BB_VER); +} diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h new file mode 100644 index 000000000..70a769342 --- /dev/null +++ b/networking/udhcp/common.h @@ -0,0 +1,108 @@ +/* vi: set sw=4 ts=4: */ +/* common.h + * + * Russ Dill September 2001 + * Rewritten by Vladimir Oleynik (C) 2003 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#ifndef _COMMON_H +#define _COMMON_H + +#include "busybox.h" + +#ifdef CONFIG_INSTALL_NO_USR +# define DEFAULT_SCRIPT "/share/udhcpc/default.script" +#else +# define DEFAULT_SCRIPT "/usr/share/udhcpc/default.script" +#endif + +#define COMBINED_BINARY + + +/*** packet.h ***/ + +#include +#include + +struct dhcpMessage { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t cookie; + uint8_t options[308]; /* 312 - cookie */ +}; + +struct udp_dhcp_packet { + struct iphdr ip; + struct udphdr udp; + struct dhcpMessage data; +}; + +void udhcp_init_header(struct dhcpMessage *packet, char type); +int udhcp_get_packet(struct dhcpMessage *packet, int fd); +uint16_t udhcp_checksum(void *addr, int count); +int udhcp_raw_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port, uint8_t *dest_arp, int ifindex); +int udhcp_kernel_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port); + + +/**/ + +void udhcp_background(const char *pidfile); +void udhcp_start_log_and_pid(const char *pidfile); + +void udhcp_run_script(struct dhcpMessage *packet, const char *name); + +// Still need to clean these up... + +/* from pidfile.h */ +#define pidfile_acquire udhcp_pidfile_acquire +#define pidfile_write_release udhcp_pidfile_write_release +/* from options.h */ +#define get_option udhcp_get_option +#define end_option udhcp_end_option +#define add_option_string udhcp_add_option_string +#define add_simple_option udhcp_add_simple_option +#define option_lengths udhcp_option_lengths +/* from socket.h */ +#define listen_socket udhcp_listen_socket +#define read_interface udhcp_read_interface +/* from dhcpc.h */ +#define client_config udhcp_client_config +/* from dhcpd.h */ +#define server_config udhcp_server_config + +long uptime(void); +void udhcp_sp_setup(void); +int udhcp_sp_fd_set(fd_set *rfds, int extra_fd); +int udhcp_sp_read(fd_set *rfds); +int raw_socket(int ifindex); +int read_interface(char *interface, int *ifindex, uint32_t *addr, uint8_t *arp); +int listen_socket(uint32_t ip, int port, char *inf); +int pidfile_acquire(const char *pidfile); +void pidfile_write_release(int pid_fd); +int arpping(uint32_t yiaddr, uint32_t ip, uint8_t *arp, char *interface); + +#if ENABLE_FEATURE_UDHCP_DEBUG +# define DEBUG(str, args...) bb_info_msg(str, ## args) +#else +# define DEBUG(str, args...) do {;} while (0) +#endif + +#endif diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c new file mode 100644 index 000000000..71315ff0a --- /dev/null +++ b/networking/udhcp/dhcpc.c @@ -0,0 +1,509 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpc.c + * + * udhcp DHCP client + * + * Russ Dill July 2001 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include + +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" + + +static int state; +/* Something is definitely wrong here. IPv4 addresses + * in variables of type long?? BTW, we use inet_ntoa() + * in the code. Manpage says that struct in_addr has a member of type long (!) + * which holds IPv4 address, and the struct is passed by value (!!) + */ +static unsigned long requested_ip; /* = 0 */ +static unsigned long server_addr; +static unsigned long timeout; +static int packet_num; /* = 0 */ +static int fd = -1; + +#define LISTEN_NONE 0 +#define LISTEN_KERNEL 1 +#define LISTEN_RAW 2 +static int listen_mode; + +struct client_config_t client_config; + + +/* just a little helper */ +static void change_mode(int new_mode) +{ + DEBUG("entering %s listen mode", + new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none"); + if (fd >= 0) close(fd); + fd = -1; + listen_mode = new_mode; +} + + +/* perform a renew */ +static void perform_renew(void) +{ + bb_info_msg("Performing a DHCP renew"); + switch (state) { + case BOUND: + change_mode(LISTEN_KERNEL); + case RENEWING: + case REBINDING: + state = RENEW_REQUESTED; + break; + case RENEW_REQUESTED: /* impatient are we? fine, square 1 */ + udhcp_run_script(NULL, "deconfig"); + case REQUESTING: + case RELEASED: + change_mode(LISTEN_RAW); + state = INIT_SELECTING; + break; + case INIT_SELECTING: + break; + } + + /* start things over */ + packet_num = 0; + + /* Kill any timeouts because the user wants this to hurry along */ + timeout = 0; +} + + +/* perform a release */ +static void perform_release(void) +{ + char buffer[16]; + struct in_addr temp_addr; + + /* send release packet */ + if (state == BOUND || state == RENEWING || state == REBINDING) { + temp_addr.s_addr = server_addr; + sprintf(buffer, "%s", inet_ntoa(temp_addr)); + temp_addr.s_addr = requested_ip; + bb_info_msg("Unicasting a release of %s to %s", + inet_ntoa(temp_addr), buffer); + send_release(server_addr, requested_ip); /* unicast */ + udhcp_run_script(NULL, "deconfig"); + } + bb_info_msg("Entering released state"); + + change_mode(LISTEN_NONE); + state = RELEASED; + timeout = 0x7fffffff; +} + + +static void client_background(void) +{ + udhcp_background(client_config.pidfile); + client_config.foreground = 1; /* Do not fork again. */ + client_config.background_if_no_lease = 0; +} + + +static uint8_t* alloc_dhcp_option(int code, const char *str, int extra) +{ + uint8_t *storage; + int len = strlen(str); + if (len > 255) len = 255; + storage = xzalloc(len + extra + OPT_DATA); + storage[OPT_CODE] = code; + storage[OPT_LEN] = len + extra; + memcpy(storage + extra + OPT_DATA, str, len); + return storage; +} + + +int udhcpc_main(int argc, char *argv[]) +{ + uint8_t *temp, *message; + char *str_c, *str_V, *str_h, *str_F, *str_r, *str_T, *str_t; + unsigned long t1 = 0, t2 = 0, xid = 0; + unsigned long start = 0, lease = 0; + long now; + unsigned opt; + int max_fd; + int sig; + int retval; + int len; + int no_clientid = 0; + fd_set rfds; + struct timeval tv; + struct dhcpMessage packet; + struct in_addr temp_addr; + + enum { + OPT_c = 1 << 0, + OPT_C = 1 << 1, + OPT_V = 1 << 2, + OPT_f = 1 << 3, + OPT_b = 1 << 4, + OPT_H = 1 << 5, + OPT_h = 1 << 6, + OPT_F = 1 << 7, + OPT_i = 1 << 8, + OPT_n = 1 << 9, + OPT_p = 1 << 10, + OPT_q = 1 << 11, + OPT_R = 1 << 12, + OPT_r = 1 << 13, + OPT_s = 1 << 14, + OPT_T = 1 << 15, + OPT_t = 1 << 16, + OPT_v = 1 << 17, + }; +#if ENABLE_GETOPT_LONG + static const struct option arg_options[] = { + { "clientid", required_argument, 0, 'c' }, + { "clientid-none", no_argument, 0, 'C' }, + { "vendorclass", required_argument, 0, 'V' }, + { "foreground", no_argument, 0, 'f' }, + { "background", no_argument, 0, 'b' }, + { "hostname", required_argument, 0, 'H' }, + { "hostname", required_argument, 0, 'h' }, + { "fqdn", required_argument, 0, 'F' }, + { "interface", required_argument, 0, 'i' }, + { "now", no_argument, 0, 'n' }, + { "pidfile", required_argument, 0, 'p' }, + { "quit", no_argument, 0, 'q' }, + { "release", no_argument, 0, 'R' }, + { "request", required_argument, 0, 'r' }, + { "script", required_argument, 0, 's' }, + { "timeout", required_argument, 0, 'T' }, + { "version", no_argument, 0, 'v' }, + { "retries", required_argument, 0, 't' }, + { 0, 0, 0, 0 } + }; +#endif + /* Default options. */ + client_config.interface = "eth0"; + client_config.script = DEFAULT_SCRIPT; + client_config.retries = 3; + client_config.timeout = 3; + + /* Parse command line */ + opt_complementary = "?:c--C:C--c" // mutually exclusive + ":hH:Hh"; // -h and -H are the same +#if ENABLE_GETOPT_LONG + applet_long_options = arg_options; +#endif + opt = getopt32(argc, argv, "c:CV:fbH:h:F:i:np:qRr:s:T:t:v", + &str_c, &str_V, &str_h, &str_h, &str_F, + &client_config.interface, &client_config.pidfile, &str_r, + &client_config.script, &str_T, &str_t + ); + + if (opt & OPT_c) + client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, str_c, 0); + if (opt & OPT_C) + no_clientid = 1; + if (opt & OPT_V) + client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0); + if (opt & OPT_f) + client_config.foreground = 1; + if (opt & OPT_b) + client_config.background_if_no_lease = 1; + if (opt & OPT_h) + client_config.hostname = alloc_dhcp_option(DHCP_HOST_NAME, str_h, 0); + if (opt & OPT_F) { + client_config.fqdn = alloc_dhcp_option(DHCP_FQDN, str_F, 3); + /* Flags: 0000NEOS + S: 1 => Client requests Server to update A RR in DNS as well as PTR + O: 1 => Server indicates to client that DNS has been updated regardless + E: 1 => Name data is DNS format, i.e. <4>host<6>domain<4>com<0> not "host.domain.com" + N: 1 => Client requests Server to not update DNS + */ + client_config.fqdn[OPT_DATA + 0] = 0x1; + /* client_config.fqdn[OPT_DATA + 1] = 0; - redundant */ + /* client_config.fqdn[OPT_DATA + 2] = 0; - redundant */ + } + // if (opt & OPT_i) client_config.interface = ... + if (opt & OPT_n) + client_config.abort_if_no_lease = 1; + // if (opt & OPT_p) client_config.pidfile = ... + if (opt & OPT_q) + client_config.quit_after_lease = 1; + if (opt & OPT_R) + client_config.release_on_quit = 1; + if (opt & OPT_r) + requested_ip = inet_addr(str_r); + // if (opt & OPT_s) client_config.script = ... + if (opt & OPT_T) + client_config.timeout = xatoi_u(str_T); + if (opt & OPT_t) + client_config.retries = xatoi_u(str_t); + if (opt & OPT_v) { + printf("version %s\n\n", BB_VER); + return 0; + } + + /* Start the log, sanitize fd's, and write a pid file */ + udhcp_start_log_and_pid(client_config.pidfile); + + if (read_interface(client_config.interface, &client_config.ifindex, + NULL, client_config.arp) < 0) + return 1; + + /* if not set, and not suppressed, setup the default client ID */ + if (!client_config.clientid && !no_clientid) { + client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, "", 7); + client_config.clientid[OPT_DATA] = 1; + memcpy(client_config.clientid + OPT_DATA+1, client_config.arp, 6); + } + + if (!client_config.vendorclass) + client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, "udhcp "BB_VER, 0); + + /* setup the signal pipe */ + udhcp_sp_setup(); + + state = INIT_SELECTING; + udhcp_run_script(NULL, "deconfig"); + change_mode(LISTEN_RAW); + + for (;;) { + tv.tv_sec = timeout - uptime(); + tv.tv_usec = 0; + + if (listen_mode != LISTEN_NONE && fd < 0) { + if (listen_mode == LISTEN_KERNEL) + fd = listen_socket(INADDR_ANY, CLIENT_PORT, client_config.interface); + else + fd = raw_socket(client_config.ifindex); + if (fd < 0) { + bb_perror_msg("FATAL: cannot listen on socket"); + return 0; + } + } + max_fd = udhcp_sp_fd_set(&rfds, fd); + + if (tv.tv_sec > 0) { + DEBUG("Waiting on select..."); + retval = select(max_fd + 1, &rfds, NULL, NULL, &tv); + } else retval = 0; /* If we already timed out, fall through */ + + now = uptime(); + if (retval == 0) { + /* timeout dropped to zero */ + switch (state) { + case INIT_SELECTING: + if (packet_num < client_config.retries) { + if (packet_num == 0) + xid = random_xid(); + + /* send discover packet */ + send_discover(xid, requested_ip); /* broadcast */ + + timeout = now + client_config.timeout; + packet_num++; + } else { + udhcp_run_script(NULL, "leasefail"); + if (client_config.background_if_no_lease) { + bb_info_msg("No lease, forking to background"); + client_background(); + } else if (client_config.abort_if_no_lease) { + bb_info_msg("No lease, failing"); + return 1; + } + /* wait to try again */ + packet_num = 0; + timeout = now + 60; + } + break; + case RENEW_REQUESTED: + case REQUESTING: + if (packet_num < client_config.retries) { + /* send request packet */ + if (state == RENEW_REQUESTED) + send_renew(xid, server_addr, requested_ip); /* unicast */ + else send_selecting(xid, server_addr, requested_ip); /* broadcast */ + + timeout = now + ((packet_num == 2) ? 10 : 2); + packet_num++; + } else { + /* timed out, go back to init state */ + if (state == RENEW_REQUESTED) udhcp_run_script(NULL, "deconfig"); + state = INIT_SELECTING; + timeout = now; + packet_num = 0; + change_mode(LISTEN_RAW); + } + break; + case BOUND: + /* Lease is starting to run out, time to enter renewing state */ + state = RENEWING; + change_mode(LISTEN_KERNEL); + DEBUG("Entering renew state"); + /* fall right through */ + case RENEWING: + /* Either set a new T1, or enter REBINDING state */ + if ((t2 - t1) <= (lease / 14400 + 1)) { + /* timed out, enter rebinding state */ + state = REBINDING; + timeout = now + (t2 - t1); + DEBUG("Entering rebinding state"); + } else { + /* send a request packet */ + send_renew(xid, server_addr, requested_ip); /* unicast */ + + t1 = (t2 - t1) / 2 + t1; + timeout = t1 + start; + } + break; + case REBINDING: + /* Either set a new T2, or enter INIT state */ + if ((lease - t2) <= (lease / 14400 + 1)) { + /* timed out, enter init state */ + state = INIT_SELECTING; + bb_info_msg("Lease lost, entering init state"); + udhcp_run_script(NULL, "deconfig"); + timeout = now; + packet_num = 0; + change_mode(LISTEN_RAW); + } else { + /* send a request packet */ + send_renew(xid, 0, requested_ip); /* broadcast */ + + t2 = (lease - t2) / 2 + t2; + timeout = t2 + start; + } + break; + case RELEASED: + /* yah, I know, *you* say it would never happen */ + timeout = 0x7fffffff; + break; + } + } else if (retval > 0 && listen_mode != LISTEN_NONE && FD_ISSET(fd, &rfds)) { + /* a packet is ready, read it */ + + if (listen_mode == LISTEN_KERNEL) + len = udhcp_get_packet(&packet, fd); + else len = get_raw_packet(&packet, fd); + + if (len == -1 && errno != EINTR) { + DEBUG("error on read, %s, reopening socket", strerror(errno)); + change_mode(listen_mode); /* just close and reopen */ + } + if (len < 0) continue; + + if (packet.xid != xid) { + DEBUG("Ignoring XID %lx (our xid is %lx)", + (unsigned long) packet.xid, xid); + continue; + } + + /* Ignore packets that aren't for us */ + if (memcmp(packet.chaddr, client_config.arp, 6)) { + DEBUG("Packet does not have our chaddr - ignoring"); + continue; + } + + if ((message = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { + bb_error_msg("cannot get option from packet - ignoring"); + continue; + } + + switch (state) { + case INIT_SELECTING: + /* Must be a DHCPOFFER to one of our xid's */ + if (*message == DHCPOFFER) { + temp = get_option(&packet, DHCP_SERVER_ID); + if (temp) { + server_addr = *(uint32_t*)temp; + xid = packet.xid; + requested_ip = packet.yiaddr; + + /* enter requesting state */ + state = REQUESTING; + timeout = now; + packet_num = 0; + } else { + bb_error_msg("no server ID in message"); + } + } + break; + case RENEW_REQUESTED: + case REQUESTING: + case RENEWING: + case REBINDING: + if (*message == DHCPACK) { + temp = get_option(&packet, DHCP_LEASE_TIME); + if (!temp) { + bb_error_msg("no lease time with ACK, using 1 hour lease"); + lease = 60 * 60; + } else { + lease = ntohl(*(uint32_t*)temp); + } + + /* enter bound state */ + t1 = lease / 2; + + /* little fixed point for n * .875 */ + t2 = (lease * 0x7) >> 3; + temp_addr.s_addr = packet.yiaddr; + bb_info_msg("Lease of %s obtained, lease time %ld", + inet_ntoa(temp_addr), lease); + start = now; + timeout = t1 + start; + requested_ip = packet.yiaddr; + udhcp_run_script(&packet, + ((state == RENEWING || state == REBINDING) ? "renew" : "bound")); + + state = BOUND; + change_mode(LISTEN_NONE); + if (client_config.quit_after_lease) { + if (client_config.release_on_quit) + perform_release(); + return 0; + } + if (!client_config.foreground) + client_background(); + + } else if (*message == DHCPNAK) { + /* return to init state */ + bb_info_msg("Received DHCP NAK"); + udhcp_run_script(&packet, "nak"); + if (state != REQUESTING) + udhcp_run_script(NULL, "deconfig"); + state = INIT_SELECTING; + timeout = now; + requested_ip = 0; + packet_num = 0; + change_mode(LISTEN_RAW); + sleep(3); /* avoid excessive network traffic */ + } + break; + /* case BOUND, RELEASED: - ignore all packets */ + } + } else if (retval > 0 && (sig = udhcp_sp_read(&rfds))) { + switch (sig) { + case SIGUSR1: + perform_renew(); + break; + case SIGUSR2: + perform_release(); + break; + case SIGTERM: + bb_info_msg("Received SIGTERM"); + if (client_config.release_on_quit) + perform_release(); + return 0; + } + } else if (retval == -1 && errno == EINTR) { + /* a signal was caught */ + } else { + /* An error occured */ + bb_perror_msg("select"); + } + + } + return 0; +} diff --git a/networking/udhcp/dhcpc.h b/networking/udhcp/dhcpc.h new file mode 100644 index 000000000..fd17917d0 --- /dev/null +++ b/networking/udhcp/dhcpc.h @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpc.h */ +#ifndef _DHCPC_H +#define _DHCPC_H + +#define INIT_SELECTING 0 +#define REQUESTING 1 +#define BOUND 2 +#define RENEWING 3 +#define REBINDING 4 +#define INIT_REBOOT 5 +#define RENEW_REQUESTED 6 +#define RELEASED 7 + +struct client_config_t { + /* TODO: combine flag fields into single "unsigned opt" */ + /* (can be set directly to the result of getopt32) */ + char foreground; /* Do not fork */ + char quit_after_lease; /* Quit after obtaining lease */ + char release_on_quit; /* perform release on quit */ + char abort_if_no_lease; /* Abort if no lease */ + char background_if_no_lease; /* Fork to background if no lease */ + char *interface; /* The name of the interface to use */ + char *pidfile; /* Optionally store the process ID */ + char *script; /* User script to run at dhcp events */ + uint8_t *clientid; /* Optional client id to use */ + uint8_t *vendorclass; /* Optional vendor class-id to use */ + uint8_t *hostname; /* Optional hostname to use */ + uint8_t *fqdn; /* Optional fully qualified domain name to use */ + int ifindex; /* Index number of the interface to use */ + int retries; /* Max number of request packets */ + int timeout; /* Number of seconds to try to get a lease */ + uint8_t arp[6]; /* Our arp address */ +}; + +extern struct client_config_t client_config; + + +/*** clientpacket.h ***/ + +unsigned long random_xid(void); +int send_discover(unsigned long xid, unsigned long requested); +int send_selecting(unsigned long xid, unsigned long server, unsigned long requested); +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr); +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr); +int send_release(unsigned long server, unsigned long ciaddr); +int get_raw_packet(struct dhcpMessage *payload, int fd); + + +#endif diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c new file mode 100644 index 000000000..74380367f --- /dev/null +++ b/networking/udhcp/dhcpd.c @@ -0,0 +1,226 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpd.c + * + * udhcp Server + * Copyright (C) 1999 Matthew Ramsay + * Chris Trew + * + * Rewrite by Russ Dill July 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* globals */ +struct dhcpOfferedAddr *leases; +struct server_config_t server_config; + + +int udhcpd_main(int argc, char *argv[]) +{ + fd_set rfds; + struct timeval tv; + int server_socket = -1, bytes, retval, max_sock; + struct dhcpMessage packet; + uint8_t *state, *server_id, *requested; + uint32_t server_id_align, requested_align, static_lease_ip; + unsigned long timeout_end, num_ips; + struct option_set *option; + struct dhcpOfferedAddr *lease, static_lease; + + read_config(argc < 2 ? DHCPD_CONF_FILE : argv[1]); + + /* Start the log, sanitize fd's, and write a pid file */ + udhcp_start_log_and_pid(server_config.pidfile); + + if ((option = find_option(server_config.options, DHCP_LEASE_TIME))) { + memcpy(&server_config.lease, option->data + 2, 4); + server_config.lease = ntohl(server_config.lease); + } + else server_config.lease = LEASE_TIME; + + /* Sanity check */ + num_ips = ntohl(server_config.end) - ntohl(server_config.start) + 1; + if (server_config.max_leases > num_ips) { + bb_error_msg("max_leases value (%lu) not sane, " + "setting to %lu instead", + server_config.max_leases, num_ips); + server_config.max_leases = num_ips; + } + + leases = xzalloc(server_config.max_leases * sizeof(struct dhcpOfferedAddr)); + read_leases(server_config.lease_file); + + if (read_interface(server_config.interface, &server_config.ifindex, + &server_config.server, server_config.arp) < 0) + return 1; + + if (!ENABLE_FEATURE_UDHCP_DEBUG) + udhcp_background(server_config.pidfile); /* hold lock during fork. */ + + /* Setup the signal pipe */ + udhcp_sp_setup(); + + timeout_end = time(0) + server_config.auto_time; + while (1) { /* loop until universe collapses */ + + if (server_socket < 0) { + server_socket = listen_socket(INADDR_ANY, SERVER_PORT, server_config.interface); + if (server_socket < 0) { + bb_perror_msg("FATAL: cannot create server socket"); + return 2; + } + } + + max_sock = udhcp_sp_fd_set(&rfds, server_socket); + if (server_config.auto_time) { + tv.tv_sec = timeout_end - time(0); + tv.tv_usec = 0; + } + if (!server_config.auto_time || tv.tv_sec > 0) { + retval = select(max_sock + 1, &rfds, NULL, NULL, + server_config.auto_time ? &tv : NULL); + } else retval = 0; /* If we already timed out, fall through */ + + if (retval == 0) { + write_leases(); + timeout_end = time(0) + server_config.auto_time; + continue; + } else if (retval < 0 && errno != EINTR) { + DEBUG("error on select"); + continue; + } + + switch (udhcp_sp_read(&rfds)) { + case SIGUSR1: + bb_info_msg("Received a SIGUSR1"); + write_leases(); + /* why not just reset the timeout, eh */ + timeout_end = time(0) + server_config.auto_time; + continue; + case SIGTERM: + bb_info_msg("Received a SIGTERM"); + return 0; + case 0: break; /* no signal */ + default: continue; /* signal or error (probably EINTR) */ + } + + if ((bytes = udhcp_get_packet(&packet, server_socket)) < 0) { /* this waits for a packet - idle */ + if (bytes == -1 && errno != EINTR) { + DEBUG("error on read, %s, reopening socket", strerror(errno)); + close(server_socket); + server_socket = -1; + } + continue; + } + + if ((state = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { + bb_error_msg("cannot get option from packet, ignoring"); + continue; + } + + /* Look for a static lease */ + static_lease_ip = getIpByMac(server_config.static_leases, &packet.chaddr); + + if (static_lease_ip) { + bb_info_msg("Found static lease: %x", static_lease_ip); + + memcpy(&static_lease.chaddr, &packet.chaddr, 16); + static_lease.yiaddr = static_lease_ip; + static_lease.expires = 0; + + lease = &static_lease; + + } else { + lease = find_lease_by_chaddr(packet.chaddr); + } + + switch (state[0]) { + case DHCPDISCOVER: + DEBUG("Received DISCOVER"); + + if (sendOffer(&packet) < 0) { + bb_error_msg("send OFFER failed"); + } + break; + case DHCPREQUEST: + DEBUG("received REQUEST"); + + requested = get_option(&packet, DHCP_REQUESTED_IP); + server_id = get_option(&packet, DHCP_SERVER_ID); + + if (requested) memcpy(&requested_align, requested, 4); + if (server_id) memcpy(&server_id_align, server_id, 4); + + if (lease) { + if (server_id) { + /* SELECTING State */ + DEBUG("server_id = %08x", ntohl(server_id_align)); + if (server_id_align == server_config.server && requested && + requested_align == lease->yiaddr) { + sendACK(&packet, lease->yiaddr); + } + } else { + if (requested) { + /* INIT-REBOOT State */ + if (lease->yiaddr == requested_align) + sendACK(&packet, lease->yiaddr); + else sendNAK(&packet); + } else { + /* RENEWING or REBINDING State */ + if (lease->yiaddr == packet.ciaddr) + sendACK(&packet, lease->yiaddr); + else { + /* don't know what to do!!!! */ + sendNAK(&packet); + } + } + } + + /* what to do if we have no record of the client */ + } else if (server_id) { + /* SELECTING State */ + + } else if (requested) { + /* INIT-REBOOT State */ + if ((lease = find_lease_by_yiaddr(requested_align))) { + if (lease_expired(lease)) { + /* probably best if we drop this lease */ + memset(lease->chaddr, 0, 16); + /* make some contention for this address */ + } else sendNAK(&packet); + } else if (requested_align < server_config.start || + requested_align > server_config.end) { + sendNAK(&packet); + } /* else remain silent */ + + } else { + /* RENEWING or REBINDING State */ + } + break; + case DHCPDECLINE: + DEBUG("Received DECLINE"); + if (lease) { + memset(lease->chaddr, 0, 16); + lease->expires = time(0) + server_config.decline_time; + } + break; + case DHCPRELEASE: + DEBUG("Received RELEASE"); + if (lease) lease->expires = time(0); + break; + case DHCPINFORM: + DEBUG("Received INFORM"); + send_inform(&packet); + break; + default: + bb_info_msg("Unsupported DHCP message (%02x) - ignoring", state[0]); + } + } + + return 0; +} diff --git a/networking/udhcp/dhcpd.h b/networking/udhcp/dhcpd.h new file mode 100644 index 000000000..40959e4ae --- /dev/null +++ b/networking/udhcp/dhcpd.h @@ -0,0 +1,190 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpd.h */ +#ifndef _DHCPD_H +#define _DHCPD_H + +/************************************/ +/* Defaults _you_ may want to tweak */ +/************************************/ + +/* the period of time the client is allowed to use that address */ +#define LEASE_TIME (60*60*24*10) /* 10 days of seconds */ +#define LEASES_FILE "/var/lib/misc/udhcpd.leases" + +/* where to find the DHCP server configuration file */ +#define DHCPD_CONF_FILE "/etc/udhcpd.conf" + +/*****************************************************************/ +/* Do not modify below here unless you know what you are doing!! */ +/*****************************************************************/ + +/* DHCP protocol -- see RFC 2131 */ +#define SERVER_PORT 67 +#define CLIENT_PORT 68 + +#define DHCP_MAGIC 0x63825363 + +/* DHCP option codes (partial list) */ +#define DHCP_PADDING 0x00 +#define DHCP_SUBNET 0x01 +#define DHCP_TIME_OFFSET 0x02 +#define DHCP_ROUTER 0x03 +#define DHCP_TIME_SERVER 0x04 +#define DHCP_NAME_SERVER 0x05 +#define DHCP_DNS_SERVER 0x06 +#define DHCP_LOG_SERVER 0x07 +#define DHCP_COOKIE_SERVER 0x08 +#define DHCP_LPR_SERVER 0x09 +#define DHCP_HOST_NAME 0x0c +#define DHCP_BOOT_SIZE 0x0d +#define DHCP_DOMAIN_NAME 0x0f +#define DHCP_SWAP_SERVER 0x10 +#define DHCP_ROOT_PATH 0x11 +#define DHCP_IP_TTL 0x17 +#define DHCP_MTU 0x1a +#define DHCP_BROADCAST 0x1c +#define DHCP_NTP_SERVER 0x2a +#define DHCP_WINS_SERVER 0x2c +#define DHCP_REQUESTED_IP 0x32 +#define DHCP_LEASE_TIME 0x33 +#define DHCP_OPTION_OVER 0x34 +#define DHCP_MESSAGE_TYPE 0x35 +#define DHCP_SERVER_ID 0x36 +#define DHCP_PARAM_REQ 0x37 +#define DHCP_MESSAGE 0x38 +#define DHCP_MAX_SIZE 0x39 +#define DHCP_T1 0x3a +#define DHCP_T2 0x3b +#define DHCP_VENDOR 0x3c +#define DHCP_CLIENT_ID 0x3d +#define DHCP_FQDN 0x51 + +#define DHCP_END 0xFF + + +#define BOOTREQUEST 1 +#define BOOTREPLY 2 + +#define ETH_10MB 1 +#define ETH_10MB_LEN 6 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 + +#define BROADCAST_FLAG 0x8000 + +#define OPTION_FIELD 0 +#define FILE_FIELD 1 +#define SNAME_FIELD 2 + +/* miscellaneous defines */ +#define MAC_BCAST_ADDR (uint8_t *) "\xff\xff\xff\xff\xff\xff" +#define OPT_CODE 0 +#define OPT_LEN 1 +#define OPT_DATA 2 + +struct option_set { + uint8_t *data; + struct option_set *next; +}; + +struct static_lease { + uint8_t *mac; + uint32_t *ip; + struct static_lease *next; +}; + +struct server_config_t { + uint32_t server; /* Our IP, in network order */ + uint32_t start; /* Start address of leases, network order */ + uint32_t end; /* End of leases, network order */ + struct option_set *options; /* List of DHCP options loaded from the config file */ + char *interface; /* The name of the interface to use */ + int ifindex; /* Index number of the interface to use */ + uint8_t arp[6]; /* Our arp address */ + unsigned long lease; /* lease time in seconds (host order) */ + unsigned long max_leases; /* maximum number of leases (including reserved address) */ + char remaining; /* should the lease file be interpreted as lease time remaining, or + * as the time the lease expires */ + unsigned long auto_time; /* how long should udhcpd wait before writing a config file. + * if this is zero, it will only write one on SIGUSR1 */ + unsigned long decline_time; /* how long an address is reserved if a client returns a + * decline message */ + unsigned long conflict_time; /* how long an arp conflict offender is leased for */ + unsigned long offer_time; /* how long an offered address is reserved */ + unsigned long min_lease; /* minimum lease a client can request*/ + char *lease_file; + char *pidfile; + char *notify_file; /* What to run whenever leases are written */ + uint32_t siaddr; /* next server bootp option */ + char *sname; /* bootp server name */ + char *boot_file; /* bootp boot file option */ + struct static_lease *static_leases; /* List of ip/mac pairs to assign static leases */ +}; + +extern struct server_config_t server_config; +extern struct dhcpOfferedAddr *leases; + + +/*** leases.h ***/ + +struct dhcpOfferedAddr { + uint8_t chaddr[16]; + uint32_t yiaddr; /* network order */ + uint32_t expires; /* host order */ +}; + +extern uint8_t blank_chaddr[]; + +void clear_lease(uint8_t *chaddr, uint32_t yiaddr); +struct dhcpOfferedAddr *add_lease(uint8_t *chaddr, uint32_t yiaddr, unsigned long lease); +int lease_expired(struct dhcpOfferedAddr *lease); +struct dhcpOfferedAddr *oldest_expired_lease(void); +struct dhcpOfferedAddr *find_lease_by_chaddr(uint8_t *chaddr); +struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr); +uint32_t find_address(int check_expired); + + +/*** static_leases.h ***/ + +/* Config file will pass static lease info to this function which will add it + * to a data structure that can be searched later */ +int addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t *ip); +/* Check to see if a mac has an associated static lease */ +uint32_t getIpByMac(struct static_lease *lease_struct, void *arg); +/* Check to see if an ip is reserved as a static ip */ +uint32_t reservedIp(struct static_lease *lease_struct, uint32_t ip); +/* Print out static leases just to check what's going on (debug code) */ +void printStaticLeases(struct static_lease **lease_struct); + + +/*** serverpacket.h ***/ + +int sendOffer(struct dhcpMessage *oldpacket); +int sendNAK(struct dhcpMessage *oldpacket); +int sendACK(struct dhcpMessage *oldpacket, uint32_t yiaddr); +int send_inform(struct dhcpMessage *oldpacket); + + +/*** files.h ***/ + +struct config_keyword { + const char *keyword; + int (* const handler)(const char *line, void *var); + void *var; + const char *def; +}; + +int read_config(const char *file); +void write_leases(void); +void read_leases(const char *file); +struct option_set *find_option(struct option_set *opt_list, char code); + + +#endif diff --git a/networking/udhcp/dhcprelay.c b/networking/udhcp/dhcprelay.c new file mode 100644 index 000000000..e3a816886 --- /dev/null +++ b/networking/udhcp/dhcprelay.c @@ -0,0 +1,340 @@ +/* vi: set sw=4 ts=4: */ +/* Port to Busybox Copyright (C) 2006 Jesse Dutton + * + * Licensed under GPL v2, see file LICENSE in this tarball for details. + * + * DHCP Relay for 'DHCPv4 Configuration of IPSec Tunnel Mode' support + * Copyright (C) 2002 Mario Strasser , + * Zuercher Hochschule Winterthur, + * Netbeat AG + * Upstream has GPL v2 or later + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + +/* constants */ +#define SELECT_TIMEOUT 5 /* select timeout in sec. */ +#define MAX_LIFETIME 2*60 /* lifetime of an xid entry in sec. */ +#define MAX_INTERFACES 9 + + +/* This list holds information about clients. The xid_* functions manipulate this list. */ +static struct xid_item { + u_int32_t xid; + struct sockaddr_in ip; + int client; + time_t timestamp; + struct xid_item *next; +} dhcprelay_xid_list = {0, {0}, 0, 0, NULL}; + + +static struct xid_item * xid_add(u_int32_t xid, struct sockaddr_in *ip, int client) +{ + struct xid_item *item; + + /* create new xid entry */ + item = xmalloc(sizeof(struct xid_item)); + + /* add xid entry */ + item->ip = *ip; + item->xid = xid; + item->client = client; + item->timestamp = time(NULL); + item->next = dhcprelay_xid_list.next; + dhcprelay_xid_list.next = item; + + return item; +} + + +static void xid_expire(void) +{ + struct xid_item *item = dhcprelay_xid_list.next; + struct xid_item *last = &dhcprelay_xid_list; + time_t current_time = time(NULL); + + while (item != NULL) { + if ((current_time-item->timestamp) > MAX_LIFETIME) { + last->next = item->next; + free(item); + item = last->next; + } else { + last = item; + item = item->next; + } + } +} + +static struct xid_item * xid_find(u_int32_t xid) +{ + struct xid_item *item = dhcprelay_xid_list.next; + while (item != NULL) { + if (item->xid == xid) { + return item; + } + item = item->next; + } + return NULL; +} + +static void xid_del(u_int32_t xid) +{ + struct xid_item *item = dhcprelay_xid_list.next; + struct xid_item *last = &dhcprelay_xid_list; + while (item != NULL) { + if (item->xid == xid) { + last->next = item->next; + free(item); + item = last->next; + } else { + last = item; + item = item->next; + } + } +} + + +/** + * get_dhcp_packet_type - gets the message type of a dhcp packet + * p - pointer to the dhcp packet + * returns the message type on success, -1 otherwise + */ +static int get_dhcp_packet_type(struct dhcpMessage *p) +{ + u_char *op; + + /* it must be either a BOOTREQUEST or a BOOTREPLY */ + if (p->op != BOOTREQUEST && p->op != BOOTREPLY) + return -1; + /* get message type option */ + op = get_option(p, DHCP_MESSAGE_TYPE); + if (op != NULL) + return op[0]; + return -1; +} + +/** + * signal_handler - handles signals ;-) + * sig - sent signal + */ +static int dhcprelay_stopflag; +static void dhcprelay_signal_handler(int sig) +{ + dhcprelay_stopflag = 1; +} + +/** + * get_client_devices - parses the devices list + * dev_list - comma separated list of devices + * returns array + */ +static char ** get_client_devices(char *dev_list, int *client_number) +{ + char *s, *list, **client_dev; + int i, cn; + + /* copy list */ + list = xstrdup(dev_list); + if (list == NULL) return NULL; + + /* get number of items */ + for (s = dev_list, cn = 1; *s; s++) + if (*s == ',') + cn++; + + client_dev = xzalloc(cn * sizeof(*client_dev)); + + /* parse list */ + s = strtok(list, ","); + i = 0; + while (s != NULL) { + client_dev[i++] = xstrdup(s); + s = strtok(NULL, ","); + } + + /* free copy and exit */ + free(list); + *client_number = cn; + return client_dev; +} + + +/* Creates listen sockets (in fds) and returns the number allocated. */ +static int init_sockets(char **client, int num_clients, + char *server, int *fds, int *max_socket) +{ + int i; + + /* talk to real server on bootps */ + fds[0] = listen_socket(htonl(INADDR_ANY), 67, server); + if (fds[0] < 0) return -1; + *max_socket = fds[0]; + + /* array starts at 1 since server is 0 */ + num_clients++; + + for (i=1; i < num_clients; i++) { + /* listen for clients on bootps */ + fds[i] = listen_socket(htonl(INADDR_ANY), 67, client[i-1]); + if (fds[i] < 0) return -1; + if (fds[i] > *max_socket) *max_socket = fds[i]; + } + + return i; +} + + +/** + * pass_on() - forwards dhcp packets from client to server + * p - packet to send + * client - number of the client + */ +static void pass_on(struct dhcpMessage *p, int packet_len, int client, int *fds, + struct sockaddr_in *client_addr, struct sockaddr_in *server_addr) +{ + int res, type; + struct xid_item *item; + + /* check packet_type */ + type = get_dhcp_packet_type(p); + if (type != DHCPDISCOVER && type != DHCPREQUEST + && type != DHCPDECLINE && type != DHCPRELEASE + && type != DHCPINFORM + ) { + return; + } + + /* create new xid entry */ + item = xid_add(p->xid, client_addr, client); + + /* forward request to LAN (server) */ + res = sendto(fds[0], p, packet_len, 0, (struct sockaddr*)server_addr, + sizeof(struct sockaddr_in)); + if (res != packet_len) { + bb_perror_msg("pass_on"); + return; + } +} + +/** + * pass_back() - forwards dhcp packets from server to client + * p - packet to send + */ +static void pass_back(struct dhcpMessage *p, int packet_len, int *fds) +{ + int res, type; + struct xid_item *item; + + /* check xid */ + item = xid_find(p->xid); + if (!item) { + return; + } + + /* check packet type */ + type = get_dhcp_packet_type(p); + if (type != DHCPOFFER && type != DHCPACK && type != DHCPNAK) { + return; + } + + if (item->ip.sin_addr.s_addr == htonl(INADDR_ANY)) + item->ip.sin_addr.s_addr = htonl(INADDR_BROADCAST); + if (item->client > MAX_INTERFACES) + return; + res = sendto(fds[item->client], p, packet_len, 0, (struct sockaddr*)(&item->ip), + sizeof(item->ip)); + if (res != packet_len) { + bb_perror_msg("pass_back"); + return; + } + + /* remove xid entry */ + xid_del(p->xid); +} + +static void dhcprelay_loop(int *fds, int num_sockets, int max_socket, char **clients, + struct sockaddr_in *server_addr, uint32_t gw_ip) +{ + struct dhcpMessage dhcp_msg; + fd_set rfds; + size_t packlen, addr_size; + struct sockaddr_in client_addr; + struct timeval tv; + int i; + + while (!dhcprelay_stopflag) { + FD_ZERO(&rfds); + for (i = 0; i < num_sockets; i++) + FD_SET(fds[i], &rfds); + tv.tv_sec = SELECT_TIMEOUT; + tv.tv_usec = 0; + if (select(max_socket + 1, &rfds, NULL, NULL, &tv) > 0) { + /* server */ + if (FD_ISSET(fds[0], &rfds)) { + packlen = udhcp_get_packet(&dhcp_msg, fds[0]); + if (packlen > 0) { + pass_back(&dhcp_msg, packlen, fds); + } + } + for (i = 1; i < num_sockets; i++) { + /* clients */ + if (!FD_ISSET(fds[i], &rfds)) + continue; + addr_size = sizeof(struct sockaddr_in); + packlen = recvfrom(fds[i], &dhcp_msg, sizeof(dhcp_msg), 0, + (struct sockaddr *)(&client_addr), &addr_size); + if (packlen <= 0) + continue; + if (read_interface(clients[i-1], NULL, &dhcp_msg.giaddr, NULL) < 0) + dhcp_msg.giaddr = gw_ip; + pass_on(&dhcp_msg, packlen, i, fds, &client_addr, server_addr); + } + } + xid_expire(); + } +} + +int dhcprelay_main(int argc, char **argv) +{ + int i, num_sockets, max_socket, fds[MAX_INTERFACES]; + uint32_t gw_ip; + char **clients; + struct sockaddr_in server_addr; + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(67); + if (argc == 4) { + if (!inet_aton(argv[3], &server_addr.sin_addr)) + bb_perror_msg_and_die("didn't grok server"); + } else if (argc == 3) { + server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); + } else { + bb_show_usage(); + } + clients = get_client_devices(argv[1], &num_sockets); + if (!clients) return 0; + + signal(SIGTERM, dhcprelay_signal_handler); + signal(SIGQUIT, dhcprelay_signal_handler); + signal(SIGINT, dhcprelay_signal_handler); + + num_sockets = init_sockets(clients, num_sockets, argv[2], fds, &max_socket); + if (num_sockets == -1) + bb_perror_msg_and_die("init_sockets() failed"); + + if (read_interface(argv[2], NULL, &gw_ip, NULL) == -1) + return 1; + + dhcprelay_loop(fds, num_sockets, max_socket, clients, &server_addr, gw_ip); + + if (ENABLE_FEATURE_CLEAN_UP) { + for (i = 0; i < num_sockets; i++) { + close(fds[i]); + free(clients[i]); + } + } + + return 0; +} diff --git a/networking/udhcp/dumpleases.c b/networking/udhcp/dumpleases.c new file mode 100644 index 000000000..a0e81bb13 --- /dev/null +++ b/networking/udhcp/dumpleases.c @@ -0,0 +1,74 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ +#include + +#include "common.h" +#include "dhcpd.h" + + +#define REMAINING 0 +#define ABSOLUTE 1 + +int dumpleases_main(int argc, char *argv[]) +{ + int fp; + int i, c, mode = REMAINING; + unsigned long expires; + const char *file = LEASES_FILE; + struct dhcpOfferedAddr lease; + struct in_addr addr; + + static const struct option options[] = { + {"absolute", 0, 0, 'a'}, + {"remaining", 0, 0, 'r'}, + {"file", 1, 0, 'f'}, + {0, 0, 0, 0} + }; + + while (1) { + int option_index = 0; + c = getopt_long(argc, argv, "arf:", options, &option_index); + if (c == -1) break; + + switch (c) { + case 'a': mode = ABSOLUTE; break; + case 'r': mode = REMAINING; break; + case 'f': + file = optarg; + break; + default: + bb_show_usage(); + } + } + + fp = xopen(file, O_RDONLY); + + printf("Mac Address IP-Address Expires %s\n", mode == REMAINING ? "in" : "at"); + /* "00:00:00:00:00:00 255.255.255.255 Wed Jun 30 21:49:08 1993" */ + while (full_read(fp, &lease, sizeof(lease)) == sizeof(lease)) { + printf(":%02x"+1, lease.chaddr[0]); + for (i = 1; i < 6; i++) { + printf(":%02x", lease.chaddr[i]); + } + addr.s_addr = lease.yiaddr; + printf(" %-15s ", inet_ntoa(addr)); + expires = ntohl(lease.expires); + if (mode == REMAINING) { + if (!expires) + printf("expired\n"); + else { + unsigned d, h, m; + d = expires / (24*60*60); expires %= (24*60*60); + h = expires / (60*60); expires %= (60*60); + m = expires / 60; expires %= 60; + if (d) printf("%u days ", d); + printf("%02u:%02u:%02u\n", h, m, (unsigned)expires); + } + } else fputs(ctime(&expires), stdout); + } + /* close(fp); */ + + return 0; +} diff --git a/networking/udhcp/files.c b/networking/udhcp/files.c new file mode 100644 index 000000000..5e399e1f8 --- /dev/null +++ b/networking/udhcp/files.c @@ -0,0 +1,395 @@ +/* vi: set sw=4 ts=4: */ +/* + * files.c -- DHCP server file manipulation * + * Rewrite by Russ Dill July 2001 + */ + +#include + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* + * Domain names may have 254 chars, and string options can be 254 + * chars long. However, 80 bytes will be enough for most, and won't + * hog up memory. If you have a special application, change it + */ +#define READ_CONFIG_BUF_SIZE 80 + +/* on these functions, make sure you datatype matches */ +static int read_ip(const char *line, void *arg) +{ + struct in_addr *addr = arg; + struct hostent *host; + int retval = 1; + + if (!inet_aton(line, addr)) { + host = gethostbyname(line); + if (host) + addr->s_addr = *((unsigned long *) host->h_addr_list[0]); + else retval = 0; + } + return retval; +} + +static int read_mac(const char *line, void *arg) +{ + uint8_t *mac_bytes = arg; + struct ether_addr *temp_ether_addr; + int retval = 1; + + temp_ether_addr = ether_aton(line); + + if (temp_ether_addr == NULL) + retval = 0; + else + memcpy(mac_bytes, temp_ether_addr, 6); + + return retval; +} + + +static int read_str(const char *line, void *arg) +{ + char **dest = arg; + + free(*dest); + *dest = strdup(line); + + return 1; +} + + +static int read_u32(const char *line, void *arg) +{ + *((uint32_t*)arg) = bb_strtou32(line, NULL, 10); + return errno == 0; +} + + +static int read_yn(const char *line, void *arg) +{ + char *dest = arg; + int retval = 1; + + if (!strcasecmp("yes", line)) + *dest = 1; + else if (!strcasecmp("no", line)) + *dest = 0; + else retval = 0; + + return retval; +} + + +/* find option 'code' in opt_list */ +struct option_set *find_option(struct option_set *opt_list, char code) +{ + while (opt_list && opt_list->data[OPT_CODE] < code) + opt_list = opt_list->next; + + if (opt_list && opt_list->data[OPT_CODE] == code) return opt_list; + else return NULL; +} + + +/* add an option to the opt_list */ +static void attach_option(struct option_set **opt_list, + const struct dhcp_option *option, char *buffer, int length) +{ + struct option_set *existing, *new, **curr; + + /* add it to an existing option */ + existing = find_option(*opt_list, option->code); + if (existing) { + DEBUG("Attaching option %s to existing member of list", option->name); + if (option->flags & OPTION_LIST) { + if (existing->data[OPT_LEN] + length <= 255) { + existing->data = realloc(existing->data, + existing->data[OPT_LEN] + length + 2); + memcpy(existing->data + existing->data[OPT_LEN] + 2, buffer, length); + existing->data[OPT_LEN] += length; + } /* else, ignore the data, we could put this in a second option in the future */ + } /* else, ignore the new data */ + } else { + DEBUG("Attaching option %s to list", option->name); + + /* make a new option */ + new = xmalloc(sizeof(struct option_set)); + new->data = xmalloc(length + 2); + new->data[OPT_CODE] = option->code; + new->data[OPT_LEN] = length; + memcpy(new->data + 2, buffer, length); + + curr = opt_list; + while (*curr && (*curr)->data[OPT_CODE] < option->code) + curr = &(*curr)->next; + + new->next = *curr; + *curr = new; + } +} + + +/* read a dhcp option and add it to opt_list */ +static int read_opt(const char *const_line, void *arg) +{ + struct option_set **opt_list = arg; + char *opt, *val, *endptr; + const struct dhcp_option *option; + int retval = 0, length; + char buffer[8]; + char *line; + uint16_t *result_u16 = (uint16_t *) buffer; + uint32_t *result_u32 = (uint32_t *) buffer; + + /* Cheat, the only const line we'll actually get is "" */ + line = (char *) const_line; + opt = strtok(line, " \t="); + if (!opt) return 0; + + option = dhcp_options; + while (1) { + if (!option->code) + return 0; + if (!strcasecmp(option->name, opt)) + break; + option++; + } + + do { + val = strtok(NULL, ", \t"); + if (!val) break; + length = option_lengths[option->flags & TYPE_MASK]; + retval = 0; + opt = buffer; /* new meaning for variable opt */ + switch (option->flags & TYPE_MASK) { + case OPTION_IP: + retval = read_ip(val, buffer); + break; + case OPTION_IP_PAIR: + retval = read_ip(val, buffer); + if (!(val = strtok(NULL, ", \t/-"))) retval = 0; + if (retval) retval = read_ip(val, buffer + 4); + break; + case OPTION_STRING: + length = strlen(val); + if (length > 0) { + if (length > 254) length = 254; + opt = val; + retval = 1; + } + break; + case OPTION_BOOLEAN: + retval = read_yn(val, buffer); + break; + case OPTION_U8: + buffer[0] = strtoul(val, &endptr, 0); + retval = (endptr[0] == '\0'); + break; + case OPTION_U16: + *result_u16 = htons(strtoul(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + case OPTION_S16: + *result_u16 = htons(strtol(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + case OPTION_U32: + *result_u32 = htonl(strtoul(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + case OPTION_S32: + *result_u32 = htonl(strtol(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + default: + break; + } + if (retval) + attach_option(opt_list, option, opt, length); + } while (retval && option->flags & OPTION_LIST); + return retval; +} + +static int read_staticlease(const char *const_line, void *arg) +{ + char *line; + char *mac_string; + char *ip_string; + uint8_t *mac_bytes; + uint32_t *ip; + + + /* Allocate memory for addresses */ + mac_bytes = xmalloc(sizeof(unsigned char) * 8); + ip = xmalloc(sizeof(uint32_t)); + + /* Read mac */ + line = (char *) const_line; + mac_string = strtok(line, " \t"); + read_mac(mac_string, mac_bytes); + + /* Read ip */ + ip_string = strtok(NULL, " \t"); + read_ip(ip_string, ip); + + addStaticLease(arg, mac_bytes, ip); + + if (ENABLE_FEATURE_UDHCP_DEBUG) printStaticLeases(arg); + + return 1; +} + + +static const struct config_keyword keywords[] = { + /* keyword handler variable address default */ + {"start", read_ip, &(server_config.start), "192.168.0.20"}, + {"end", read_ip, &(server_config.end), "192.168.0.254"}, + {"interface", read_str, &(server_config.interface), "eth0"}, + {"option", read_opt, &(server_config.options), ""}, + {"opt", read_opt, &(server_config.options), ""}, + {"max_leases", read_u32, &(server_config.max_leases), "254"}, + {"remaining", read_yn, &(server_config.remaining), "yes"}, + {"auto_time", read_u32, &(server_config.auto_time), "7200"}, + {"decline_time",read_u32, &(server_config.decline_time),"3600"}, + {"conflict_time",read_u32,&(server_config.conflict_time),"3600"}, + {"offer_time", read_u32, &(server_config.offer_time), "60"}, + {"min_lease", read_u32, &(server_config.min_lease), "60"}, + {"lease_file", read_str, &(server_config.lease_file), LEASES_FILE}, + {"pidfile", read_str, &(server_config.pidfile), "/var/run/udhcpd.pid"}, + {"notify_file", read_str, &(server_config.notify_file), ""}, + {"siaddr", read_ip, &(server_config.siaddr), "0.0.0.0"}, + {"sname", read_str, &(server_config.sname), ""}, + {"boot_file", read_str, &(server_config.boot_file), ""}, + {"static_lease",read_staticlease, &(server_config.static_leases), ""}, + /*ADDME: static lease */ + {"", NULL, NULL, ""} +}; + + +int read_config(const char *file) +{ + FILE *in; + char buffer[READ_CONFIG_BUF_SIZE], *token, *line; + int i, lm = 0; + + for (i = 0; keywords[i].keyword[0]; i++) + if (keywords[i].def[0]) + keywords[i].handler(keywords[i].def, keywords[i].var); + + in = fopen(file, "r"); + if (!in) { + bb_error_msg("cannot open config file: %s", file); + return 0; + } + + while (fgets(buffer, READ_CONFIG_BUF_SIZE, in)) { + char debug_orig[READ_CONFIG_BUF_SIZE]; + char *p; + + lm++; + p = strchr(buffer, '\n'); + if (p) *p = '\0'; + if (ENABLE_FEATURE_UDHCP_DEBUG) strcpy(debug_orig, buffer); + p = strchr(buffer, '#'); + if (p) *p = '\0'; + + if (!(token = strtok(buffer, " \t"))) continue; + if (!(line = strtok(NULL, ""))) continue; + + /* eat leading whitespace */ + line = skip_whitespace(line); + /* eat trailing whitespace */ + i = strlen(line) - 1; + while (i >= 0 && isspace(line[i])) + line[i--] = '\0'; + + for (i = 0; keywords[i].keyword[0]; i++) + if (!strcasecmp(token, keywords[i].keyword)) + if (!keywords[i].handler(line, keywords[i].var)) { + bb_error_msg("cannot parse line %d of %s", lm, file); + if (ENABLE_FEATURE_UDHCP_DEBUG) + bb_error_msg("cannot parse '%s'", debug_orig); + /* reset back to the default value */ + keywords[i].handler(keywords[i].def, keywords[i].var); + } + } + fclose(in); + return 1; +} + + +void write_leases(void) +{ + int fp; + unsigned i; + time_t curr = time(0); + unsigned long tmp_time; + + fp = open(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (fp < 0) { + bb_error_msg("cannot open %s for writing", server_config.lease_file); + return; + } + + for (i = 0; i < server_config.max_leases; i++) { + if (leases[i].yiaddr != 0) { + + /* screw with the time in the struct, for easier writing */ + tmp_time = leases[i].expires; + + if (server_config.remaining) { + if (lease_expired(&(leases[i]))) + leases[i].expires = 0; + else leases[i].expires -= curr; + } /* else stick with the time we got */ + leases[i].expires = htonl(leases[i].expires); + // FIXME: error check?? + full_write(fp, &leases[i], sizeof(leases[i])); + + /* then restore it when done */ + leases[i].expires = tmp_time; + } + } + close(fp); + + if (server_config.notify_file) { + char *cmd = xasprintf("%s %s", server_config.notify_file, server_config.lease_file); + system(cmd); + free(cmd); + } +} + + +void read_leases(const char *file) +{ + int fp; + unsigned int i = 0; + struct dhcpOfferedAddr lease; + + fp = open(file, O_RDONLY); + if (fp < 0) { + bb_error_msg("cannot open %s for reading", file); + return; + } + + while (i < server_config.max_leases + && full_read(fp, &lease, sizeof(lease)) == sizeof(lease) + ) { + /* ADDME: is it a static lease */ + if (lease.yiaddr >= server_config.start && lease.yiaddr <= server_config.end) { + lease.expires = ntohl(lease.expires); + if (!server_config.remaining) lease.expires -= time(0); + if (!(add_lease(lease.chaddr, lease.yiaddr, lease.expires))) { + bb_error_msg("too many leases while loading %s", file); + break; + } + i++; + } + } + DEBUG("Read %d leases", i); + close(fp); +} diff --git a/networking/udhcp/leases.c b/networking/udhcp/leases.c new file mode 100644 index 000000000..2f7847d74 --- /dev/null +++ b/networking/udhcp/leases.c @@ -0,0 +1,145 @@ +/* vi: set sw=4 ts=4: */ +/* + * leases.c -- tools to manage DHCP leases + * Russ Dill July 2001 + */ + +#include "common.h" +#include "dhcpd.h" + + +uint8_t blank_chaddr[] = {[0 ... 15] = 0}; + +/* clear every lease out that chaddr OR yiaddr matches and is nonzero */ +void clear_lease(uint8_t *chaddr, uint32_t yiaddr) +{ + unsigned int i, j; + + for (j = 0; j < 16 && !chaddr[j]; j++); + + for (i = 0; i < server_config.max_leases; i++) + if ((j != 16 && !memcmp(leases[i].chaddr, chaddr, 16)) || + (yiaddr && leases[i].yiaddr == yiaddr)) { + memset(&(leases[i]), 0, sizeof(struct dhcpOfferedAddr)); + } +} + + +/* add a lease into the table, clearing out any old ones */ +struct dhcpOfferedAddr *add_lease(uint8_t *chaddr, uint32_t yiaddr, unsigned long lease) +{ + struct dhcpOfferedAddr *oldest; + + /* clean out any old ones */ + clear_lease(chaddr, yiaddr); + + oldest = oldest_expired_lease(); + + if (oldest) { + memcpy(oldest->chaddr, chaddr, 16); + oldest->yiaddr = yiaddr; + oldest->expires = time(0) + lease; + } + + return oldest; +} + + +/* true if a lease has expired */ +int lease_expired(struct dhcpOfferedAddr *lease) +{ + return (lease->expires < (unsigned long) time(0)); +} + + +/* Find the oldest expired lease, NULL if there are no expired leases */ +struct dhcpOfferedAddr *oldest_expired_lease(void) +{ + struct dhcpOfferedAddr *oldest = NULL; + unsigned long oldest_lease = time(0); + unsigned int i; + + + for (i = 0; i < server_config.max_leases; i++) + if (oldest_lease > leases[i].expires) { + oldest_lease = leases[i].expires; + oldest = &(leases[i]); + } + return oldest; + +} + + +/* Find the first lease that matches chaddr, NULL if no match */ +struct dhcpOfferedAddr *find_lease_by_chaddr(uint8_t *chaddr) +{ + unsigned int i; + + for (i = 0; i < server_config.max_leases; i++) + if (!memcmp(leases[i].chaddr, chaddr, 16)) return &(leases[i]); + + return NULL; +} + + +/* Find the first lease that matches yiaddr, NULL is no match */ +struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr) +{ + unsigned int i; + + for (i = 0; i < server_config.max_leases; i++) + if (leases[i].yiaddr == yiaddr) return &(leases[i]); + + return NULL; +} + + +/* check is an IP is taken, if it is, add it to the lease table */ +static int check_ip(uint32_t addr) +{ + struct in_addr temp; + + if (arpping(addr, server_config.server, server_config.arp, server_config.interface) == 0) { + temp.s_addr = addr; + bb_info_msg("%s belongs to someone, reserving it for %ld seconds", + inet_ntoa(temp), server_config.conflict_time); + add_lease(blank_chaddr, addr, server_config.conflict_time); + return 1; + } else return 0; +} + + +/* find an assignable address, it check_expired is true, we check all the expired leases as well. + * Maybe this should try expired leases by age... */ +uint32_t find_address(int check_expired) +{ + uint32_t addr, ret; + struct dhcpOfferedAddr *lease = NULL; + + addr = ntohl(server_config.start); /* addr is in host order here */ + for (;addr <= ntohl(server_config.end); addr++) { + + /* ie, 192.168.55.0 */ + if (!(addr & 0xFF)) continue; + + /* ie, 192.168.55.255 */ + if ((addr & 0xFF) == 0xFF) continue; + + /* Only do if it isn't an assigned as a static lease */ + if (!reservedIp(server_config.static_leases, htonl(addr))) { + + /* lease is not taken */ + ret = htonl(addr); + lease = find_lease_by_yiaddr(ret); + + /* no lease or it expired and we are checking for expired leases */ + if ( (!lease || (check_expired && lease_expired(lease))) + && /* and it isn't on the network */ !check_ip(ret) + ) { + return ret; + break; + } + } + } + return 0; +} diff --git a/networking/udhcp/options.c b/networking/udhcp/options.c new file mode 100644 index 000000000..4a46da579 --- /dev/null +++ b/networking/udhcp/options.c @@ -0,0 +1,174 @@ +/* vi: set sw=4 ts=4: */ +/* + * options.c -- DHCP server option packet tools + * Rewrite by Russ Dill July 2001 + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* supported options are easily added here */ +const struct dhcp_option dhcp_options[] = { + /* name[10] flags code */ + {"subnet", OPTION_IP | OPTION_REQ, 0x01}, + {"timezone", OPTION_S32, 0x02}, + {"router", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03}, + {"timesvr", OPTION_IP | OPTION_LIST, 0x04}, + {"namesvr", OPTION_IP | OPTION_LIST, 0x05}, + {"dns", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06}, + {"logsvr", OPTION_IP | OPTION_LIST, 0x07}, + {"cookiesvr", OPTION_IP | OPTION_LIST, 0x08}, + {"lprsvr", OPTION_IP | OPTION_LIST, 0x09}, + {"hostname", OPTION_STRING | OPTION_REQ, 0x0c}, + {"bootsize", OPTION_U16, 0x0d}, + {"domain", OPTION_STRING | OPTION_REQ, 0x0f}, + {"swapsvr", OPTION_IP, 0x10}, + {"rootpath", OPTION_STRING, 0x11}, + {"ipttl", OPTION_U8, 0x17}, + {"mtu", OPTION_U16, 0x1a}, + {"broadcast", OPTION_IP | OPTION_REQ, 0x1c}, + {"nisdomain", OPTION_STRING | OPTION_REQ, 0x28}, + {"nissrv", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x29}, + {"ntpsrv", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x2a}, + {"wins", OPTION_IP | OPTION_LIST, 0x2c}, + {"requestip", OPTION_IP, 0x32}, + {"lease", OPTION_U32, 0x33}, + {"dhcptype", OPTION_U8, 0x35}, + {"serverid", OPTION_IP, 0x36}, + {"message", OPTION_STRING, 0x38}, + {"vendorclass", OPTION_STRING, 0x3C}, + {"clientid", OPTION_STRING, 0x3D}, + {"tftp", OPTION_STRING, 0x42}, + {"bootfile", OPTION_STRING, 0x43}, + {"userclass", OPTION_STRING, 0x4D}, + /* MSIE's "Web Proxy Autodiscovery Protocol" support */ + {"wpad", OPTION_STRING, 0xfc}, + {"", 0x00, 0x00} +}; + +/* Lengths of the different option types */ +const unsigned char option_lengths[] = { + [OPTION_IP] = 4, + [OPTION_IP_PAIR] = 8, + [OPTION_BOOLEAN] = 1, + [OPTION_STRING] = 1, + [OPTION_U8] = 1, + [OPTION_U16] = 2, + [OPTION_S16] = 2, + [OPTION_U32] = 4, + [OPTION_S32] = 4 +}; + + +/* get an option with bounds checking (warning, not aligned). */ +uint8_t *get_option(struct dhcpMessage *packet, int code) +{ + int i, length; + uint8_t *optionptr; + int over = 0, done = 0, curr = OPTION_FIELD; + + optionptr = packet->options; + i = 0; + length = 308; + while (!done) { + if (i >= length) { + bb_error_msg("bogus packet, option fields too long"); + return NULL; + } + if (optionptr[i + OPT_CODE] == code) { + if (i + 1 + optionptr[i + OPT_LEN] >= length) { + bb_error_msg("bogus packet, option fields too long"); + return NULL; + } + return optionptr + i + 2; + } + switch (optionptr[i + OPT_CODE]) { + case DHCP_PADDING: + i++; + break; + case DHCP_OPTION_OVER: + if (i + 1 + optionptr[i + OPT_LEN] >= length) { + bb_error_msg("bogus packet, option fields too long"); + return NULL; + } + over = optionptr[i + 3]; + i += optionptr[OPT_LEN] + 2; + break; + case DHCP_END: + if (curr == OPTION_FIELD && over & FILE_FIELD) { + optionptr = packet->file; + i = 0; + length = 128; + curr = FILE_FIELD; + } else if (curr == FILE_FIELD && over & SNAME_FIELD) { + optionptr = packet->sname; + i = 0; + length = 64; + curr = SNAME_FIELD; + } else done = 1; + break; + default: + i += optionptr[OPT_LEN + i] + 2; + } + } + return NULL; +} + + +/* return the position of the 'end' option (no bounds checking) */ +int end_option(uint8_t *optionptr) +{ + int i = 0; + + while (optionptr[i] != DHCP_END) { + if (optionptr[i] == DHCP_PADDING) i++; + else i += optionptr[i + OPT_LEN] + 2; + } + return i; +} + + +/* add an option string to the options (an option string contains an option code, + * length, then data) */ +int add_option_string(uint8_t *optionptr, uint8_t *string) +{ + int end = end_option(optionptr); + + /* end position + string length + option code/length + end option */ + if (end + string[OPT_LEN] + 2 + 1 >= 308) { + bb_error_msg("option 0x%02x did not fit into the packet", + string[OPT_CODE]); + return 0; + } + DEBUG("adding option 0x%02x", string[OPT_CODE]); + memcpy(optionptr + end, string, string[OPT_LEN] + 2); + optionptr[end + string[OPT_LEN] + 2] = DHCP_END; + return string[OPT_LEN] + 2; +} + + +/* add a one to four byte option to a packet */ +int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data) +{ + const struct dhcp_option *dh; + + for (dh = dhcp_options; dh->code; dh++) { + if (dh->code == code) { + uint8_t option[6], len; + + option[OPT_CODE] = code; + len = option_lengths[dh->flags & TYPE_MASK]; + option[OPT_LEN] = len; + if (BB_BIG_ENDIAN) data <<= 8 * (4 - len); + /* This memcpy is for broken processors which can't + * handle a simple unaligned 32-bit assignment */ + memcpy(&option[OPT_DATA], &data, 4); + return add_option_string(optionptr, option); + } + } + + bb_error_msg("cannot add option 0x%02x", code); + return 0; +} diff --git a/networking/udhcp/options.h b/networking/udhcp/options.h new file mode 100644 index 000000000..588504e5d --- /dev/null +++ b/networking/udhcp/options.h @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* options.h */ +#ifndef _OPTIONS_H +#define _OPTIONS_H + +#define TYPE_MASK 0x0F + +enum { + OPTION_IP=1, + OPTION_IP_PAIR, + OPTION_STRING, + OPTION_BOOLEAN, + OPTION_U8, + OPTION_U16, + OPTION_S16, + OPTION_U32, + OPTION_S32 +}; + +#define OPTION_REQ 0x10 /* have the client request this option */ +#define OPTION_LIST 0x20 /* There can be a list of 1 or more of these */ + +struct dhcp_option { + char name[12]; + char flags; + uint8_t code; +}; + +extern const struct dhcp_option dhcp_options[]; +extern const unsigned char option_lengths[]; + +uint8_t *get_option(struct dhcpMessage *packet, int code); +int end_option(uint8_t *optionptr); +int add_option_string(uint8_t *optionptr, uint8_t *string); +int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data); + +#endif diff --git a/networking/udhcp/packet.c b/networking/udhcp/packet.c new file mode 100644 index 000000000..dec9d0ab3 --- /dev/null +++ b/networking/udhcp/packet.c @@ -0,0 +1,211 @@ +/* vi: set sw=4 ts=4: */ + +#include +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION +#include +#include +#else +#include +#include +#include +#endif + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +void udhcp_init_header(struct dhcpMessage *packet, char type) +{ + memset(packet, 0, sizeof(struct dhcpMessage)); + switch (type) { + case DHCPDISCOVER: + case DHCPREQUEST: + case DHCPRELEASE: + case DHCPINFORM: + packet->op = BOOTREQUEST; + break; + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + packet->op = BOOTREPLY; + } + packet->htype = ETH_10MB; + packet->hlen = ETH_10MB_LEN; + packet->cookie = htonl(DHCP_MAGIC); + packet->options[0] = DHCP_END; + add_simple_option(packet->options, DHCP_MESSAGE_TYPE, type); +} + + +/* read a packet from socket fd, return -1 on read error, -2 on packet error */ +int udhcp_get_packet(struct dhcpMessage *packet, int fd) +{ + static const char broken_vendors[][8] = { + "MSFT 98", + "" + }; + int bytes; + int i; + char unsigned *vendor; + + memset(packet, 0, sizeof(struct dhcpMessage)); + bytes = read(fd, packet, sizeof(struct dhcpMessage)); + if (bytes < 0) { + DEBUG("cannot read on listening socket, ignoring"); + return -1; + } + + if (ntohl(packet->cookie) != DHCP_MAGIC) { + bb_error_msg("received bogus message, ignoring"); + return -2; + } + DEBUG("Received a packet"); + + if (packet->op == BOOTREQUEST && (vendor = get_option(packet, DHCP_VENDOR))) { + for (i = 0; broken_vendors[i][0]; i++) { + if (vendor[OPT_LEN - 2] == (uint8_t)strlen(broken_vendors[i]) + && !strncmp((char*)vendor, broken_vendors[i], vendor[OPT_LEN - 2]) + ) { + DEBUG("broken client (%s), forcing broadcast", + broken_vendors[i]); + packet->flags |= htons(BROADCAST_FLAG); + } + } + } + + return bytes; +} + + +uint16_t udhcp_checksum(void *addr, int count) +{ + /* Compute Internet Checksum for "count" bytes + * beginning at location "addr". + */ + int32_t sum = 0; + uint16_t *source = (uint16_t *) addr; + + while (count > 1) { + /* This is the inner loop */ + sum += *source++; + count -= 2; + } + + /* Add left-over byte, if any */ + if (count > 0) { + /* Make sure that the left-over byte is added correctly both + * with little and big endian hosts */ + uint16_t tmp = 0; + *(uint8_t *) (&tmp) = * (uint8_t *) source; + sum += tmp; + } + /* Fold 32-bit sum to 16 bits */ + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + + +/* Construct a ip/udp header for a packet, and specify the source and dest hardware address */ +void BUG_sizeof_struct_udp_dhcp_packet_must_be_576(void); +int udhcp_raw_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port, uint8_t *dest_arp, int ifindex) +{ + int fd; + int result; + struct sockaddr_ll dest; + struct udp_dhcp_packet packet; + + fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (fd < 0) { + bb_perror_msg("socket"); + return -1; + } + + memset(&dest, 0, sizeof(dest)); + memset(&packet, 0, sizeof(packet)); + + dest.sll_family = AF_PACKET; + dest.sll_protocol = htons(ETH_P_IP); + dest.sll_ifindex = ifindex; + dest.sll_halen = 6; + memcpy(dest.sll_addr, dest_arp, 6); + if (bind(fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_ll)) < 0) { + bb_perror_msg("bind"); + close(fd); + return -1; + } + + packet.ip.protocol = IPPROTO_UDP; + packet.ip.saddr = source_ip; + packet.ip.daddr = dest_ip; + packet.udp.source = htons(source_port); + packet.udp.dest = htons(dest_port); + packet.udp.len = htons(sizeof(packet.udp) + sizeof(struct dhcpMessage)); /* cheat on the psuedo-header */ + packet.ip.tot_len = packet.udp.len; + memcpy(&(packet.data), payload, sizeof(struct dhcpMessage)); + packet.udp.check = udhcp_checksum(&packet, sizeof(struct udp_dhcp_packet)); + + packet.ip.tot_len = htons(sizeof(struct udp_dhcp_packet)); + packet.ip.ihl = sizeof(packet.ip) >> 2; + packet.ip.version = IPVERSION; + packet.ip.ttl = IPDEFTTL; + packet.ip.check = udhcp_checksum(&(packet.ip), sizeof(packet.ip)); + + if (sizeof(struct udp_dhcp_packet) != 576) + BUG_sizeof_struct_udp_dhcp_packet_must_be_576(); + + result = sendto(fd, &packet, sizeof(struct udp_dhcp_packet), 0, + (struct sockaddr *) &dest, sizeof(dest)); + if (result <= 0) { + bb_perror_msg("sendto"); + } + close(fd); + return result; +} + + +/* Let the kernel do all the work for packet generation */ +int udhcp_kernel_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port) +{ + int fd, result; + struct sockaddr_in client; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + return -1; + + if (setsockopt_reuseaddr(fd) == -1) { + close(fd); + return -1; + } + + memset(&client, 0, sizeof(client)); + client.sin_family = AF_INET; + client.sin_port = htons(source_port); + client.sin_addr.s_addr = source_ip; + + if (bind(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) { + close(fd); + return -1; + } + + memset(&client, 0, sizeof(client)); + client.sin_family = AF_INET; + client.sin_port = htons(dest_port); + client.sin_addr.s_addr = dest_ip; + + if (connect(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) { + close(fd); + return -1; + } + + result = write(fd, payload, sizeof(struct dhcpMessage)); + close(fd); + return result; +} diff --git a/networking/udhcp/pidfile.c b/networking/udhcp/pidfile.c new file mode 100644 index 000000000..bcb2608c5 --- /dev/null +++ b/networking/udhcp/pidfile.c @@ -0,0 +1,66 @@ +/* vi: set sw=4 ts=4: */ +/* pidfile.c + * + * Functions to assist in the writing and removing of pidfiles. + * + * Russ Dill September 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "common.h" + + +static const char *saved_pidfile; + +static void pidfile_delete(void) +{ + if (saved_pidfile) unlink(saved_pidfile); +} + + +int pidfile_acquire(const char *pidfile) +{ + int pid_fd; + if (!pidfile) return -1; + + pid_fd = open(pidfile, O_CREAT|O_WRONLY|O_TRUNC, 0644); + if (pid_fd < 0) { + bb_perror_msg("cannot open pidfile %s", pidfile); + } else { + lockf(pid_fd, F_LOCK, 0); + if (!saved_pidfile) + atexit(pidfile_delete); + saved_pidfile = pidfile; + } + + return pid_fd; +} + + +void pidfile_write_release(int pid_fd) +{ + FILE *out; + + if (pid_fd < 0) return; + + out = fdopen(pid_fd, "w"); + if (out) { + fprintf(out, "%d\n", getpid()); + fclose(out); + } + lockf(pid_fd, F_UNLCK, 0); + close(pid_fd); +} diff --git a/networking/udhcp/script.c b/networking/udhcp/script.c new file mode 100644 index 000000000..07f68362c --- /dev/null +++ b/networking/udhcp/script.c @@ -0,0 +1,213 @@ +/* vi: set sw=4 ts=4: */ +/* script.c + * + * Functions to call the DHCP client notification scripts + * + * Russ Dill July 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" + + +/* get a rough idea of how long an option will be (rounding up...) */ +static const int max_option_length[] = { + [OPTION_IP] = sizeof("255.255.255.255 "), + [OPTION_IP_PAIR] = sizeof("255.255.255.255 ") * 2, + [OPTION_STRING] = 1, + [OPTION_BOOLEAN] = sizeof("yes "), + [OPTION_U8] = sizeof("255 "), + [OPTION_U16] = sizeof("65535 "), + [OPTION_S16] = sizeof("-32768 "), + [OPTION_U32] = sizeof("4294967295 "), + [OPTION_S32] = sizeof("-2147483684 "), +}; + + +static inline int upper_length(int length, int opt_index) +{ + return max_option_length[opt_index] * + (length / option_lengths[opt_index]); +} + + +static int sprintip(char *dest, char *pre, uint8_t *ip) +{ + return sprintf(dest, "%s%d.%d.%d.%d", pre, ip[0], ip[1], ip[2], ip[3]); +} + + +/* really simple implementation, just count the bits */ +static int mton(struct in_addr *mask) +{ + int i; + unsigned long bits = ntohl(mask->s_addr); + /* too bad one can't check the carry bit, etc in c bit + * shifting */ + for (i = 0; i < 32 && !((bits >> i) & 1); i++); + return 32 - i; +} + + +/* Fill dest with the text of option 'option'. */ +static void fill_options(char *dest, uint8_t *option, + const struct dhcp_option *type_p) +{ + int type, optlen; + uint16_t val_u16; + int16_t val_s16; + uint32_t val_u32; + int32_t val_s32; + int len = option[OPT_LEN - 2]; + + dest += sprintf(dest, "%s=", type_p->name); + + type = type_p->flags & TYPE_MASK; + optlen = option_lengths[type]; + for (;;) { + switch (type) { + case OPTION_IP_PAIR: + dest += sprintip(dest, "", option); + *(dest++) = '/'; + option += 4; + optlen = 4; + case OPTION_IP: /* Works regardless of host byte order. */ + dest += sprintip(dest, "", option); + break; + case OPTION_BOOLEAN: + dest += sprintf(dest, *option ? "yes" : "no"); + break; + case OPTION_U8: + dest += sprintf(dest, "%u", *option); + break; + case OPTION_U16: + memcpy(&val_u16, option, 2); + dest += sprintf(dest, "%u", ntohs(val_u16)); + break; + case OPTION_S16: + memcpy(&val_s16, option, 2); + dest += sprintf(dest, "%d", ntohs(val_s16)); + break; + case OPTION_U32: + memcpy(&val_u32, option, 4); + dest += sprintf(dest, "%lu", (unsigned long) ntohl(val_u32)); + break; + case OPTION_S32: + memcpy(&val_s32, option, 4); + dest += sprintf(dest, "%ld", (long) ntohl(val_s32)); + break; + case OPTION_STRING: + memcpy(dest, option, len); + dest[len] = '\0'; + return; /* Short circuit this case */ + } + option += optlen; + len -= optlen; + if (len <= 0) break; + dest += sprintf(dest, " "); + } +} + + +/* put all the parameters into an environment */ +static char **fill_envp(struct dhcpMessage *packet) +{ + int num_options = 0; + int i, j; + char **envp; + uint8_t *temp; + struct in_addr subnet; + char over = 0; + + if (packet == NULL) + num_options = 0; + else { + for (i = 0; dhcp_options[i].code; i++) + if (get_option(packet, dhcp_options[i].code)) { + num_options++; + if (dhcp_options[i].code == DHCP_SUBNET) + num_options++; /* for mton */ + } + if (packet->siaddr) num_options++; + if ((temp = get_option(packet, DHCP_OPTION_OVER))) + over = *temp; + if (!(over & FILE_FIELD) && packet->file[0]) num_options++; + if (!(over & SNAME_FIELD) && packet->sname[0]) num_options++; + } + + envp = xzalloc(sizeof(char *) * (num_options + 5)); + j = 0; + envp[j++] = xasprintf("interface=%s", client_config.interface); + envp[j++] = xasprintf("PATH=%s", + getenv("PATH") ? : "/bin:/usr/bin:/sbin:/usr/sbin"); + envp[j++] = xasprintf("HOME=%s", getenv("HOME") ? : "/"); + + if (packet == NULL) return envp; + + envp[j] = xmalloc(sizeof("ip=255.255.255.255")); + sprintip(envp[j++], "ip=", (uint8_t *) &packet->yiaddr); + + for (i = 0; dhcp_options[i].code; i++) { + temp = get_option(packet, dhcp_options[i].code); + if (!temp) + continue; + envp[j] = xmalloc(upper_length(temp[OPT_LEN - 2], + dhcp_options[i].flags & TYPE_MASK) + strlen(dhcp_options[i].name) + 2); + fill_options(envp[j++], temp, &dhcp_options[i]); + + /* Fill in a subnet bits option for things like /24 */ + if (dhcp_options[i].code == DHCP_SUBNET) { + memcpy(&subnet, temp, 4); + envp[j++] = xasprintf("mask=%d", mton(&subnet)); + } + } + if (packet->siaddr) { + envp[j] = xmalloc(sizeof("siaddr=255.255.255.255")); + sprintip(envp[j++], "siaddr=", (uint8_t *) &packet->siaddr); + } + if (!(over & FILE_FIELD) && packet->file[0]) { + /* watch out for invalid packets */ + packet->file[sizeof(packet->file) - 1] = '\0'; + envp[j++] = xasprintf("boot_file=%s", packet->file); + } + if (!(over & SNAME_FIELD) && packet->sname[0]) { + /* watch out for invalid packets */ + packet->sname[sizeof(packet->sname) - 1] = '\0'; + envp[j++] = xasprintf("sname=%s", packet->sname); + } + return envp; +} + + +/* Call a script with a par file and env vars */ +void udhcp_run_script(struct dhcpMessage *packet, const char *name) +{ + int pid; + char **envp, **curr; + + if (client_config.script == NULL) + return; + + DEBUG("vfork'ing and execle'ing %s", client_config.script); + + envp = fill_envp(packet); + /* call script */ + pid = vfork(); + if (pid) { + waitpid(pid, NULL, 0); + for (curr = envp; *curr; curr++) free(*curr); + free(envp); + return; + } else if (pid == 0) { + /* close fd's? */ + /* exec script */ + execle(client_config.script, client_config.script, + name, NULL, envp); + bb_perror_msg("script %s failed", client_config.script); + exit(1); + } +} diff --git a/networking/udhcp/serverpacket.c b/networking/udhcp/serverpacket.c new file mode 100644 index 000000000..8889fda86 --- /dev/null +++ b/networking/udhcp/serverpacket.c @@ -0,0 +1,261 @@ +/* vi: set sw=4 ts=4: */ +/* serverpacket.c + * + * Construct and send DHCP server packets + * + * Russ Dill July 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* send a packet to giaddr using the kernel ip stack */ +static int send_packet_to_relay(struct dhcpMessage *payload) +{ + DEBUG("Forwarding packet to relay"); + + return udhcp_kernel_packet(payload, server_config.server, SERVER_PORT, + payload->giaddr, SERVER_PORT); +} + + +/* send a packet to a specific arp address and ip address by creating our own ip packet */ +static int send_packet_to_client(struct dhcpMessage *payload, int force_broadcast) +{ + uint8_t *chaddr; + uint32_t ciaddr; + + if (force_broadcast) { + DEBUG("broadcasting packet to client (NAK)"); + ciaddr = INADDR_BROADCAST; + chaddr = MAC_BCAST_ADDR; + } else if (payload->ciaddr) { + DEBUG("unicasting packet to client ciaddr"); + ciaddr = payload->ciaddr; + chaddr = payload->chaddr; + } else if (ntohs(payload->flags) & BROADCAST_FLAG) { + DEBUG("broadcasting packet to client (requested)"); + ciaddr = INADDR_BROADCAST; + chaddr = MAC_BCAST_ADDR; + } else { + DEBUG("unicasting packet to client yiaddr"); + ciaddr = payload->yiaddr; + chaddr = payload->chaddr; + } + return udhcp_raw_packet(payload, server_config.server, SERVER_PORT, + ciaddr, CLIENT_PORT, chaddr, server_config.ifindex); +} + + +/* send a dhcp packet, if force broadcast is set, the packet will be broadcast to the client */ +static int send_packet(struct dhcpMessage *payload, int force_broadcast) +{ + int ret; + + if (payload->giaddr) + ret = send_packet_to_relay(payload); + else ret = send_packet_to_client(payload, force_broadcast); + return ret; +} + + +static void init_packet(struct dhcpMessage *packet, struct dhcpMessage *oldpacket, char type) +{ + udhcp_init_header(packet, type); + packet->xid = oldpacket->xid; + memcpy(packet->chaddr, oldpacket->chaddr, 16); + packet->flags = oldpacket->flags; + packet->giaddr = oldpacket->giaddr; + packet->ciaddr = oldpacket->ciaddr; + add_simple_option(packet->options, DHCP_SERVER_ID, server_config.server); +} + + +/* add in the bootp options */ +static void add_bootp_options(struct dhcpMessage *packet) +{ + packet->siaddr = server_config.siaddr; + if (server_config.sname) + strncpy((char*)packet->sname, server_config.sname, sizeof(packet->sname) - 1); + if (server_config.boot_file) + strncpy((char*)packet->file, server_config.boot_file, sizeof(packet->file) - 1); +} + + +/* send a DHCP OFFER to a DHCP DISCOVER */ +int sendOffer(struct dhcpMessage *oldpacket) +{ + struct dhcpMessage packet; + struct dhcpOfferedAddr *lease = NULL; + uint32_t req_align, lease_time_align = server_config.lease; + uint8_t *req, *lease_time; + struct option_set *curr; + struct in_addr addr; + + uint32_t static_lease_ip; + + init_packet(&packet, oldpacket, DHCPOFFER); + + static_lease_ip = getIpByMac(server_config.static_leases, oldpacket->chaddr); + + /* ADDME: if static, short circuit */ + if (!static_lease_ip) { + /* the client is in our lease/offered table */ + lease = find_lease_by_chaddr(oldpacket->chaddr); + if (lease) { + if (!lease_expired(lease)) + lease_time_align = lease->expires - time(0); + packet.yiaddr = lease->yiaddr; + + /* Or the client has a requested ip */ + } else if ((req = get_option(oldpacket, DHCP_REQUESTED_IP)) + /* Don't look here (ugly hackish thing to do) */ + && memcpy(&req_align, req, 4) + /* and the ip is in the lease range */ + && ntohl(req_align) >= ntohl(server_config.start) + && ntohl(req_align) <= ntohl(server_config.end) + && !static_lease_ip /* Check that its not a static lease */ + /* and is not already taken/offered */ + && (!(lease = find_lease_by_yiaddr(req_align)) + /* or its taken, but expired */ /* ADDME: or maybe in here */ + || lease_expired(lease)) + ) { + packet.yiaddr = req_align; /* FIXME: oh my, is there a host using this IP? */ + /* otherwise, find a free IP */ + } else { + /* Is it a static lease? (No, because find_address skips static lease) */ + packet.yiaddr = find_address(0); + /* try for an expired lease */ + if (!packet.yiaddr) packet.yiaddr = find_address(1); + } + + if (!packet.yiaddr) { + bb_error_msg("no IP addresses to give - OFFER abandoned"); + return -1; + } + if (!add_lease(packet.chaddr, packet.yiaddr, server_config.offer_time)) { + bb_error_msg("lease pool is full - OFFER abandoned"); + return -1; + } + lease_time = get_option(oldpacket, DHCP_LEASE_TIME); + if (lease_time) { + memcpy(&lease_time_align, lease_time, 4); + lease_time_align = ntohl(lease_time_align); + if (lease_time_align > server_config.lease) + lease_time_align = server_config.lease; + } + + /* Make sure we aren't just using the lease time from the previous offer */ + if (lease_time_align < server_config.min_lease) + lease_time_align = server_config.lease; + /* ADDME: end of short circuit */ + } else { + /* It is a static lease... use it */ + packet.yiaddr = static_lease_ip; + } + + add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_align)); + + curr = server_config.options; + while (curr) { + if (curr->data[OPT_CODE] != DHCP_LEASE_TIME) + add_option_string(packet.options, curr->data); + curr = curr->next; + } + + add_bootp_options(&packet); + + addr.s_addr = packet.yiaddr; + bb_info_msg("Sending OFFER of %s", inet_ntoa(addr)); + return send_packet(&packet, 0); +} + + +int sendNAK(struct dhcpMessage *oldpacket) +{ + struct dhcpMessage packet; + + init_packet(&packet, oldpacket, DHCPNAK); + + DEBUG("Sending NAK"); + return send_packet(&packet, 1); +} + + +int sendACK(struct dhcpMessage *oldpacket, uint32_t yiaddr) +{ + struct dhcpMessage packet; + struct option_set *curr; + uint8_t *lease_time; + uint32_t lease_time_align = server_config.lease; + struct in_addr addr; + + init_packet(&packet, oldpacket, DHCPACK); + packet.yiaddr = yiaddr; + + if ((lease_time = get_option(oldpacket, DHCP_LEASE_TIME))) { + memcpy(&lease_time_align, lease_time, 4); + lease_time_align = ntohl(lease_time_align); + if (lease_time_align > server_config.lease) + lease_time_align = server_config.lease; + else if (lease_time_align < server_config.min_lease) + lease_time_align = server_config.lease; + } + + add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_align)); + + curr = server_config.options; + while (curr) { + if (curr->data[OPT_CODE] != DHCP_LEASE_TIME) + add_option_string(packet.options, curr->data); + curr = curr->next; + } + + add_bootp_options(&packet); + + addr.s_addr = packet.yiaddr; + bb_info_msg("Sending ACK to %s", inet_ntoa(addr)); + + if (send_packet(&packet, 0) < 0) + return -1; + + add_lease(packet.chaddr, packet.yiaddr, lease_time_align); + + return 0; +} + + +int send_inform(struct dhcpMessage *oldpacket) +{ + struct dhcpMessage packet; + struct option_set *curr; + + init_packet(&packet, oldpacket, DHCPACK); + + curr = server_config.options; + while (curr) { + if (curr->data[OPT_CODE] != DHCP_LEASE_TIME) + add_option_string(packet.options, curr->data); + curr = curr->next; + } + + add_bootp_options(&packet); + + return send_packet(&packet, 0); +} diff --git a/networking/udhcp/signalpipe.c b/networking/udhcp/signalpipe.c new file mode 100644 index 000000000..361596580 --- /dev/null +++ b/networking/udhcp/signalpipe.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* signalpipe.c + * + * Signal pipe infrastructure. A reliable way of delivering signals. + * + * Russ Dill December 2003 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "common.h" + + +static int signal_pipe[2]; + +static void signal_handler(int sig) +{ + if (send(signal_pipe[1], &sig, sizeof(sig), MSG_DONTWAIT) < 0) + bb_perror_msg("cannot send signal"); +} + + +/* Call this before doing anything else. Sets up the socket pair + * and installs the signal handler */ +void udhcp_sp_setup(void) +{ + socketpair(AF_UNIX, SOCK_STREAM, 0, signal_pipe); + fcntl(signal_pipe[0], F_SETFD, FD_CLOEXEC); + fcntl(signal_pipe[1], F_SETFD, FD_CLOEXEC); + signal(SIGUSR1, signal_handler); + signal(SIGUSR2, signal_handler); + signal(SIGTERM, signal_handler); +} + + +/* Quick little function to setup the rfds. Will return the + * max_fd for use with select. Limited in that you can only pass + * one extra fd */ +int udhcp_sp_fd_set(fd_set *rfds, int extra_fd) +{ + FD_ZERO(rfds); + FD_SET(signal_pipe[0], rfds); + if (extra_fd >= 0) { + fcntl(extra_fd, F_SETFD, FD_CLOEXEC); + FD_SET(extra_fd, rfds); + } + return signal_pipe[0] > extra_fd ? signal_pipe[0] : extra_fd; +} + + +/* Read a signal from the signal pipe. Returns 0 if there is + * no signal, -1 on error (and sets errno appropriately), and + * your signal on success */ +int udhcp_sp_read(fd_set *rfds) +{ + int sig; + + if (!FD_ISSET(signal_pipe[0], rfds)) + return 0; + + if (read(signal_pipe[0], &sig, sizeof(sig)) < 0) + return -1; + + return sig; +} diff --git a/networking/udhcp/socket.c b/networking/udhcp/socket.c new file mode 100644 index 000000000..2bae68f27 --- /dev/null +++ b/networking/udhcp/socket.c @@ -0,0 +1,130 @@ +/* vi: set sw=4 ts=4: */ +/* + * socket.c -- DHCP server client/server socket creation + * + * udhcp client/server + * Copyright (C) 1999 Matthew Ramsay + * Chris Trew + * + * Rewrite by Russ Dill July 2001 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION +#include +#include +#else +#include +#include +#include +#endif + +#include "common.h" + + +int read_interface(char *interface, int *ifindex, uint32_t *addr, uint8_t *arp) +{ + int fd; + struct ifreq ifr; + struct sockaddr_in *our_ip; + + memset(&ifr, 0, sizeof(struct ifreq)); + fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (fd < 0) { + bb_perror_msg("socket failed"); + return -1; + } + + ifr.ifr_addr.sa_family = AF_INET; + strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name)); + if (addr) { + if (ioctl(fd, SIOCGIFADDR, &ifr) != 0) { + bb_perror_msg("SIOCGIFADDR failed, is the interface up and configured?"); + close(fd); + return -1; + } + our_ip = (struct sockaddr_in *) &ifr.ifr_addr; + *addr = our_ip->sin_addr.s_addr; + DEBUG("%s (our ip) = %s", ifr.ifr_name, inet_ntoa(our_ip->sin_addr)); + } + + if (ifindex) { + if (ioctl(fd, SIOCGIFINDEX, &ifr) != 0) { + bb_perror_msg("SIOCGIFINDEX failed"); + close(fd); + return -1; + } + DEBUG("adapter index %d", ifr.ifr_ifindex); + *ifindex = ifr.ifr_ifindex; + } + + if (arp) { + if (ioctl(fd, SIOCGIFHWADDR, &ifr) != 0) { + bb_perror_msg("SIOCGIFHWADDR failed"); + close(fd); + return -1; + } + memcpy(arp, ifr.ifr_hwaddr.sa_data, 6); + DEBUG("adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x", + arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]); + } + + return 0; +} + + +int listen_socket(uint32_t ip, int port, char *inf) +{ + struct ifreq interface; + int fd; + struct sockaddr_in addr; + + DEBUG("Opening listen socket on 0x%08x:%d %s", ip, port, inf); + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + bb_perror_msg("socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ip; + + if (setsockopt_reuseaddr(fd) == -1) { + close(fd); + return -1; + } + if (setsockopt_broadcast(fd) == -1) { + close(fd); + return -1; + } + + strncpy(interface.ifr_name, inf, IFNAMSIZ); + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (char *)&interface, sizeof(interface)) < 0) { + close(fd); + return -1; + } + + if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1) { + close(fd); + return -1; + } + + return fd; +} diff --git a/networking/udhcp/static_leases.c b/networking/udhcp/static_leases.c new file mode 100644 index 000000000..aabfb81aa --- /dev/null +++ b/networking/udhcp/static_leases.c @@ -0,0 +1,99 @@ +/* vi: set sw=4 ts=4: */ +/* + * static_leases.c -- Couple of functions to assist with storing and + * retrieving data for static leases + * + * Wade Berrier September 2004 + * + */ + +#include "common.h" +#include "dhcpd.h" + + +/* Takes the address of the pointer to the static_leases linked list, + * Address to a 6 byte mac address + * Address to a 4 byte ip address */ +int addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t *ip) +{ + struct static_lease *cur; + struct static_lease *new_static_lease; + + /* Build new node */ + new_static_lease = xmalloc(sizeof(struct static_lease)); + new_static_lease->mac = mac; + new_static_lease->ip = ip; + new_static_lease->next = NULL; + + /* If it's the first node to be added... */ + if (*lease_struct == NULL) { + *lease_struct = new_static_lease; + } else { + cur = *lease_struct; + while (cur->next) { + cur = cur->next; + } + + cur->next = new_static_lease; + } + + return 1; +} + +/* Check to see if a mac has an associated static lease */ +uint32_t getIpByMac(struct static_lease *lease_struct, void *arg) +{ + uint32_t return_ip; + struct static_lease *cur = lease_struct; + uint8_t *mac = arg; + + return_ip = 0; + + while (cur) { + /* If the client has the correct mac */ + if (memcmp(cur->mac, mac, 6) == 0) { + return_ip = *(cur->ip); + } + + cur = cur->next; + } + + return return_ip; +} + +/* Check to see if an ip is reserved as a static ip */ +uint32_t reservedIp(struct static_lease *lease_struct, uint32_t ip) +{ + struct static_lease *cur = lease_struct; + + uint32_t return_val = 0; + + while (cur) { + /* If the client has the correct ip */ + if (*cur->ip == ip) + return_val = 1; + + cur = cur->next; + } + + return return_val; +} + +#if ENABLE_FEATURE_UDHCP_DEBUG +/* Print out static leases just to check what's going on */ +/* Takes the address of the pointer to the static_leases linked list */ +void printStaticLeases(struct static_lease **arg) +{ + /* Get a pointer to the linked list */ + struct static_lease *cur = *arg; + + while (cur) { + /* printf("PrintStaticLeases: Lease mac Address: %x\n", cur->mac); */ + printf("PrintStaticLeases: Lease mac Value: %x\n", *(cur->mac)); + /* printf("PrintStaticLeases: Lease ip Address: %x\n", cur->ip); */ + printf("PrintStaticLeases: Lease ip Value: %x\n", *(cur->ip)); + + cur = cur->next; + } +} +#endif diff --git a/networking/vconfig.c b/networking/vconfig.c new file mode 100644 index 000000000..003c1a8f7 --- /dev/null +++ b/networking/vconfig.c @@ -0,0 +1,164 @@ +/* vi: set sw=4 ts=4: */ +/* + * vconfig implementation for busybox + * + * Copyright (C) 2001 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A */ + +#include "busybox.h" +#include + +/* Stuff from linux/if_vlan.h, kernel version 2.4.23 */ +enum vlan_ioctl_cmds { + ADD_VLAN_CMD, + DEL_VLAN_CMD, + SET_VLAN_INGRESS_PRIORITY_CMD, + SET_VLAN_EGRESS_PRIORITY_CMD, + GET_VLAN_INGRESS_PRIORITY_CMD, + GET_VLAN_EGRESS_PRIORITY_CMD, + SET_VLAN_NAME_TYPE_CMD, + SET_VLAN_FLAG_CMD +}; +enum vlan_name_types { + VLAN_NAME_TYPE_PLUS_VID, /* Name will look like: vlan0005 */ + VLAN_NAME_TYPE_RAW_PLUS_VID, /* name will look like: eth1.0005 */ + VLAN_NAME_TYPE_PLUS_VID_NO_PAD, /* Name will look like: vlan5 */ + VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, /* Name will look like: eth0.5 */ + VLAN_NAME_TYPE_HIGHEST +}; + +struct vlan_ioctl_args { + int cmd; /* Should be one of the vlan_ioctl_cmds enum above. */ + char device1[24]; + + union { + char device2[24]; + int VID; + unsigned int skb_priority; + unsigned int name_type; + unsigned int bind_type; + unsigned int flag; /* Matches vlan_dev_info flags */ + } u; + + short vlan_qos; +}; + +#define VLAN_GROUP_ARRAY_LEN 4096 +#define SIOCSIFVLAN 0x8983 /* Set 802.1Q VLAN options */ + +/* On entry, table points to the length of the current string plus + * nul terminator plus data length for the subsequent entry. The + * return value is the last data entry for the matching string. */ +static const char *xfind_str(const char *table, const char *str) +{ + while (strcasecmp(str, table+1) != 0) { + if (!*(table += table[0])) { + bb_show_usage(); + } + } + return table - 1; +} + +static const char cmds[] = { + 4, ADD_VLAN_CMD, 7, + 'a', 'd', 'd', 0, + 3, DEL_VLAN_CMD, 7, + 'r', 'e', 'm', 0, + 3, SET_VLAN_NAME_TYPE_CMD, 17, + 's', 'e', 't', '_', + 'n', 'a', 'm', 'e', '_', + 't', 'y', 'p', 'e', 0, + 5, SET_VLAN_FLAG_CMD, 12, + 's', 'e', 't', '_', + 'f', 'l', 'a', 'g', 0, + 5, SET_VLAN_EGRESS_PRIORITY_CMD, 18, + 's', 'e', 't', '_', + 'e', 'g', 'r', 'e', 's', 's', '_', + 'm', 'a', 'p', 0, + 5, SET_VLAN_INGRESS_PRIORITY_CMD, 16, + 's', 'e', 't', '_', + 'i', 'n', 'g', 'r', 'e', 's', 's', '_', + 'm', 'a', 'p', 0, +}; + +static const char name_types[] = { + VLAN_NAME_TYPE_PLUS_VID, 16, + 'V', 'L', 'A', 'N', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + 0, + VLAN_NAME_TYPE_PLUS_VID_NO_PAD, 22, + 'V', 'L', 'A', 'N', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + '_', 'N', 'O', '_', 'P', 'A', 'D', 0, + VLAN_NAME_TYPE_RAW_PLUS_VID, 15, + 'D', 'E', 'V', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + 0, + VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, 20, + 'D', 'E', 'V', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + '_', 'N', 'O', '_', 'P', 'A', 'D', 0, +}; + +static const char conf_file_name[] = "/proc/net/vlan/config"; + +int vconfig_main(int argc, char **argv) +{ + struct vlan_ioctl_args ifr; + const char *p; + int fd; + + if (argc < 3) { + bb_show_usage(); + } + + /* Don't bother closing the filedes. It will be closed on cleanup. */ + /* Will die if 802.1q is not present */ + xopen(conf_file_name, O_RDONLY); + + memset(&ifr, 0, sizeof(struct vlan_ioctl_args)); + + ++argv; + p = xfind_str(cmds+2, *argv); + ifr.cmd = *p; + if (argc != p[-1]) { + bb_show_usage(); + } + + if (ifr.cmd == SET_VLAN_NAME_TYPE_CMD) { /* set_name_type */ + ifr.u.name_type = *xfind_str(name_types+1, argv[1]); + } else { + if (strlen(argv[1]) >= IF_NAMESIZE) { + bb_error_msg_and_die("if_name >= %d chars", IF_NAMESIZE); + } + strcpy(ifr.device1, argv[1]); + p = argv[2]; + + /* I suppose one could try to combine some of the function calls below, + * since ifr.u.flag, ifr.u.VID, and ifr.u.skb_priority are all same-sized + * (unsigned) int members of a unions. But because of the range checking, + * doing so wouldn't save that much space and would also make maintainence + * more of a pain. */ + if (ifr.cmd == SET_VLAN_FLAG_CMD) { /* set_flag */ + ifr.u.flag = xatoul_range(p, 0, 1); + /* DM: in order to set reorder header, qos must be set */ + ifr.vlan_qos = xatoul_range(argv[3], 0, 7); + } else if (ifr.cmd == ADD_VLAN_CMD) { /* add */ + ifr.u.VID = xatoul_range(p, 0, VLAN_GROUP_ARRAY_LEN-1); + } else if (ifr.cmd != DEL_VLAN_CMD) { /* set_{egress|ingress}_map */ + ifr.u.skb_priority = xatou(p); + ifr.vlan_qos = xatoul_range(argv[3], 0, 7); + } + } + + fd = xsocket(AF_INET, SOCK_STREAM, 0); + if (ioctl(fd, SIOCSIFVLAN, &ifr) < 0) { + bb_perror_msg_and_die("ioctl error for %s", *argv); + } + + return 0; +} diff --git a/networking/wget.c b/networking/wget.c new file mode 100644 index 000000000..028e18c73 --- /dev/null +++ b/networking/wget.c @@ -0,0 +1,832 @@ +/* vi: set sw=4 ts=4: */ +/* + * wget - retrieve a file using HTTP or FTP + * + * Chip Rosenthal Covad Communications + * + */ + +/* We want libc to give us xxx64 functions also */ +/* http://www.unix.org/version2/whatsnew/lfs20mar.html */ +#define _LARGEFILE64_SOURCE 1 + +#include "busybox.h" +#include /* for struct option */ + +struct host_info { + // May be used if we ever will want to free() all xstrdup()s... + /* char *allocated; */ + char *host; + int port; + char *path; + int is_ftp; + char *user; +}; + +static void parse_url(char *url, struct host_info *h); +static FILE *open_socket(struct sockaddr_in *s_in); +static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc); +static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf); + +/* Globals (can be accessed from signal handlers */ +static off_t content_len; /* Content-length of the file */ +static off_t beg_range; /* Range at which continue begins */ +#if ENABLE_FEATURE_WGET_STATUSBAR +static off_t transferred; /* Number of bytes transferred so far */ +#endif +static int chunked; /* chunked transfer encoding */ +#if ENABLE_FEATURE_WGET_STATUSBAR +static void progressmeter(int flag); +static char *curfile; /* Name of current file being transferred */ +static struct timeval start; /* Time a transfer started */ +enum { + STALLTIME = 5 /* Seconds when xfer considered "stalled" */ +}; +#else +static void progressmeter(int flag) {} +#endif + +/* Read NMEMB elements of SIZE bytes into PTR from STREAM. Returns the + * number of elements read, and a short count if an eof or non-interrupt + * error is encountered. */ +static size_t safe_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + size_t ret = 0; + + do { + clearerr(stream); + ret += fread((char *)ptr + (ret * size), size, nmemb - ret, stream); + } while (ret < nmemb && ferror(stream) && errno == EINTR); + + return ret; +} + +/* Read a line or SIZE - 1 bytes into S, whichever is less, from STREAM. + * Returns S, or NULL if an eof or non-interrupt error is encountered. */ +static char *safe_fgets(char *s, int size, FILE *stream) +{ + char *ret; + + do { + clearerr(stream); + ret = fgets(s, size, stream); + } while (ret == NULL && ferror(stream) && errno == EINTR); + + return ret; +} + +#if ENABLE_FEATURE_WGET_AUTHENTICATION +/* + * Base64-encode character string and return the string. + */ +static char *base64enc(unsigned char *p, char *buf, int len) +{ + bb_uuencode(p, buf, len, bb_uuenc_tbl_base64); + return buf; +} +#endif + +int wget_main(int argc, char **argv) +{ + char buf[512]; + struct host_info server, target; + struct sockaddr_in s_in; + int n, status; + int port; + int try = 5; + unsigned opt; + char *s; + char *proxy = 0; + char *dir_prefix = NULL; +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + char *extra_headers = NULL; + llist_t *headers_llist = NULL; +#endif + + /* server.allocated = target.allocated = NULL; */ + + FILE *sfp = NULL; /* socket to web/ftp server */ + FILE *dfp = NULL; /* socket to ftp server (data) */ + char *fname_out = NULL; /* where to direct output (-O) */ + int got_clen = 0; /* got content-length: from server */ + int output_fd = -1; + int use_proxy = 1; /* Use proxies if env vars are set */ + const char *proxy_flag = "on"; /* Use proxies if env vars are set */ + const char *user_agent = "Wget";/* Content of the "User-Agent" header field */ + + /* + * Crack command line. + */ + enum { + WGET_OPT_CONTINUE = 0x1, + WGET_OPT_QUIET = 0x2, + WGET_OPT_OUTNAME = 0x4, + WGET_OPT_PREFIX = 0x8, + WGET_OPT_PROXY = 0x10, + WGET_OPT_USER_AGENT = 0x20, + WGET_OPT_PASSIVE = 0x40, + WGET_OPT_HEADER = 0x80, + }; +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + static const struct option wget_long_options[] = { + // name, has_arg, flag, val + { "continue", no_argument, NULL, 'c' }, + { "quiet", no_argument, NULL, 'q' }, + { "output-document", required_argument, NULL, 'O' }, + { "directory-prefix", required_argument, NULL, 'P' }, + { "proxy", required_argument, NULL, 'Y' }, + { "user-agent", required_argument, NULL, 'U' }, + { "passive-ftp", no_argument, NULL, 0xff }, + { "header", required_argument, NULL, 0xfe }, + { 0, 0, 0, 0 } + }; + applet_long_options = wget_long_options; +#endif + opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::"); + opt = getopt32(argc, argv, "cqO:P:Y:U:", + &fname_out, &dir_prefix, + &proxy_flag, &user_agent + USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist) + ); + if (strcmp(proxy_flag, "off") == 0) { + /* Use the proxy if necessary. */ + use_proxy = 0; + } +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + if (headers_llist) { + int size = 1; + char *cp; + llist_t *ll = headers_llist = rev_llist(headers_llist); + while (ll) { + size += strlen(ll->data) + 2; + ll = ll->link; + } + extra_headers = cp = xmalloc(size); + while (headers_llist) { + cp += sprintf(cp, "%s\r\n", headers_llist->data); + headers_llist = headers_llist->link; + } + } +#endif + + parse_url(argv[optind], &target); + server.host = target.host; + server.port = target.port; + + /* + * Use the proxy if necessary. + */ + if (use_proxy) { + proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy"); + if (proxy && *proxy) { + parse_url(proxy, &server); + } else { + use_proxy = 0; + } + } + + /* Guess an output filename */ + if (!fname_out) { + // Dirty hack. Needed because bb_get_last_path_component + // will destroy trailing / by storing '\0' in last byte! + if (*target.path && target.path[strlen(target.path)-1] != '/') { + fname_out = +#if ENABLE_FEATURE_WGET_STATUSBAR + curfile = +#endif + bb_get_last_path_component(target.path); + } + if (!fname_out || !fname_out[0]) { + fname_out = +#if ENABLE_FEATURE_WGET_STATUSBAR + curfile = +#endif + "index.html"; + } + if (dir_prefix != NULL) + fname_out = concat_path_file(dir_prefix, fname_out); +#if ENABLE_FEATURE_WGET_STATUSBAR + } else { + curfile = bb_get_last_path_component(fname_out); +#endif + } + /* Impossible? + if ((opt & WGET_OPT_CONTINUE) && !fname_out) + bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)"); */ + + /* + * Determine where to start transfer. + */ + if (fname_out[0] == '-' && !fname_out[1]) { + output_fd = 1; + opt &= ~WGET_OPT_CONTINUE; + } + if (opt & WGET_OPT_CONTINUE) { + output_fd = open(fname_out, O_WRONLY); + if (output_fd >= 0) { + beg_range = xlseek(output_fd, 0, SEEK_END); + } + /* File doesn't exist. We do not create file here yet. + We are not sure it exists on remove side */ + } + + /* We want to do exactly _one_ DNS lookup, since some + * sites (i.e. ftp.us.debian.org) use round-robin DNS + * and we want to connect to only one IP... */ + bb_lookup_host(&s_in, server.host); + s_in.sin_port = server.port; + if (!(opt & WGET_OPT_QUIET)) { + fprintf(stderr, "Connecting to %s[%s]:%d\n", + server.host, inet_ntoa(s_in.sin_addr), ntohs(server.port)); + } + + if (use_proxy || !target.is_ftp) { + /* + * HTTP session + */ + do { + got_clen = chunked = 0; + + if (!--try) + bb_error_msg_and_die("too many redirections"); + + /* + * Open socket to http server + */ + if (sfp) fclose(sfp); + sfp = open_socket(&s_in); + + /* + * Send HTTP request. + */ + if (use_proxy) { + const char *format = "GET %stp://%s:%d/%s HTTP/1.1\r\n"; +#if ENABLE_FEATURE_WGET_IP6_LITERAL + if (strchr(target.host, ':')) + format = "GET %stp://[%s]:%d/%s HTTP/1.1\r\n"; +#endif + fprintf(sfp, format, + target.is_ftp ? "f" : "ht", target.host, + ntohs(target.port), target.path); + } else { + fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path); + } + + fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n", target.host, + user_agent); + +#if ENABLE_FEATURE_WGET_AUTHENTICATION + if (target.user) { + fprintf(sfp, "Authorization: Basic %s\r\n", + base64enc((unsigned char*)target.user, buf, sizeof(buf))); + } + if (use_proxy && server.user) { + fprintf(sfp, "Proxy-Authorization: Basic %s\r\n", + base64enc((unsigned char*)server.user, buf, sizeof(buf))); + } +#endif + + if (beg_range) + fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range); +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + if (extra_headers) + fputs(extra_headers, sfp); +#endif + fprintf(sfp, "Connection: close\r\n\r\n"); + + /* + * Retrieve HTTP response line and check for "200" status code. + */ + read_response: + if (fgets(buf, sizeof(buf), sfp) == NULL) + bb_error_msg_and_die("no response from server"); + + s = buf; + while (*s != '\0' && !isspace(*s)) ++s; + s = skip_whitespace(s); + // FIXME: no error check + // xatou wouldn't work: "200 OK" + status = atoi(s); + switch (status) { + case 0: + case 100: + while (gethdr(buf, sizeof(buf), sfp, &n) != NULL) + /* eat all remaining headers */; + goto read_response; + case 200: + break; + case 300: /* redirection */ + case 301: + case 302: + case 303: + break; + case 206: + if (beg_range) + break; + /*FALLTHRU*/ + default: + /* Show first line only and kill any ESC tricks */ + buf[strcspn(buf, "\n\r\x1b")] = '\0'; + bb_error_msg_and_die("server returned error: %s", buf); + } + + /* + * Retrieve HTTP headers. + */ + while ((s = gethdr(buf, sizeof(buf), sfp, &n)) != NULL) { + if (strcasecmp(buf, "content-length") == 0) { + content_len = BB_STRTOOFF(s, NULL, 10); + if (errno || content_len < 0) { + bb_error_msg_and_die("content-length %s is garbage", s); + } + got_clen = 1; + continue; + } + if (strcasecmp(buf, "transfer-encoding") == 0) { + if (strcasecmp(s, "chunked") != 0) + bb_error_msg_and_die("server wants to do %s transfer encoding", s); + chunked = got_clen = 1; + } + if (strcasecmp(buf, "location") == 0) { + if (s[0] == '/') + /* free(target.allocated); */ + target.path = /* target.allocated = */ xstrdup(s+1); + else { + parse_url(s, &target); + if (use_proxy == 0) { + server.host = target.host; + server.port = target.port; + } + bb_lookup_host(&s_in, server.host); + s_in.sin_port = server.port; + break; + } + } + } + } while(status >= 300); + + dfp = sfp; + + } else { + + /* + * FTP session + */ + if (!target.user) + target.user = xstrdup("anonymous:busybox@"); + + sfp = open_socket(&s_in); + if (ftpcmd(NULL, NULL, sfp, buf) != 220) + bb_error_msg_and_die("%s", buf+4); + + /* + * Splitting username:password pair, + * trying to log in + */ + s = strchr(target.user, ':'); + if (s) + *(s++) = '\0'; + switch (ftpcmd("USER ", target.user, sfp, buf)) { + case 230: + break; + case 331: + if (ftpcmd("PASS ", s, sfp, buf) == 230) + break; + /* FALLTHRU (failed login) */ + default: + bb_error_msg_and_die("ftp login: %s", buf+4); + } + + ftpcmd("TYPE I", NULL, sfp, buf); + + /* + * Querying file size + */ + if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) { + content_len = BB_STRTOOFF(buf+4, NULL, 10); + if (errno || content_len < 0) { + bb_error_msg_and_die("SIZE value is garbage"); + } + got_clen = 1; + } + + /* + * Entering passive mode + */ + if (ftpcmd("PASV", NULL, sfp, buf) != 227) { + pasv_error: + bb_error_msg_and_die("bad response to %s: %s", "PASV", buf); + } + // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage] + // Server's IP is N1.N2.N3.N4 (we ignore it) + // Server's port for data connection is P1*256+P2 + s = strrchr(buf, ')'); + if (s) s[0] = '\0'; + s = strrchr(buf, ','); + if (!s) goto pasv_error; + port = xatou_range(s+1, 0, 255); + *s = '\0'; + s = strrchr(buf, ','); + if (!s) goto pasv_error; + port += xatou_range(s+1, 0, 255) * 256; + s_in.sin_port = htons(port); + dfp = open_socket(&s_in); + + if (beg_range) { + sprintf(buf, "REST %"OFF_FMT"d", beg_range); + if (ftpcmd(buf, NULL, sfp, buf) == 350) + content_len -= beg_range; + } + + if (ftpcmd("RETR ", target.path, sfp, buf) > 150) + bb_error_msg_and_die("bad response to RETR: %s", buf); + } + + + /* + * Retrieve file + */ + if (chunked) { + fgets(buf, sizeof(buf), dfp); + content_len = STRTOOFF(buf, NULL, 16); + /* FIXME: error check?? */ + } + + /* Do it before progressmeter (want to have nice error message) */ + if (output_fd < 0) + output_fd = xopen(fname_out, + O_WRONLY|O_CREAT|O_EXCL|O_TRUNC); + + if (!(opt & WGET_OPT_QUIET)) + progressmeter(-1); + + do { + while (content_len > 0 || !got_clen) { + unsigned rdsz = sizeof(buf); + if (content_len < sizeof(buf) && (chunked || got_clen)) + rdsz = (unsigned)content_len; + n = safe_fread(buf, 1, rdsz, dfp); + if (n <= 0) + break; + if (full_write(output_fd, buf, n) != n) { + bb_perror_msg_and_die(bb_msg_write_error); + } +#if ENABLE_FEATURE_WGET_STATUSBAR + transferred += n; +#endif + if (got_clen) { + content_len -= n; + } + } + + if (chunked) { + safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */ + safe_fgets(buf, sizeof(buf), dfp); + content_len = STRTOOFF(buf, NULL, 16); + /* FIXME: error check? */ + if (content_len == 0) { + chunked = 0; /* all done! */ + } + } + + if (n == 0 && ferror(dfp)) { + bb_perror_msg_and_die(bb_msg_read_error); + } + } while (chunked); + + if (!(opt & WGET_OPT_QUIET)) + progressmeter(1); + + if ((use_proxy == 0) && target.is_ftp) { + fclose(dfp); + if (ftpcmd(NULL, NULL, sfp, buf) != 226) + bb_error_msg_and_die("ftp error: %s", buf+4); + ftpcmd("QUIT", NULL, sfp, buf); + } + exit(EXIT_SUCCESS); +} + + +static void parse_url(char *src_url, struct host_info *h) +{ + char *url, *p, *cp, *sp, *up, *pp; + + /* h->allocated = */ url = xstrdup(src_url); + + if (strncmp(url, "http://", 7) == 0) { + h->port = bb_lookup_port("http", "tcp", 80); + h->host = url + 7; + h->is_ftp = 0; + } else if (strncmp(url, "ftp://", 6) == 0) { + h->port = bb_lookup_port("ftp", "tcp", 21); + h->host = url + 6; + h->is_ftp = 1; + } else + bb_error_msg_and_die("not an http or ftp url: %s", url); + + // FYI: + // "Real" wget 'http://busybox.net?var=a/b' sends this request: + // 'GET /?var=a/b HTTP 1.0' + // and saves 'index.html?var=a%2Fb' (we save 'b') + // wget 'http://busybox.net?login=john@doe': + // request: 'GET /?login=john@doe HTTP/1.0' + // saves: 'index.html?login=john@doe' (we save '?login=john@doe') + // wget 'http://busybox.net#test/test': + // request: 'GET / HTTP/1.0' + // saves: 'index.html' (we save 'test') + // + // We also don't add unique .N suffix if file exists... + sp = strchr(h->host, '/'); + p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p; + p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p; + if (!sp) { + h->path = ""; + } else if (*sp == '/') { + *sp++ = '\0'; + h->path = sp; + } else { // '#' or '?' + // http://busybox.net?login=john@doe is a valid URL + // memmove converts to: + // http:/busybox.nett?login=john@doe... + memmove(h->host-1, h->host, sp - h->host); + h->host--; + sp[-1] = '\0'; + h->path = sp; + } + + up = strrchr(h->host, '@'); + if (up != NULL) { + h->user = h->host; + *up++ = '\0'; + h->host = up; + } else + h->user = NULL; + + pp = h->host; + +#if ENABLE_FEATURE_WGET_IP6_LITERAL + if (h->host[0] == '[') { + char *ep; + + ep = h->host + 1; + while (*ep == ':' || isxdigit(*ep)) + ep++; + if (*ep == ']') { + h->host++; + *ep = '\0'; + pp = ep + 1; + } + } +#endif + + cp = strchr(pp, ':'); + if (cp != NULL) { + *cp++ = '\0'; + h->port = htons(xatou16(cp)); + } +} + + +static FILE *open_socket(struct sockaddr_in *s_in) +{ + FILE *fp; + + /* glibc 2.4 seems to try seeking on it - ??! */ + /* hopefully it understands what ESPIPE means... */ + fp = fdopen(xconnect_tcp_v4(s_in), "r+"); + if (fp == NULL) + bb_perror_msg_and_die("fdopen"); + + return fp; +} + + +static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc) +{ + char *s, *hdrval; + int c; + + *istrunc = 0; + + /* retrieve header line */ + if (fgets(buf, bufsiz, fp) == NULL) + return NULL; + + /* see if we are at the end of the headers */ + for (s = buf; *s == '\r'; ++s) + ; + if (s[0] == '\n') + return NULL; + + /* convert the header name to lower case */ + for (s = buf; isalnum(*s) || *s == '-'; ++s) + *s = tolower(*s); + + /* verify we are at the end of the header name */ + if (*s != ':') + bb_error_msg_and_die("bad header line: %s", buf); + + /* locate the start of the header value */ + for (*s++ = '\0'; *s == ' ' || *s == '\t'; ++s) + ; + hdrval = s; + + /* locate the end of header */ + while (*s != '\0' && *s != '\r' && *s != '\n') + ++s; + + /* end of header found */ + if (*s != '\0') { + *s = '\0'; + return hdrval; + } + + /* Rats! The buffer isn't big enough to hold the entire header value. */ + while (c = getc(fp), c != EOF && c != '\n') + ; + *istrunc = 1; + return hdrval; +} + +static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf) +{ + int result; + if (s1) { + if (!s2) s2 = ""; + fprintf(fp, "%s%s\r\n", s1, s2); + fflush(fp); + } + + do { + char *buf_ptr; + + if (fgets(buf, 510, fp) == NULL) { + bb_perror_msg_and_die("error getting response"); + } + buf_ptr = strstr(buf, "\r\n"); + if (buf_ptr) { + *buf_ptr = '\0'; + } + } while (!isdigit(buf[0]) || buf[3] != ' '); + + buf[3] = '\0'; + result = xatoi_u(buf); + buf[3] = ' '; + return result; +} + +#if ENABLE_FEATURE_WGET_STATUSBAR +/* Stuff below is from BSD rcp util.c, as added to openshh. + * Original copyright notice is retained at the end of this file. + */ +static int +getttywidth(void) +{ + int width; + get_terminal_width_height(0, &width, NULL); + return width; +} + +static void +updateprogressmeter(int ignore) +{ + int save_errno = errno; + + progressmeter(0); + errno = save_errno; +} + +static void alarmtimer(int iwait) +{ + struct itimerval itv; + + itv.it_value.tv_sec = iwait; + itv.it_value.tv_usec = 0; + itv.it_interval = itv.it_value; + setitimer(ITIMER_REAL, &itv, NULL); +} + + +static void +progressmeter(int flag) +{ + static struct timeval lastupdate; + static off_t lastsize, totalsize; + + struct timeval now, td, tvwait; + off_t abbrevsize; + int elapsed, ratio, barlength, i; + char buf[256]; + + if (flag == -1) { /* first call to progressmeter */ + gettimeofday(&start, (struct timezone *) 0); + lastupdate = start; + lastsize = 0; + totalsize = content_len + beg_range; /* as content_len changes.. */ + } + + gettimeofday(&now, (struct timezone *) 0); + ratio = 100; + if (totalsize != 0 && !chunked) { + /* long long helps to have working ETA even if !LFS */ + ratio = (int) (100 * (unsigned long long)(transferred+beg_range) / totalsize); + ratio = MIN(ratio, 100); + } + + fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio); + + barlength = getttywidth() - 51; + if (barlength > 0 && barlength < sizeof(buf)) { + i = barlength * ratio / 100; + memset(buf, '*', i); + memset(buf + i, ' ', barlength - i); + buf[barlength] = '\0'; + fprintf(stderr, "|%s|", buf); + } + i = 0; + abbrevsize = transferred + beg_range; + while (abbrevsize >= 100000) { + i++; + abbrevsize >>= 10; + } + /* see http://en.wikipedia.org/wiki/Tera */ + fprintf(stderr, "%6d %c%c ", (int)abbrevsize, " KMGTPEZY"[i], i?'B':' '); + + timersub(&now, &lastupdate, &tvwait); + if (transferred > lastsize) { + lastupdate = now; + lastsize = transferred; + if (tvwait.tv_sec >= STALLTIME) + timeradd(&start, &tvwait, &start); + tvwait.tv_sec = 0; + } + timersub(&now, &start, &td); + elapsed = td.tv_sec; + + if (tvwait.tv_sec >= STALLTIME) { + fprintf(stderr, " - stalled -"); + } else { + off_t to_download = totalsize - beg_range; + if (transferred <= 0 || elapsed <= 0 || transferred > to_download || chunked) { + fprintf(stderr, "--:--:-- ETA"); + } else { + /* to_download / (transferred/elapsed) - elapsed: */ + int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed); + /* (long long helps to have working ETA even if !LFS) */ + i = eta % 3600; + fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60); + } + } + + if (flag == -1) { /* first call to progressmeter */ + struct sigaction sa; + sa.sa_handler = updateprogressmeter; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + alarmtimer(1); + } else if (flag == 1) { /* last call to progressmeter */ + alarmtimer(0); + transferred = 0; + putc('\n', stderr); + } +} +#endif + +/* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff, + * much of which was blatantly stolen from openssh. */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: wget.c,v 1.75 2004/10/08 08:27:40 andersen Exp $ + */ diff --git a/networking/zcip.c b/networking/zcip.c new file mode 100644 index 000000000..27e281c93 --- /dev/null +++ b/networking/zcip.c @@ -0,0 +1,546 @@ +/* vi: set sw=4 ts=4: */ +/* + * RFC3927 ZeroConf IPv4 Link-Local addressing + * (see ) + * + * Copyright (C) 2003 by Arthur van Hoff (avh@strangeberry.com) + * Copyright (C) 2004 by David Brownell + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* + * ZCIP just manages the 169.254.*.* addresses. That network is not + * routed at the IP level, though various proxies or bridges can + * certainly be used. Its naming is built over multicast DNS. + */ + +//#define DEBUG + +// TODO: +// - more real-world usage/testing, especially daemon mode +// - kernel packet filters to reduce scheduling noise +// - avoid silent script failures, especially under load... +// - link status monitoring (restart on link-up; stop on link-down) + +#include "busybox.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +struct arp_packet { + struct ether_header hdr; + struct ether_arp arp; +} ATTRIBUTE_PACKED; + +enum { +/* 169.254.0.0 */ + LINKLOCAL_ADDR = 0xa9fe0000, + +/* protocol timeout parameters, specified in seconds */ + PROBE_WAIT = 1, + PROBE_MIN = 1, + PROBE_MAX = 2, + PROBE_NUM = 3, + MAX_CONFLICTS = 10, + RATE_LIMIT_INTERVAL = 60, + ANNOUNCE_WAIT = 2, + ANNOUNCE_NUM = 2, + ANNOUNCE_INTERVAL = 2, + DEFEND_INTERVAL = 10 +}; + +/* States during the configuration process. */ +enum { + PROBE = 0, + RATE_LIMIT_PROBE, + ANNOUNCE, + MONITOR, + DEFEND +}; + +#define VDBG(fmt,args...) \ + do { } while (0) + +static unsigned opts; +#define FOREGROUND (opts & 1) +#define QUIT (opts & 2) + +/** + * Pick a random link local IP address on 169.254/16, except that + * the first and last 256 addresses are reserved. + */ +static void pick(struct in_addr *ip) +{ + unsigned tmp; + + /* use cheaper math than lrand48() mod N */ + do { + tmp = (lrand48() >> 16) & IN_CLASSB_HOST; + } while (tmp > (IN_CLASSB_HOST - 0x0200)); + ip->s_addr = htonl((LINKLOCAL_ADDR + 0x0100) + tmp); +} + +/* TODO: we need a flag to direct bb_[p]error_msg output to stderr. */ + +/** + * Broadcast an ARP packet. + */ +static void arp(int fd, struct sockaddr *saddr, int op, + const struct ether_addr *source_addr, struct in_addr source_ip, + const struct ether_addr *target_addr, struct in_addr target_ip) +{ + struct arp_packet p; + memset(&p, 0, sizeof(p)); + + // ether header + p.hdr.ether_type = htons(ETHERTYPE_ARP); + memcpy(p.hdr.ether_shost, source_addr, ETH_ALEN); + memset(p.hdr.ether_dhost, 0xff, ETH_ALEN); + + // arp request + p.arp.arp_hrd = htons(ARPHRD_ETHER); + p.arp.arp_pro = htons(ETHERTYPE_IP); + p.arp.arp_hln = ETH_ALEN; + p.arp.arp_pln = 4; + p.arp.arp_op = htons(op); + memcpy(&p.arp.arp_sha, source_addr, ETH_ALEN); + memcpy(&p.arp.arp_spa, &source_ip, sizeof (p.arp.arp_spa)); + memcpy(&p.arp.arp_tha, target_addr, ETH_ALEN); + memcpy(&p.arp.arp_tpa, &target_ip, sizeof (p.arp.arp_tpa)); + + // send it + if (sendto(fd, &p, sizeof (p), 0, saddr, sizeof (*saddr)) < 0) { + bb_perror_msg("sendto"); + //return -errno; + } + // Currently all callers ignore errors, that's why returns are + // commented out... + //return 0; +} + +/** + * Run a script. + */ +static int run(char *script, char *arg, char *intf, struct in_addr *ip) +{ + int pid, status; + char *why; + + if(1) { //always true: if (script != NULL) + VDBG("%s run %s %s\n", intf, script, arg); + if (ip != NULL) { + char *addr = inet_ntoa(*ip); + setenv("ip", addr, 1); + bb_info_msg("%s %s %s", arg, intf, addr); + } + + pid = vfork(); + if (pid < 0) { // error + why = "vfork"; + goto bad; + } else if (pid == 0) { // child + execl(script, script, arg, NULL); + bb_perror_msg("execl"); + _exit(EXIT_FAILURE); + } + + if (waitpid(pid, &status, 0) <= 0) { + why = "waitpid"; + goto bad; + } + if (WEXITSTATUS(status) != 0) { + bb_error_msg("script %s failed, exit=%d", + script, WEXITSTATUS(status)); + return -errno; + } + } + return 0; +bad: + status = -errno; + bb_perror_msg("%s %s, %s", arg, intf, why); + return status; +} + + +/** + * Return milliseconds of random delay, up to "secs" seconds. + */ +static unsigned ATTRIBUTE_ALWAYS_INLINE ms_rdelay(unsigned secs) +{ + return lrand48() % (secs * 1000); +} + +/** + * main program + */ + +/* Used to be auto variables on main() stack, but + * most of them were zero-inited. Moving them to bss + * is more space-efficient. + */ +static const struct in_addr null_ip; // = { 0 }; +static const struct ether_addr null_addr; // = { {0, 0, 0, 0, 0, 0} }; + +static struct sockaddr saddr; // memset(0); +static struct in_addr ip; // = { 0 }; +static struct ifreq ifr; //memset(0); + +static char *intf; // = NULL; +static char *script; // = NULL; +static suseconds_t timeout; // = 0; // milliseconds +static unsigned conflicts; // = 0; +static unsigned nprobes; // = 0; +static unsigned nclaims; // = 0; +static int ready; // = 0; +static int verbose; // = 0; +static int state = PROBE; + +int zcip_main(int argc, char *argv[]) +{ + struct ether_addr eth_addr; + char *why; + int fd; + + // parse commandline: prog [options] ifname script + char *r_opt; + opt_complementary = "vv:vf"; // -v accumulates and implies -f + opts = getopt32(argc, argv, "fqr:v", &r_opt, &verbose); + if (!FOREGROUND) { + /* Do it early, before all bb_xx_msg calls */ + logmode = LOGMODE_SYSLOG; + openlog(applet_name, 0, LOG_DAEMON); + } + if (opts & 4) { // -r n.n.n.n + if (inet_aton(r_opt, &ip) == 0 + || (ntohl(ip.s_addr) & IN_CLASSB_NET) != LINKLOCAL_ADDR) { + bb_error_msg_and_die("invalid link address"); + } + } + argc -= optind; + argv += optind; + if (argc != 2) + bb_show_usage(); + intf = argv[0]; + script = argv[1]; + setenv("interface", intf, 1); + + // initialize the interface (modprobe, ifup, etc) + if (run(script, "init", intf, NULL) < 0) + return EXIT_FAILURE; + + // initialize saddr + //memset(&saddr, 0, sizeof (saddr)); + safe_strncpy(saddr.sa_data, intf, sizeof (saddr.sa_data)); + + // open an ARP socket + fd = xsocket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)); + // bind to the interface's ARP socket + xbind(fd, &saddr, sizeof (saddr)); + + // get the interface's ethernet address + //memset(&ifr, 0, sizeof (ifr)); + strncpy(ifr.ifr_name, intf, sizeof (ifr.ifr_name)); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { + bb_perror_msg_and_die("get ethernet address"); + } + memcpy(ð_addr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + // start with some stable ip address, either a function of + // the hardware address or else the last address we used. + // NOTE: the sequence of addresses we try changes only + // depending on when we detect conflicts. + // (SVID 3 bogon: who says that "short" is always 16 bits?) + seed48( (unsigned short*)&ifr.ifr_hwaddr.sa_data ); + if (ip.s_addr == 0) + pick(&ip); + + // FIXME cases to handle: + // - zcip already running! + // - link already has local address... just defend/update + + // daemonize now; don't delay system startup + if (!FOREGROUND) { + setsid(); + xdaemon(0, 0); + bb_info_msg("start, interface %s", intf); + } + + // run the dynamic address negotiation protocol, + // restarting after address conflicts: + // - start with some address we want to try + // - short random delay + // - arp probes to see if another host else uses it + // - arp announcements that we're claiming it + // - use it + // - defend it, within limits + while (1) { + struct pollfd fds[1]; + struct timeval tv1; + struct arp_packet p; + + int source_ip_conflict = 0; + int target_ip_conflict = 0; + + fds[0].fd = fd; + fds[0].events = POLLIN; + fds[0].revents = 0; + + // poll, being ready to adjust current timeout + if (!timeout) { + timeout = ms_rdelay(PROBE_WAIT); + // FIXME setsockopt(fd, SO_ATTACH_FILTER, ...) to + // make the kernel filter out all packets except + // ones we'd care about. + } + // set tv1 to the point in time when we timeout + gettimeofday(&tv1, NULL); + tv1.tv_usec += (timeout % 1000) * 1000; + while (tv1.tv_usec > 1000000) { + tv1.tv_usec -= 1000000; + tv1.tv_sec++; + } + tv1.tv_sec += timeout / 1000; + + VDBG("...wait %ld %s nprobes=%d, nclaims=%d\n", + timeout, intf, nprobes, nclaims); + switch (poll(fds, 1, timeout)) { + + // timeout + case 0: + VDBG("state = %d\n", state); + switch (state) { + case PROBE: + // timeouts in the PROBE state mean no conflicting ARP packets + // have been received, so we can progress through the states + if (nprobes < PROBE_NUM) { + nprobes++; + VDBG("probe/%d %s@%s\n", + nprobes, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, null_ip, + &null_addr, ip); + timeout = PROBE_MIN * 1000; + timeout += ms_rdelay(PROBE_MAX + - PROBE_MIN); + } + else { + // Switch to announce state. + state = ANNOUNCE; + nclaims = 0; + VDBG("announce/%d %s@%s\n", + nclaims, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + timeout = ANNOUNCE_INTERVAL * 1000; + } + break; + case RATE_LIMIT_PROBE: + // timeouts in the RATE_LIMIT_PROBE state mean no conflicting ARP packets + // have been received, so we can move immediately to the announce state + state = ANNOUNCE; + nclaims = 0; + VDBG("announce/%d %s@%s\n", + nclaims, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + timeout = ANNOUNCE_INTERVAL * 1000; + break; + case ANNOUNCE: + // timeouts in the ANNOUNCE state mean no conflicting ARP packets + // have been received, so we can progress through the states + if (nclaims < ANNOUNCE_NUM) { + nclaims++; + VDBG("announce/%d %s@%s\n", + nclaims, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + timeout = ANNOUNCE_INTERVAL * 1000; + } + else { + // Switch to monitor state. + state = MONITOR; + // link is ok to use earlier + // FIXME update filters + run(script, "config", intf, &ip); + ready = 1; + conflicts = 0; + timeout = -1; // Never timeout in the monitor state. + + // NOTE: all other exit paths + // should deconfig ... + if (QUIT) + return EXIT_SUCCESS; + } + break; + case DEFEND: + // We won! No ARP replies, so just go back to monitor. + state = MONITOR; + timeout = -1; + conflicts = 0; + break; + default: + // Invalid, should never happen. Restart the whole protocol. + state = PROBE; + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + break; + } // switch (state) + break; // case 0 (timeout) + // packets arriving + case 1: + // We need to adjust the timeout in case we didn't receive + // a conflicting packet. + if (timeout > 0) { + struct timeval tv2; + + gettimeofday(&tv2, NULL); + if (timercmp(&tv1, &tv2, <)) { + // Current time is greater than the expected timeout time. + // Should never happen. + VDBG("missed an expected timeout\n"); + timeout = 0; + } else { + VDBG("adjusting timeout\n"); + timersub(&tv1, &tv2, &tv1); + timeout = 1000 * tv1.tv_sec + + tv1.tv_usec / 1000; + } + } + + if ((fds[0].revents & POLLIN) == 0) { + if (fds[0].revents & POLLERR) { + // FIXME: links routinely go down; + // this shouldn't necessarily exit. + bb_error_msg("%s: poll error", intf); + if (ready) { + run(script, "deconfig", + intf, &ip); + } + return EXIT_FAILURE; + } + continue; + } + + // read ARP packet + if (recv(fd, &p, sizeof (p), 0) < 0) { + why = "recv"; + goto bad; + } + if (p.hdr.ether_type != htons(ETHERTYPE_ARP)) + continue; + +#ifdef DEBUG + { + struct ether_addr * sha = (struct ether_addr *) p.arp.arp_sha; + struct ether_addr * tha = (struct ether_addr *) p.arp.arp_tha; + struct in_addr * spa = (struct in_addr *) p.arp.arp_spa; + struct in_addr * tpa = (struct in_addr *) p.arp.arp_tpa; + VDBG("%s recv arp type=%d, op=%d,\n", + intf, ntohs(p.hdr.ether_type), + ntohs(p.arp.arp_op)); + VDBG("\tsource=%s %s\n", + ether_ntoa(sha), + inet_ntoa(*spa)); + VDBG("\ttarget=%s %s\n", + ether_ntoa(tha), + inet_ntoa(*tpa)); + } +#endif + if (p.arp.arp_op != htons(ARPOP_REQUEST) + && p.arp.arp_op != htons(ARPOP_REPLY)) + continue; + + if (memcmp(p.arp.arp_spa, &ip.s_addr, sizeof(struct in_addr)) == 0 && + memcmp(ð_addr, &p.arp.arp_sha, ETH_ALEN) != 0) { + source_ip_conflict = 1; + } + if (memcmp(p.arp.arp_tpa, &ip.s_addr, sizeof(struct in_addr)) == 0 && + p.arp.arp_op == htons(ARPOP_REQUEST) && + memcmp(ð_addr, &p.arp.arp_tha, ETH_ALEN) != 0) { + target_ip_conflict = 1; + } + + VDBG("state = %d, source ip conflict = %d, target ip conflict = %d\n", + state, source_ip_conflict, target_ip_conflict); + switch (state) { + case PROBE: + case ANNOUNCE: + // When probing or announcing, check for source IP conflicts + // and other hosts doing ARP probes (target IP conflicts). + if (source_ip_conflict || target_ip_conflict) { + conflicts++; + if (conflicts >= MAX_CONFLICTS) { + VDBG("%s ratelimit\n", intf); + timeout = RATE_LIMIT_INTERVAL * 1000; + state = RATE_LIMIT_PROBE; + } + + // restart the whole protocol + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + } + break; + case MONITOR: + // If a conflict, we try to defend with a single ARP probe. + if (source_ip_conflict) { + VDBG("monitor conflict -- defending\n"); + state = DEFEND; + timeout = DEFEND_INTERVAL * 1000; + arp(fd, &saddr, + ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + } + break; + case DEFEND: + // Well, we tried. Start over (on conflict). + if (source_ip_conflict) { + state = PROBE; + VDBG("defend conflict -- starting over\n"); + ready = 0; + run(script, "deconfig", intf, &ip); + + // restart the whole protocol + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + } + break; + default: + // Invalid, should never happen. Restart the whole protocol. + VDBG("invalid state -- starting over\n"); + state = PROBE; + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + break; + } // switch state + + break; // case 1 (packets arriving) + default: + why = "poll"; + goto bad; + } // switch poll + } +bad: + bb_perror_msg("%s, %s", intf, why); + return EXIT_FAILURE; +} diff --git a/procps/Config.in b/procps/Config.in new file mode 100644 index 000000000..20d5f9bf2 --- /dev/null +++ b/procps/Config.in @@ -0,0 +1,121 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Process Utilities" + +config FREE + bool "free" + default n + help + free displays the total amount of free and used physical and swap + memory in the system, as well as the buffers used by the kernel. + The shared memory column should be ignored; it is obsolete. + +config FUSER + bool "fuser" + default n + help + fuser lists all PIDs (Process IDs) that currently have a given + file open. fuser can also list all PIDs that have a given network + (TCP or UDP) port open. + +config KILL + bool "kill" + default n + help + The command kill sends the specified signal to the specified + process or process group. If no signal is specified, the TERM + signal is sent. + +config KILLALL + bool "killall" + default n + depends on KILL + help + killall sends a signal to all processes running any of the + specified commands. If no signal name is specified, SIGTERM is + sent. + +config KILLALL5 + bool "killall5" + default n + depends on KILL + +config PIDOF + bool "pidof" + default n + help + Pidof finds the process id's (pids) of the named programs. It prints + those id's on the standard output. + +config FEATURE_PIDOF_SINGLE + bool "Enable argument for single shot (-s)" + default n + depends on PIDOF + help + Support argument '-s' for returning only the first pid found. + +config FEATURE_PIDOF_OMIT + bool "Enable argument for omitting pids (-o)" + default n + depends on PIDOF + help + Support argument '-o' for omitting the given pids in output. + The special pid %PPID can be used to name the parent process + of the pidof, in other words the calling shell or shell script. + +config PS + bool "ps" + default n + help + ps gives a snapshot of the current processes. + +config FEATURE_PS_WIDE + bool "Enable argument for wide output (-w)" + default n + depends on PS + help + Support argument 'w' for wide output. + If given once, 132 chars are printed and given more than + one, the length is unlimited. + +config RENICE + bool "renice" + default n + help + Renice alters the scheduling priority of one or more running + processes. + +config BB_SYSCTL + bool "sysctl" + default n + help + Configure kernel parameters at runtime. + +config TOP + bool "top" + default n + help + The top program provides a dynamic real-time view of a running + system. + +config FEATURE_TOP_CPU_USAGE_PERCENTAGE + bool "Support showing CPU usage percentage (add 2k bytes)" + default y + depends on TOP + help + Make top display CPU usage. + +config UPTIME + bool "uptime" + default n + help + uptime gives a one line display of the current time, how long + the system has been running, how many users are currently logged + on, and the system load averages for the past 1, 5, and 15 minutes. + + +endmenu + diff --git a/procps/Kbuild b/procps/Kbuild new file mode 100644 index 000000000..6a9a86637 --- /dev/null +++ b/procps/Kbuild @@ -0,0 +1,16 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_FREE) += free.o +lib-$(CONFIG_KILL) += kill.o +lib-$(CONFIG_PIDOF) += pidof.o +lib-$(CONFIG_PS) += ps.o +lib-$(CONFIG_RENICE) += renice.o +lib-$(CONFIG_BB_SYSCTL) += sysctl.o +lib-$(CONFIG_TOP) += top.o +lib-$(CONFIG_UPTIME) += uptime.o +lib-$(CONFIG_FUSER) += fuser.o diff --git a/procps/free.c b/procps/free.c new file mode 100644 index 000000000..84432e0d1 --- /dev/null +++ b/procps/free.c @@ -0,0 +1,68 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini free implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +/* getopt not needed */ + +#include "busybox.h" + +int free_main(int argc, char **argv) +{ + struct sysinfo info; + sysinfo(&info); + + /* Kernels prior to 2.4.x will return info.mem_unit==0, so cope... */ + if (info.mem_unit==0) { + info.mem_unit=1; + } + if ( info.mem_unit == 1 ) { + info.mem_unit=1024; + + /* TODO: Make all this stuff not overflow when mem >= 4 Gib */ + info.totalram/=info.mem_unit; + info.freeram/=info.mem_unit; +#ifndef __uClinux__ + info.totalswap/=info.mem_unit; + info.freeswap/=info.mem_unit; +#endif + info.sharedram/=info.mem_unit; + info.bufferram/=info.mem_unit; + } else { + info.mem_unit/=1024; + /* TODO: Make all this stuff not overflow when mem >= 4 Gib */ + info.totalram*=info.mem_unit; + info.freeram*=info.mem_unit; +#ifndef __uClinux__ + info.totalswap*=info.mem_unit; + info.freeswap*=info.mem_unit; +#endif + info.sharedram*=info.mem_unit; + info.bufferram*=info.mem_unit; + } + + if (argc > 1 && **(argv + 1) == '-') + bb_show_usage(); + + printf("%6s%13s%13s%13s%13s%13s\n", "", "total", "used", "free", + "shared", "buffers"); + + printf("%6s%13ld%13ld%13ld%13ld%13ld\n", "Mem:", info.totalram, + info.totalram-info.freeram, info.freeram, + info.sharedram, info.bufferram); + +#ifndef __uClinux__ + printf("%6s%13ld%13ld%13ld\n", "Swap:", info.totalswap, + info.totalswap-info.freeswap, info.freeswap); + + printf("%6s%13ld%13ld%13ld\n", "Total:", info.totalram+info.totalswap, + (info.totalram-info.freeram)+(info.totalswap-info.freeswap), + info.freeram+info.freeswap); +#endif + return EXIT_SUCCESS; +} + diff --git a/procps/fuser.c b/procps/fuser.c new file mode 100644 index 000000000..4628cdf5c --- /dev/null +++ b/procps/fuser.c @@ -0,0 +1,366 @@ +/* vi: set sw=4 ts=4: */ +/* + * tiny fuser implementation + * + * Copyright 2004 Tony J. White + * + * May be distributed under the conditions of the + * GNU Library General Public License + */ + +#include "busybox.h" + +#define FUSER_PROC_DIR "/proc" +#define FUSER_MAX_LINE 255 + +#define FUSER_OPT_MOUNT 1 +#define FUSER_OPT_KILL 2 +#define FUSER_OPT_SILENT 4 +#define FUSER_OPT_IP6 8 +#define FUSER_OPT_IP4 16 + +typedef struct inode_list { + ino_t inode; + dev_t dev; + struct inode_list *next; +} inode_list; + +typedef struct pid_list { + pid_t pid; + struct pid_list *next; +} pid_list; + +static int fuser_option(char *option) +{ + int opt = 0; + + if(!(strlen(option))) return 0; + if(option[0] != '-') return 0; + ++option; + while(*option != '\0') { + if(*option == 'm') opt |= FUSER_OPT_MOUNT; + else if(*option == 'k') opt |= FUSER_OPT_KILL; + else if(*option == 's') opt |= FUSER_OPT_SILENT; + else if(*option == '6') opt |= FUSER_OPT_IP6; + else if(*option == '4') opt |= FUSER_OPT_IP4; + else { + bb_error_msg_and_die( + "Unsupported option '%c'", *option); + } + ++option; + } + return opt; +} + +static int fuser_file_to_dev_inode(const char *filename, + dev_t *dev, ino_t *inode) +{ + struct stat f_stat; + if((stat(filename, &f_stat)) < 0) return 0; + *inode = f_stat.st_ino; + *dev = f_stat.st_dev; + return 1; +} + +static int fuser_find_socket_dev(dev_t *dev) +{ + int fd = socket(PF_INET, SOCK_DGRAM,0); + struct stat buf; + + if (fd >= 0 && (fstat(fd, &buf)) == 0) { + *dev = buf.st_dev; + close(fd); + return 1; + } + return 0; +} + +static int fuser_parse_net_arg(const char *filename, + const char **proto, int *port) +{ + char path[sizeof(FUSER_PROC_DIR)+12], tproto[5]; + + if((sscanf(filename, "%d/%4s", port, tproto)) != 2) return 0; + sprintf(path, "%s/net/%s", FUSER_PROC_DIR, tproto); + if((access(path, R_OK)) != 0) return 0; + *proto = xstrdup(tproto); + return 1; +} + +static int fuser_add_pid(pid_list *plist, pid_t pid) +{ + pid_list *curr = NULL, *last = NULL; + + if(plist->pid == 0) plist->pid = pid; + curr = plist; + while(curr != NULL) { + if(curr->pid == pid) return 1; + last = curr; + curr = curr->next; + } + curr = xmalloc(sizeof(pid_list)); + last->next = curr; + curr->pid = pid; + curr->next = NULL; + return 1; +} + +static int fuser_add_inode(inode_list *ilist, dev_t dev, ino_t inode) +{ + inode_list *curr = NULL, *last = NULL; + + if(!ilist->inode && !ilist->dev) { + ilist->dev = dev; + ilist->inode = inode; + } + curr = ilist; + while(curr != NULL) { + if(curr->inode == inode && curr->dev == dev) return 1; + last = curr; + curr = curr->next; + } + curr = xmalloc(sizeof(inode_list)); + last->next = curr; + curr->dev = dev; + curr->inode = inode; + curr->next = NULL; + return 1; +} + +static int fuser_scan_proc_net(int opts, const char *proto, + int port, inode_list *ilist) +{ + char path[sizeof(FUSER_PROC_DIR)+12], line[FUSER_MAX_LINE+1]; + char addr[128]; + ino_t tmp_inode; + dev_t tmp_dev; + long long uint64_inode; + int tmp_port; + FILE *f; + + if(!fuser_find_socket_dev(&tmp_dev)) tmp_dev = 0; + sprintf(path, "%s/net/%s", FUSER_PROC_DIR, proto); + + if (!(f = fopen(path, "r"))) return 0; + while(fgets(line, FUSER_MAX_LINE, f)) { + if(sscanf(line, + "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x %*x:%*x " + "%*x:%*x %*x %*d %*d %llu", + addr, &tmp_port, &uint64_inode) == 3) { + if((strlen(addr) == 8) && + (opts & FUSER_OPT_IP6)) continue; + else if((strlen(addr) > 8) && + (opts & FUSER_OPT_IP4)) continue; + if(tmp_port == port) { + tmp_inode = uint64_inode; + fuser_add_inode(ilist, tmp_dev, tmp_inode); + } + } + + } + fclose(f); + return 1; +} + +static int fuser_search_dev_inode(int opts, inode_list *ilist, + dev_t dev, ino_t inode) +{ + inode_list *curr; + curr = ilist; + + while(curr) { + if((opts & FUSER_OPT_MOUNT) && curr->dev == dev) + return 1; + if(curr->inode == inode && curr->dev == dev) + return 1; + curr = curr->next; + } + return 0; +} + +static int fuser_scan_pid_maps(int opts, const char *fname, pid_t pid, + inode_list *ilist, pid_list *plist) +{ + FILE *file; + char line[FUSER_MAX_LINE + 1]; + int major, minor; + ino_t inode; + long long uint64_inode; + dev_t dev; + + if (!(file = fopen(fname, "r"))) return 0; + while (fgets(line, FUSER_MAX_LINE, file)) { + if(sscanf(line, "%*s %*s %*s %x:%x %llu", + &major, &minor, &uint64_inode) != 3) continue; + inode = uint64_inode; + if(major == 0 && minor == 0 && inode == 0) continue; + dev = makedev(major, minor); + if(fuser_search_dev_inode(opts, ilist, dev, inode)) { + fuser_add_pid(plist, pid); + } + + } + fclose(file); + return 1; +} + +static int fuser_scan_link(int opts, const char *lname, pid_t pid, + inode_list *ilist, pid_list *plist) +{ + ino_t inode; + dev_t dev; + + if(!fuser_file_to_dev_inode(lname, &dev, &inode)) return 0; + if(fuser_search_dev_inode(opts, ilist, dev, inode)) + fuser_add_pid(plist, pid); + return 1; +} + +static int fuser_scan_dir_links(int opts, const char *dname, pid_t pid, + inode_list *ilist, pid_list *plist) +{ + DIR *d; + struct dirent *de; + char *lname; + + if((d = opendir(dname))) { + while((de = readdir(d)) != NULL) { + lname = concat_subpath_file(dname, de->d_name); + if(lname == NULL) + continue; + fuser_scan_link(opts, lname, pid, ilist, plist); + free(lname); + } + closedir(d); + } + else return 0; + return 1; + +} + +static int fuser_scan_proc_pids(int opts, inode_list *ilist, pid_list *plist) +{ + DIR *d; + struct dirent *de; + pid_t pid; + char *dname; + + if(!(d = opendir(FUSER_PROC_DIR))) return 0; + while((de = readdir(d)) != NULL) { + pid = (pid_t)atoi(de->d_name); + if(!pid) continue; + dname = concat_subpath_file(FUSER_PROC_DIR, de->d_name); + if(chdir(dname) < 0) { + free(dname); + continue; + } + free(dname); + fuser_scan_link(opts, "cwd", pid, ilist, plist); + fuser_scan_link(opts, "exe", pid, ilist, plist); + fuser_scan_link(opts, "root", pid, ilist, plist); + fuser_scan_dir_links(opts, "fd", pid, ilist, plist); + fuser_scan_dir_links(opts, "lib", pid, ilist, plist); + fuser_scan_dir_links(opts, "mmap", pid, ilist, plist); + fuser_scan_pid_maps(opts, "maps", pid, ilist, plist); + chdir(".."); + } + closedir(d); + return 1; +} + +static int fuser_print_pid_list(pid_list *plist) +{ + pid_list *curr = plist; + + if(plist == NULL) return 0; + while(curr != NULL) { + if(curr->pid > 0) printf("%d ", curr->pid); + curr = curr->next; + } + puts(""); + return 1; +} + +static int fuser_kill_pid_list(pid_list *plist, int sig) +{ + pid_list *curr = plist; + pid_t mypid = getpid(); + int success = 1; + + if(plist == NULL) return 0; + while(curr != NULL) { + if(curr->pid > 0 && curr->pid != mypid) { + if (kill(curr->pid, sig) != 0) { + bb_perror_msg( + "cannot kill pid '%d'", curr->pid); + success = 0; + } + } + curr = curr->next; + } + return success; +} + +int fuser_main(int argc, char **argv) +{ + int port, i, optn; + int* fni; /* file name indexes of argv */ + int fnic = 0; /* file name index count */ + const char *proto; + static int opt = 0; /* FUSER_OPT_ */ + dev_t dev; + ino_t inode; + pid_list *pids; + inode_list *inodes; + int killsig = SIGTERM; + int success = 1; + + if (argc < 2) + bb_show_usage(); + + fni = xmalloc(sizeof(int)); + for(i=1;i(killsig = get_signum(argv[i]+1))) + killsig = SIGTERM; + } + else { + fni = xrealloc(fni, sizeof(int) * (fnic+2)); + fni[fnic++] = i; + } + } + if(!fnic) return 1; + + inodes = xmalloc(sizeof(inode_list)); + for(i=0;ipid == 0) success = 0; + if(success) { + if(opt & FUSER_OPT_KILL) { + success = fuser_kill_pid_list(pids, killsig); + } + else if(!(opt & FUSER_OPT_SILENT)) { + success = fuser_print_pid_list(pids); + } + } + free(pids); + free(inodes); + /* return 0 on (success == 1) 1 otherwise */ + return (success != 1); +} diff --git a/procps/kill.c b/procps/kill.c new file mode 100644 index 000000000..18121f06f --- /dev/null +++ b/procps/kill.c @@ -0,0 +1,155 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini kill/killall implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens . + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +int kill_main(int argc, char **argv) +{ + char *arg; + pid_t pid; + int signo = SIGTERM, errors = 0, quiet = 0; + const int killall = (ENABLE_KILLALL && applet_name[4] == 'a' + && (!ENABLE_KILLALL5 || applet_name[7] != '5')); + const int killall5 = (ENABLE_KILLALL5 && applet_name[4] == 'a' + && (!ENABLE_KILLALL || applet_name[7] == '5')); + + /* Parse any options */ + argc--; + arg = *++argv; + + if (argc < 1 || arg[0] != '-') { + goto do_it_now; + } + + /* The -l option, which prints out signal names. */ + if (arg[1] == 'l' && arg[2] == '\0') { + const char *name; + if (argc == 1) { + /* Print the whole signal list */ + int col = 0; + for (signo = 1; signo < 32; signo++) { + name = get_signame(signo); + if (isdigit(name[0])) continue; + if (col > 66) { + puts(""); + col = 0; + } + col += printf("%2d) %-6s", signo, name); + } + puts(""); + } else { /* -l */ + while ((arg = *++argv)) { + if (isdigit(arg[0])) { + signo = xatoi_u(arg); + name = get_signame(signo); + } else { + signo = get_signum(arg); + if (signo < 0) + bb_error_msg_and_die("unknown signal '%s'", arg); + name = get_signame(signo); + } + printf("%2d) %s\n", signo, name); + } + } + /* If they specified -l, we are all done */ + return EXIT_SUCCESS; + } + + /* The -q quiet option */ + if (killall && arg[1] == 'q' && arg[2] == '\0') { + quiet = 1; + arg = *++argv; + argc--; + if (argc < 1) bb_show_usage(); + if (arg[0] != '-') goto do_it_now; + } + + /* -SIG */ + signo = get_signum(&arg[1]); + if (signo < 0) + bb_error_msg_and_die("bad signal name '%s'", &arg[1]); + arg = *++argv; + argc--; + +do_it_now: + + if (killall5) { + pid_t sid; + procps_status_t* p = NULL; + +// Cannot happen anyway? We don't TERM ourself, we STOP +// /* kill(-1, sig) on Linux (at least 2.1.x) +// * might send signal to the calling process too */ +// signal(SIGTERM, SIG_IGN); + /* Now stop all processes */ + kill(-1, SIGSTOP); + /* Find out our own session id */ + pid = getpid(); + sid = getsid(pid); + /* Now kill all processes except our session */ + while ((p = procps_scan(p, PSSCAN_PID|PSSCAN_SID))) { + if (p->sid != sid && p->pid != pid && p->pid != 1) + kill(p->pid, signo); + } + /* And let them continue */ + kill(-1, SIGCONT); + return 0; + } + + /* Pid or name required for kill/killall */ + if (argc < 1) + bb_show_usage(); + + if (killall) { + /* Looks like they want to do a killall. Do that */ + pid = getpid(); + while (arg) { + pid_t* pidList; + + pidList = find_pid_by_name(arg); + if (*pidList == 0) { + errors++; + if (!quiet) + bb_error_msg("%s: no process killed", arg); + } else { + pid_t *pl; + + for (pl = pidList; *pl; pl++) { + if (*pl == pid) + continue; + if (kill(*pl, signo) == 0) + continue; + errors++; + if (!quiet) + bb_perror_msg("cannot kill pid %u", (unsigned)*pl); + } + } + free(pidList); + arg = *++argv; + } + return errors; + } + + /* Looks like they want to do a kill. Do that */ + while (arg) { + /* Huh? + if (!isdigit(arg[0]) && arg[0] != '-') + bb_error_msg_and_die("bad pid '%s'", arg); + */ + pid = xatou(arg); + /* FIXME: better overflow check? */ + if (kill(pid, signo) != 0) { + bb_perror_msg("cannot kill pid %u", (unsigned)pid); + errors++; + } + arg = *++argv; + } + return errors; +} diff --git a/procps/pidof.c b/procps/pidof.c new file mode 100644 index 000000000..1d9718945 --- /dev/null +++ b/procps/pidof.c @@ -0,0 +1,90 @@ +/* vi: set sw=4 ts=4: */ +/* + * pidof implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +enum { + USE_FEATURE_PIDOF_SINGLE(OPTBIT_SINGLE,) + USE_FEATURE_PIDOF_OMIT( OPTBIT_OMIT ,) + OPT_SINGLE = USE_FEATURE_PIDOF_SINGLE((1<data, "%PPID", 5)) { + llist_pop(&omits_p); + snprintf(getppid_str, sizeof(getppid_str), "%u", (unsigned)getppid()); + llist_add_to(&omits_p, getppid_str); + } + omits_p = omits_p->link; + } + } +#endif + /* Looks like everything is set to go. */ + while (optind < argc) { + pid_t *pidList; + pid_t *pl; + + /* reverse the pidlist like GNU pidof does. */ + pidList = pidlist_reverse(find_pid_by_name(argv[optind])); + for (pl = pidList; *pl; pl++) { + SKIP_FEATURE_PIDOF_OMIT(const) unsigned omitted = 0; +#if ENABLE_FEATURE_PIDOF_OMIT + if (opt & OPT_OMIT) { + llist_t *omits_p = omits; + while (omits_p) { + if (xatoul(omits_p->data) == *pl) { + omitted = 1; + break; + } else + omits_p = omits_p->link; + } + } +#endif + if (!omitted) { + printf(" %u" + first, (unsigned)*pl); + first = 0; + } + fail = (!ENABLE_FEATURE_PIDOF_OMIT && omitted); + + if (ENABLE_FEATURE_PIDOF_SINGLE && (opt & OPT_SINGLE)) + break; + } + free(pidList); + optind++; + } + putchar('\n'); + +#if ENABLE_FEATURE_PIDOF_OMIT + if (ENABLE_FEATURE_CLEAN_UP) + llist_free(omits, NULL); +#endif + return fail ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/procps/ps.c b/procps/ps.c new file mode 100644 index 000000000..e18bd2a58 --- /dev/null +++ b/procps/ps.c @@ -0,0 +1,388 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini ps implementation(s) for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +#if ENABLE_DESKTOP + +/* Print value to buf, max size+1 chars (including trailing '\0') */ + +void func_user(char *buf, int size, const procps_status_t *ps) +{ + safe_strncpy(buf, get_cached_username(ps->uid), size+1); +} + +void func_comm(char *buf, int size, const procps_status_t *ps) +{ + safe_strncpy(buf, ps->comm, size+1); +} + +void func_args(char *buf, int size, const procps_status_t *ps) +{ + buf[0] = '\0'; + if (ps->cmd) + safe_strncpy(buf, ps->cmd, size+1); + else if (size >= 2) + snprintf(buf, size+1, "[%.*s]", size-2, ps->comm); +} + +void func_pid(char *buf, int size, const procps_status_t *ps) +{ + snprintf(buf, size+1, "%*u", size, ps->pid); +} + +void func_ppid(char *buf, int size, const procps_status_t *ps) +{ + snprintf(buf, size+1, "%*u", size, ps->ppid); +} + +void func_pgid(char *buf, int size, const procps_status_t *ps) +{ + snprintf(buf, size+1, "%*u", size, ps->pgid); +} + +void func_rss(char *buf, int size, const procps_status_t *ps) +{ + char buf5[5]; + smart_ulltoa5( ((unsigned long long)ps->rss) << 10, buf5); + snprintf(buf, size+1, "%.*s", size, buf5); +} + +/* +void func_nice(char *buf, int size, const procps_status_t *ps) +{ + ps->??? +} + +void func_etime(char *buf, int size, const procps_status_t *ps) +{ + elapled time [[dd-]hh:]mm:ss +} + +void func_time(char *buf, int size, const procps_status_t *ps) +{ + cumulative time [[dd-]hh:]mm:ss +} + +void func_pcpu(char *buf, int size, const procps_status_t *ps) +{ +} + +void func_tty(char *buf, int size, const procps_status_t *ps) +{ +} +*/ + +typedef struct { + char name[8]; + const char *header; + void (*f)(char *buf, int size, const procps_status_t *ps); + int ps_flags; + int width; +} ps_out_t; + +static const ps_out_t out_spec[] = { +// Mandated by POSIX: + { "user" ,"USER" ,func_user ,PSSCAN_UIDGID,8 }, + { "comm" ,"COMMAND",func_comm ,PSSCAN_COMM ,16 }, + { "args" ,"COMMAND",func_args ,PSSCAN_CMD|PSSCAN_COMM,256 }, + { "pid" ,"PID" ,func_pid ,PSSCAN_PID ,5 }, + { "ppid" ,"PPID" ,func_ppid ,PSSCAN_PPID ,5 }, + { "pgid" ,"PGID" ,func_pgid ,PSSCAN_PGID ,5 }, +// { "etime" ,"ELAPSED",func_etime ,PSSCAN_ ,sizeof("ELAPSED")-1 }, +// { "group" ,"GROUP" ,func_group ,PSSCAN_UIDGID,sizeof("GROUP" )-1 }, +// { "nice" ,"NI" ,func_nice ,PSSCAN_ ,sizeof("NI" )-1 }, +// { "pcpu" ,"%CPU" ,func_pcpu ,PSSCAN_ ,sizeof("%CPU" )-1 }, +// { "rgroup","RGROUP" ,func_rgroup,PSSCAN_UIDGID,sizeof("RGROUP" )-1 }, +// { "ruser" ,"RUSER" ,func_ruser ,PSSCAN_UIDGID,sizeof("RUSER" )-1 }, +// { "time" ,"TIME" ,func_time ,PSSCAN_ ,sizeof("TIME" )-1 }, +// { "tty" ,"TT" ,func_tty ,PSSCAN_ ,sizeof("TT" )-1 }, +// { "vsz" ,"VSZ" ,func_vsz ,PSSCAN_VSZ ,4 }, +// Not mandated by POSIX: + { "rss" ,"RSS" ,func_rss ,PSSCAN_RSS ,4 }, +}; + +#define VEC_SIZE(v) ( sizeof(v) / sizeof((v)[0]) ) + +static ps_out_t* out; +static int out_cnt; +static int print_header; +static int ps_flags; +static char *buffer; +static unsigned terminal_width; + + +static ps_out_t* new_out_t(void) +{ + int i = out_cnt++; + out = xrealloc(out, out_cnt * sizeof(*out)); + return &out[i]; +} + +static const ps_out_t* find_out_spec(const char *name) +{ + int i; + for (i = 0; i < VEC_SIZE(out_spec); i++) { + if (!strcmp(name, out_spec[i].name)) + return &out_spec[i]; + } + bb_error_msg_and_die("bad -o argument '%s'", name); +} + +static void parse_o(char* opt) +{ + ps_out_t* new; + // POSIX: "-o is blank- or comma-separated list" (FIXME) + char *comma, *equal; + while (1) { + comma = strchr(opt, ','); + equal = strchr(opt, '='); + if (comma && (!equal || equal > comma)) { + *comma = '\0'; + *new_out_t() = *find_out_spec(opt); + *comma = ','; + opt = comma + 1; + continue; + } + break; + } + new = new_out_t(); + if (equal) + *equal = '\0'; + *new = *find_out_spec(opt); + if (equal) { + *equal = '='; + new->header = equal + 1; + // POSIX: the field widths shall be ... at least as wide as + // the header text (default or overridden value). + // If the header text is null, such as -o user=, + // the field width shall be at least as wide as the + // default header text + if (new->header[0]) { + new->width = strlen(new->header); + print_header = 1; + } + } else + print_header = 1; +} + +static void post_process(void) +{ + int i; + int width = 0; + for (i = 0; i < out_cnt; i++) { + ps_flags |= out[i].ps_flags; + if (out[i].header[0]) { + print_header = 1; + } + width += out[i].width + 1; /* "FIELD " */ + } + buffer = xmalloc(width + 1); /* for trailing \0 */ +} + +static void format_header(void) +{ + int i; + ps_out_t* op; + char *p = buffer; + if (!print_header) + return; + i = 0; + if (out_cnt) { + while (1) { + op = &out[i]; + if (++i == out_cnt) /* do not pad last field */ + break; + p += sprintf(p, "%-*s ", op->width, op->header); + } + strcpy(p, op->header); + } + printf("%.*s\n", terminal_width, buffer); +} + +static void format_process(const procps_status_t *ps) +{ + int i, len; + char *p = buffer; + i = 0; + if (out_cnt) while (1) { + out[i].f(p, out[i].width, ps); + // POSIX: Any field need not be meaningful in all + // implementations. In such a case a hyphen ( '-' ) + // should be output in place of the field value. + if (!*p) { + *p++ = '-'; + *p = '\0'; + } + len = strlen(p); + p += len; + len = out[i].width - len + 1; + if (++i == out_cnt) /* do not pad last field */ + break; + while (len--) + *p++ = ' '; + *p = '\0'; + } + printf("%.*s\n", terminal_width, buffer); +} + +/* Cannot be const: parse_o() will choke */ +static char default_o[] = "pid,user" /* TODO: ,vsz,stat */ ",args"; + +int ps_main(int argc, char **argv) +{ + procps_status_t *p; + llist_t* opt_o = NULL; + + // POSIX: + // -a Write information for all processes associated with terminals + // Implementations may omit session leaders from this list + // -A Write information for all processes + // -d Write information for all processes, except session leaders + // -e Write information for all processes (equivalent to -A.) + // -f Generate a full listing + // -l Generate a long listing + // -o col1,col2,col3=header + // Select which columns to distplay + /* We allow (and ignore) most of the above. FIXME */ + opt_complementary = "o::"; + getopt32(argc, argv, "o:aAdefl", &opt_o); + if (opt_o) { + opt_o = rev_llist(opt_o); + do { + parse_o(opt_o->data); + opt_o = opt_o->link; + } while (opt_o); + } else + parse_o(default_o); + post_process(); + + terminal_width = INT_MAX; + if (isatty(1)) { + get_terminal_width_height(1, &terminal_width, NULL); + terminal_width--; + } + format_header(); + + p = NULL; + while ((p = procps_scan(p, ps_flags))) { + format_process(p); + } + + return EXIT_SUCCESS; +} + + +#else /* !ENABLE_DESKTOP */ + + +int ps_main(int argc, char **argv) +{ + procps_status_t *p = NULL; + int i, len; + SKIP_SELINUX(const) int use_selinux = 0; + USE_SELINUX(security_context_t sid = NULL;) +#if !ENABLE_FEATURE_PS_WIDE + enum { terminal_width = 79 }; +#else + int terminal_width; + int w_count = 0; +#endif + +#if ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX +#if ENABLE_FEATURE_PS_WIDE + opt_complementary = "-:ww"; + USE_SELINUX(i =) getopt32(argc, argv, USE_SELINUX("c") "w", &w_count); + /* if w is given once, GNU ps sets the width to 132, + * if w is given more than once, it is "unlimited" + */ + if (w_count) { + terminal_width = (w_count==1) ? 132 : INT_MAX; + } else { + get_terminal_width_height(1, &terminal_width, NULL); + /* Go one less... */ + terminal_width--; + } +#else /* only ENABLE_SELINUX */ + i = getopt32(argc, argv, "c"); +#endif +#if ENABLE_SELINUX + if ((i & 1) && is_selinux_enabled()) + use_selinux = 1; +#endif +#endif /* ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX */ + + if (use_selinux) + puts(" PID Context Stat Command"); + else + puts(" PID Uid VmSize Stat Command"); + + while ((p = procps_scan(p, 0 + | PSSCAN_PID + | PSSCAN_UIDGID + | PSSCAN_STATE + | PSSCAN_RSS + | PSSCAN_CMD + ))) { + char *namecmd = p->cmd; +#if ENABLE_SELINUX + if (use_selinux) { + char sbuf[128]; + len = sizeof(sbuf); + + if (is_selinux_enabled()) { + if (getpidcon(p->pid, &sid) < 0) + sid = NULL; + } + + if (sid) { + /* I assume sid initialized with NULL */ + len = strlen(sid) + 1; + safe_strncpy(sbuf, sid, len); + freecon(sid); + sid = NULL; + } else { + safe_strncpy(sbuf, "unknown", 7); + } + len = printf("%5u %-32s %s ", p->pid, sbuf, p->state); + } else +#endif + { + const char *user = get_cached_username(p->uid); + if (p->rss == 0) + len = printf("%5u %-8s %s ", + p->pid, user, p->state); + else + len = printf("%5u %-8s %6ld %s ", + p->pid, user, p->rss, p->state); + } + + i = terminal_width-len; + + if (namecmd && namecmd[0]) { + if (i < 0) + i = 0; + if (strlen(namecmd) > (size_t)i) + namecmd[i] = 0; + puts(namecmd); + } else { + namecmd = p->comm; + if (i < 2) + i = 2; + if (strlen(namecmd) > ((size_t)i-2)) + namecmd[i-2] = 0; + printf("[%s]\n", namecmd); + } + } + if (ENABLE_FEATURE_CLEAN_UP) + clear_username_cache(); + return EXIT_SUCCESS; +} + +#endif /* ENABLE_DESKTOP */ diff --git a/procps/ps.posix b/procps/ps.posix new file mode 100644 index 000000000..57f4fa8af --- /dev/null +++ b/procps/ps.posix @@ -0,0 +1,175 @@ +This is what POSIX 2003 says about ps: + +By default, ps shall select all processes with the same effective user +ID as the current user and the same controlling terminal as the invoker + +ps [-aA][-defl][-G grouplist][-o format]...[-p proclist][-t termlist] +[-U userlist][-g grouplist][-n namelist][-u userlist] + +-a Write information for all processes associated with terminals. + Implementations may omit session leaders from this list. + +-A Write information for all processes. + +-d Write information for all processes, except session leaders. + +-e Write information for all processes. (Equivalent to -A.) + +-f Generate a full listing. (See the STDOUT section for the con- + tents of a full listing.) + +-g grouplist + Write information for processes whose session leaders are given + in grouplist. The application shall ensure that the grouplist is + a single argument in the form of a or comma-separated + list. + +-G grouplist + Write information for processes whose real group ID numbers are + given in grouplist. The application shall ensure that the grou- + plist is a single argument in the form of a or comma- + separated list. + +-l Generate a long listing. (See STDOUT for the contents of a long + listing.) + +-n namelist + Specify the name of an alternative system namelist file in place + of the default. The name of the default file and the format of a + namelist file are unspecified. + +-o format + Write information according to the format specification given in + format. Multiple -o options can be specified; the format speci- + fication shall be interpreted as the -separated concate- + nation of all the format option-arguments. + +-p proclist + Write information for processes whose process ID numbers are + given in proclist. The application shall ensure that the pro- + clist is a single argument in the form of a or comma- + separated list. + +-t termlist + Write information for processes associated with terminals given + in termlist. The application shall ensure that the termlist is a + single argument in the form of a or comma-separated + list. Terminal identifiers shall be given in an implementation- + defined format. On XSI-conformant systems, they shall be + given in one of two forms: the device's filename (for example, + tty04) or, if the device's filename starts with tty, just the + identifier following the characters tty (for example, "04" ). + +-u userlist + Write information for processes whose user ID numbers or login + names are given in userlist. The application shall ensure that + the userlist is a single argument in the form of a or + comma-separated list. In the listing, the numerical user ID + shall be written unless the -f option is used, in which case the + login name shall be written. + +-U userlist + Write information for processes whose real user ID numbers or + login names are given in userlist. The application shall ensure + that the userlist is a single argument in the form of a + or comma-separated list. + +With the exception of -o format, all of the options shown are used to +select processes. If any are specified, the default list shall be +ignored and ps shall select the processes represented by the inclusive +OR of all the selection-criteria options. + +The -o option allows the output format to be specified under user con- +trol. + +The application shall ensure that the format specification is a list of +names presented as a single argument, or comma-separated. Each +variable has a default header. The default header can be overridden by +appending an equals sign and the new text of the header. The rest of +the characters in the argument shall be used as the header text. The +fields specified shall be written in the order specified on the command +line, and should be arranged in columns in the output. The field widths +shall be selected by the system to be at least as wide as the header +text (default or overridden value). If the header text is null, such as +-o user=, the field width shall be at least as wide as the default +header text. If all header text fields are null, no header line shall +be written. + +ruser The real user ID of the process. This shall be the textual user + ID, if it can be obtained and the field width permits, or a dec- + imal representation otherwise. + +user The effective user ID of the process. This shall be the textual + user ID, if it can be obtained and the field width permits, or a + decimal representation otherwise. + +rgroup The real group ID of the process. This shall be the textual + group ID, if it can be obtained and the field width permits, or + a decimal representation otherwise. + +group The effective group ID of the process. This shall be the textual + group ID, if it can be obtained and the field width permits, or + a decimal representation otherwise. + +pid The decimal value of the process ID. + +ppid The decimal value of the parent process ID. + +pgid The decimal value of the process group ID. + +pcpu The ratio of CPU time used recently to CPU time available in the + same period, expressed as a percentage. The meaning of + "recently" in this context is unspecified. The CPU time avail- + able is determined in an unspecified manner. + +vsz The size of the process in (virtual) memory in 1024 byte units + as a decimal integer. + +nice The decimal value of the nice value of the process; see nice() . + +etime In the POSIX locale, the elapsed time since the process was + started, in the form: [[dd-]hh:]mm:ss + +time In the POSIX locale, the cumulative CPU time of the process in + the form: [dd-]hh:mm:ss + +tty The name of the controlling terminal of the process (if any) in + the same format used by the who utility. + +comm The name of the command being executed ( argv[0] value) as a + string. + +args The command with all its arguments as a string. The implementa- + tion may truncate this value to the field width; it is implemen- + tation-defined whether any further truncation occurs. It is + unspecified whether the string represented is a version of the + argument list as it was passed to the command when it started, + or is a version of the arguments as they may have been modified + by the application. Applications cannot depend on being able to + modify their argument list and having that modification be + reflected in the output of ps. + +Any field need not be meaningful in all implementations. In such a case +a hyphen ( '-' ) should be output in place of the field value. + +Only comm and args shall be allowed to contain s; all others +shall not. + +The following table specifies the default header to be used in the +POSIX locale corresponding to each format specifier. + + Format Specifier Default Header Format Specifier Default Header + args COMMAND ppid PPID + comm COMMAND rgroup RGROUP + etime ELAPSED ruser RUSER + group GROUP time TIME + nice NI tty TT + pcpu %CPU user USER + pgid PGID vsz VSZ + pid PID + +There is no special quoting mechanism for header text. The header text +is the rest of the argument. If multiple header changes are needed, +multiple -o options can be used, such as: + + ps -o "user=User Name" -o pid=Process\ ID diff --git a/procps/renice.c b/procps/renice.c new file mode 100644 index 000000000..08e0dc264 --- /dev/null +++ b/procps/renice.c @@ -0,0 +1,126 @@ +/* vi: set sw=4 ts=4: */ +/* + * renice implementation for busybox + * + * Copyright (C) 2005 Manuel Novoa III + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* Notes: + * Setting an absolute priority was obsoleted in SUSv2 and removed + * in SUSv3. However, the common linux version of renice does + * absolute and not relative. So we'll continue supporting absolute, + * although the stdout logging has been removed since both SUSv2 and + * SUSv3 specify that stdout isn't used. + * + * This version is lenient in that it doesn't require any IDs. The + * options -p, -g, and -u are treated as mode switches for the + * following IDs (if any). Multiple switches are allowed. + */ + +#include "busybox.h" +#include + +void BUG_bad_PRIO_PROCESS(void); +void BUG_bad_PRIO_PGRP(void); +void BUG_bad_PRIO_USER(void); + +int renice_main(int argc, char **argv) +{ + static const char Xetpriority_msg[] = "%cetpriority"; + + int retval = EXIT_SUCCESS; + int which = PRIO_PROCESS; /* Default 'which' value. */ + int use_relative = 0; + int adjustment, new_priority; + unsigned who; + char *arg; + + /* Yes, they are not #defines in glibc 2.4! #if won't work */ + if (PRIO_PROCESS < CHAR_MIN || PRIO_PROCESS > CHAR_MAX) + BUG_bad_PRIO_PROCESS(); + if (PRIO_PGRP < CHAR_MIN || PRIO_PGRP > CHAR_MAX) + BUG_bad_PRIO_PGRP(); + if (PRIO_USER < CHAR_MIN || PRIO_USER > CHAR_MAX) + BUG_bad_PRIO_USER(); + + arg = *++argv; + + /* Check if we are using a relative adjustment. */ + if (arg && arg[0] == '-' && arg[1] == 'n') { + use_relative = 1; + if (!arg[2]) + arg = *++argv; + else + arg += 2; + } + + if (!arg) { /* No args? Then show usage. */ + bb_show_usage(); + } + + /* Get the priority adjustment (absolute or relative). */ + adjustment = xatoi_range(arg, INT_MIN/2, INT_MAX/2); + + while ((arg = *++argv) != NULL) { + /* Check for a mode switch. */ + if (arg[0] == '-' && arg[1]) { + static const char opts[] + = { 'p', 'g', 'u', 0, PRIO_PROCESS, PRIO_PGRP, PRIO_USER }; + const char *p = strchr(opts, arg[1]); + if (p) { + which = p[4]; + if (!arg[2]) + continue; + arg += 2; + } + } + + /* Process an ID arg. */ + if (which == PRIO_USER) { + struct passwd *p; + p = getpwnam(arg); + if (!p) { + bb_error_msg("unknown user: %s", arg); + goto HAD_ERROR; + } + who = p->pw_uid; + } else { + who = bb_strtou(arg, NULL, 10); + if (errno) { + bb_error_msg("bad value: %s", arg); + goto HAD_ERROR; + } + } + + /* Get priority to use, and set it. */ + if (use_relative) { + int old_priority; + + errno = 0; /* Needed for getpriority error detection. */ + old_priority = getpriority(which, who); + if (errno) { + bb_perror_msg(Xetpriority_msg, 'g'); + goto HAD_ERROR; + } + + new_priority = old_priority + adjustment; + } else { + new_priority = adjustment; + } + + if (setpriority(which, who, new_priority) == 0) { + continue; + } + + bb_perror_msg(Xetpriority_msg, 's'); + HAD_ERROR: + retval = EXIT_FAILURE; + } + + /* No need to check for errors outputing to stderr since, if it + * was used, the HAD_ERROR label was reached and retval was set. */ + + return retval; +} diff --git a/procps/sysctl.c b/procps/sysctl.c new file mode 100644 index 000000000..49754489b --- /dev/null +++ b/procps/sysctl.c @@ -0,0 +1,326 @@ +/* vi: set sw=4 ts=4: */ +/* + * Sysctl 1.01 - A utility to read and manipulate the sysctl parameters + * + * Copyright 1999 George Staikos + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Changelog: + * v1.01: + * - added -p to preload values from a file + * v1.01.1 + * - busybox applet aware by + * + */ + +#include "busybox.h" + +/* + * Function Prototypes + */ +static int sysctl_read_setting(const char *setting, int output); +static int sysctl_write_setting(const char *setting, int output); +static int sysctl_preload_file(const char *filename, int output); +static int sysctl_display_all(const char *path, int output, int show_table); + +/* + * Globals... + */ +static const char PROC_PATH[] = "/proc/sys/"; +static const char DEFAULT_PRELOAD[] = "/etc/sysctl.conf"; + +/* error messages */ +static const char ERR_UNKNOWN_PARAMETER[] = "error: Unknown parameter '%s'\n"; +static const char ERR_MALFORMED_SETTING[] = "error: Malformed setting '%s'\n"; +static const char ERR_NO_EQUALS[] = + "error: '%s' must be of the form name=value\n"; +static const char ERR_INVALID_KEY[] = "error: '%s' is an unknown key\n"; +static const char ERR_UNKNOWN_WRITING[] = + "error: unknown error %d setting key '%s'\n"; +static const char ERR_UNKNOWN_READING[] = + "error: unknown error %d reading key '%s'\n"; +static const char ERR_PERMISSION_DENIED[] = + "error: permission denied on key '%s'\n"; +static const char ERR_PRELOAD_FILE[] = + "error: cannot open preload file '%s'\n"; +static const char WARN_BAD_LINE[] = + "warning: %s(%d): invalid syntax, continuing...\n"; + + +static void dwrite_str(int fd, const char *buf) +{ + write(fd, buf, strlen(buf)); +} + +/* + * sysctl_main()... + */ +int sysctl_main(int argc, char **argv) +{ + int retval = 0; + int output = 1; + int write_mode = 0; + int switches_allowed = 1; + + if (argc < 2) + bb_show_usage(); + + argv++; + + for (; argv && *argv && **argv; argv++) { + if (switches_allowed && **argv == '-') { /* we have a switch */ + switch ((*argv)[1]) { + case 'n': + output = 0; + break; + case 'w': + write_mode = 1; + switches_allowed = 0; + break; + case 'p': + argv++; + return + sysctl_preload_file(((argv && *argv + && **argv) ? *argv : + DEFAULT_PRELOAD), output); + case 'a': + case 'A': + switches_allowed = 0; + return sysctl_display_all(PROC_PATH, output, + ((*argv)[1] == 'a') ? 0 : 1); + case 'h': + case '?': + bb_show_usage(); + default: + bb_error_msg(ERR_UNKNOWN_PARAMETER, *argv); + bb_show_usage(); + } + } else { + switches_allowed = 0; + if (write_mode) + retval = sysctl_write_setting(*argv, output); + else + sysctl_read_setting(*argv, output); + } + } + return retval; +} /* end sysctl_main() */ + + + +/* + * sysctl_preload_file + * preload the sysctl's from a conf file + * - we parse the file and then reform it (strip out whitespace) + */ +#define PRELOAD_BUF 256 + +int sysctl_preload_file(const char *filename, int output) +{ + int lineno = 0; + char oneline[PRELOAD_BUF]; + char buffer[PRELOAD_BUF]; + char *name, *value, *ptr; + FILE *fp = NULL; + + if (!filename || ((fp = fopen(filename, "r")) == NULL)) { + bb_error_msg_and_die(ERR_PRELOAD_FILE, filename); + } + + while (fgets(oneline, sizeof(oneline) - 1, fp)) { + oneline[sizeof(oneline) - 1] = '\0'; + lineno++; + trim(oneline); + ptr = (char *) oneline; + + if (*ptr == '#' || *ptr == ';') + continue; + + if (strlen(ptr) < 2) + continue; + + name = strtok(ptr, "="); + if (!name || !*name) { + bb_error_msg(WARN_BAD_LINE, filename, lineno); + continue; + } + + trim(name); + + value = strtok(NULL, "\n\r"); + if (!value || !*value) { + bb_error_msg(WARN_BAD_LINE, filename, lineno); + continue; + } + + while ((*value == ' ' || *value == '\t') && *value != 0) + value++; + /* safe because sizeof(oneline) == sizeof(buffer) */ + sprintf(buffer, "%s=%s", name, value); + sysctl_write_setting(buffer, output); + } + fclose(fp); + return 0; +} /* end sysctl_preload_file() */ + + +/* + * Write a single sysctl setting + */ +int sysctl_write_setting(const char *setting, int output) +{ + int retval = 0; + const char *name = setting; + const char *value; + const char *equals; + char *tmpname, *outname, *cptr; + int fd = -1; + + if (!name) /* probably dont' want to display this err */ + return 0; + + if (!(equals = strchr(setting, '='))) { + bb_error_msg(ERR_NO_EQUALS, setting); + return -1; + } + + value = equals + sizeof(char); /* point to the value in name=value */ + + if (!*name || !*value || name == equals) { + bb_error_msg(ERR_MALFORMED_SETTING, setting); + return -2; + } + + tmpname = xasprintf("%s%.*s", PROC_PATH, (int)(equals - name), name); + outname = xstrdup(tmpname + strlen(PROC_PATH)); + + while ((cptr = strchr(tmpname, '.')) != NULL) + *cptr = '/'; + + while ((cptr = strchr(outname, '/')) != NULL) + *cptr = '.'; + + if ((fd = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) { + switch (errno) { + case ENOENT: + bb_error_msg(ERR_INVALID_KEY, outname); + break; + case EACCES: + bb_perror_msg(ERR_PERMISSION_DENIED, outname); + break; + default: + bb_error_msg(ERR_UNKNOWN_WRITING, errno, outname); + break; + } + retval = -1; + } else { + dwrite_str(fd, value); + close(fd); + if (output) { + dwrite_str(STDOUT_FILENO, outname); + dwrite_str(STDOUT_FILENO, " = "); + } + dwrite_str(STDOUT_FILENO, value); + dwrite_str(STDOUT_FILENO, "\n"); + } + + /* cleanup */ + free(tmpname); + free(outname); + return retval; +} /* end sysctl_write_setting() */ + + +/* + * Read a sysctl setting + * + */ +int sysctl_read_setting(const char *setting, int output) +{ + int retval = 0; + char *tmpname, *outname, *cptr; + char inbuf[1025]; + const char *name = setting; + FILE *fp; + + if (!setting || !*setting) + bb_error_msg(ERR_INVALID_KEY, setting); + + tmpname = concat_path_file(PROC_PATH, name); + outname = xstrdup(tmpname + strlen(PROC_PATH)); + + while ((cptr = strchr(tmpname, '.')) != NULL) + *cptr = '/'; + while ((cptr = strchr(outname, '/')) != NULL) + *cptr = '.'; + + if ((fp = fopen(tmpname, "r")) == NULL) { + switch (errno) { + case ENOENT: + bb_error_msg(ERR_INVALID_KEY, outname); + break; + case EACCES: + bb_error_msg(ERR_PERMISSION_DENIED, outname); + break; + default: + bb_error_msg(ERR_UNKNOWN_READING, errno, outname); + break; + } + retval = -1; + } else { + while (fgets(inbuf, sizeof(inbuf) - 1, fp)) { + if (output) { + dwrite_str(STDOUT_FILENO, outname); + dwrite_str(STDOUT_FILENO, " = "); + } + dwrite_str(STDOUT_FILENO, inbuf); + } + fclose(fp); + } + + free(tmpname); + free(outname); + return retval; +} /* end sysctl_read_setting() */ + + + +/* + * Display all the sysctl settings + * + */ +int sysctl_display_all(const char *path, int output, int show_table) +{ + int retval = 0; + int retval2; + DIR *dp; + struct dirent *de; + char *tmpdir; + struct stat ts; + + if (!(dp = opendir(path))) { + retval = -1; + } else { + while ((de = readdir(dp)) != NULL) { + tmpdir = concat_subpath_file(path, de->d_name); + if(tmpdir == NULL) + continue; + if ((retval2 = stat(tmpdir, &ts)) != 0) + bb_perror_msg(tmpdir); + else { + if (S_ISDIR(ts.st_mode)) { + sysctl_display_all(tmpdir, output, show_table); + } else + retval |= + sysctl_read_setting(tmpdir + strlen(PROC_PATH), + output); + + } + free(tmpdir); + } /* end while */ + closedir(dp); + } + + return retval; +} /* end sysctl_display_all() */ diff --git a/procps/top.c b/procps/top.c new file mode 100644 index 000000000..8d732d4b2 --- /dev/null +++ b/procps/top.c @@ -0,0 +1,566 @@ +/* vi: set sw=4 ts=4: */ +/* + * A tiny 'top' utility. + * + * This is written specifically for the linux /proc//stat(m) + * files format. + + * This reads the PIDs of all processes and their status and shows + * the status of processes (first ones that fit to screen) at given + * intervals. + * + * NOTES: + * - At startup this changes to /proc, all the reads are then + * relative to that. + * + * (C) Eero Tamminen + * + * Rewritten by Vladimir Oleynik (C) 2002 + */ + +/* Original code Copyrights */ +/* + * Copyright (c) 1992 Branko Lankester + * Copyright (c) 1992 Roger Binns + * Copyright (C) 1994-1996 Charles L. Blake. + * Copyright (C) 1992-1998 Michael K. Johnson + * May be distributed under the conditions of the + * GNU Library General Public License + */ + +#include "busybox.h" + + +typedef struct { + unsigned long rss; +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + unsigned long ticks; + unsigned pcpu; /* delta of ticks */ +#endif + unsigned pid, ppid; + unsigned uid; + char state[4]; + char comm[COMM_LEN]; +} top_status_t; +static top_status_t *top; +static int ntop; +/* This structure stores some critical information from one frame to + the next. Used for finding deltas. */ +struct save_hist { + unsigned long ticks; + unsigned pid; +}; +static struct save_hist *prev_hist; +static int prev_hist_count; +/* static int hist_iterations; */ +static unsigned total_pcpu; +/* static unsigned long total_rss; */ + + +#define OPT_BATCH_MODE (option_mask32 & 0x4) + +#if ENABLE_FEATURE_USE_TERMIOS +static int pid_sort(top_status_t *P, top_status_t *Q) +{ + /* Buggy wrt pids with high bit set */ + /* (linux pids are in [1..2^15-1]) */ + return (Q->pid - P->pid); +} +#endif + +static int mem_sort(top_status_t *P, top_status_t *Q) +{ + /* We want to avoid unsigned->signed and truncation errors */ + if (Q->rss < P->rss) return -1; + return Q->rss != P->rss; /* 0 if ==, 1 if > */ +} + + +typedef int (*cmp_funcp)(top_status_t *P, top_status_t *Q); + +#if !ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + +static cmp_funcp sort_function; + +#else + +enum { SORT_DEPTH = 3 }; + +static cmp_funcp sort_function[SORT_DEPTH]; + +static int pcpu_sort(top_status_t *P, top_status_t *Q) +{ + /* Buggy wrt ticks with high bit set */ + /* Affects only processes for which ticks overflow */ + return (int)Q->pcpu - (int)P->pcpu; +} + +static int time_sort(top_status_t *P, top_status_t *Q) +{ + /* We want to avoid unsigned->signed and truncation errors */ + if (Q->ticks < P->ticks) return -1; + return Q->ticks != P->ticks; /* 0 if ==, 1 if > */ +} + +static int mult_lvl_cmp(void* a, void* b) { + int i, cmp_val; + + for (i = 0; i < SORT_DEPTH; i++) { + cmp_val = (*sort_function[i])(a, b); + if (cmp_val != 0) + return cmp_val; + } + return 0; +} + + +typedef struct { + unsigned long long usr,nic,sys,idle,iowait,irq,softirq,steal; + unsigned long long total; + unsigned long long busy; +} jiffy_counts_t; +static jiffy_counts_t jif, prev_jif; +static void get_jiffy_counts(void) +{ + FILE* fp = xfopen("stat", "r"); + prev_jif = jif; + if (fscanf(fp, "cpu %lld %lld %lld %lld %lld %lld %lld %lld", + &jif.usr,&jif.nic,&jif.sys,&jif.idle, + &jif.iowait,&jif.irq,&jif.softirq,&jif.steal) < 4) { + bb_error_msg_and_die("failed to read /proc/stat"); + } + fclose(fp); + jif.total = jif.usr + jif.nic + jif.sys + jif.idle + + jif.iowait + jif.irq + jif.softirq + jif.steal; + /* procps 2.x does not count iowait as busy time */ + jif.busy = jif.total - jif.idle - jif.iowait; +} + + +static void do_stats(void) +{ + top_status_t *cur; + pid_t pid; + int i, last_i, n; + struct save_hist *new_hist; + + get_jiffy_counts(); + total_pcpu = 0; + /* total_rss = 0; */ + new_hist = xmalloc(sizeof(struct save_hist)*ntop); + /* + * Make a pass through the data to get stats. + */ + /* hist_iterations = 0; */ + i = 0; + for (n = 0; n < ntop; n++) { + cur = top + n; + + /* + * Calculate time in cur process. Time is sum of user time + * and system time + */ + pid = cur->pid; + new_hist[n].ticks = cur->ticks; + new_hist[n].pid = pid; + + /* find matching entry from previous pass */ + cur->pcpu = 0; + /* do not start at index 0, continue at last used one + * (brought hist_iterations from ~14000 down to 172) */ + last_i = i; + if (prev_hist_count) do { + if (prev_hist[i].pid == pid) { + cur->pcpu = cur->ticks - prev_hist[i].ticks; + total_pcpu += cur->pcpu; + break; + } + i = (i+1) % prev_hist_count; + /* hist_iterations++; */ + } while (i != last_i); + /* total_rss += cur->rss; */ + } + + /* + * Save cur frame's information. + */ + free(prev_hist); + prev_hist = new_hist; + prev_hist_count = ntop; +} +#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ + + +/* display generic info (meminfo / loadavg) */ +static unsigned long display_generic(int scr_width) +{ + FILE *fp; + char buf[80]; + char scrbuf[80]; + char *end; + unsigned long total, used, mfree, shared, buffers, cached; + unsigned int needs_conversion = 1; + + /* read memory info */ + fp = xfopen("meminfo", "r"); + + /* + * Old kernels (such as 2.4.x) had a nice summary of memory info that + * we could parse, however this is gone entirely in 2.6. Try parsing + * the old way first, and if that fails, parse each field manually. + * + * First, we read in the first line. Old kernels will have bogus + * strings we don't care about, whereas new kernels will start right + * out with MemTotal: + * -- PFM. + */ + if (fscanf(fp, "MemTotal: %lu %s\n", &total, buf) != 2) { + fgets(buf, sizeof(buf), fp); /* skip first line */ + + fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu", + &total, &used, &mfree, &shared, &buffers, &cached); + } else { + /* + * Revert to manual parsing, which incidentally already has the + * sizes in kilobytes. This should be safe for both 2.4 and + * 2.6. + */ + needs_conversion = 0; + + fscanf(fp, "MemFree: %lu %s\n", &mfree, buf); + + /* + * MemShared: is no longer present in 2.6. Report this as 0, + * to maintain consistent behavior with normal procps. + */ + if (fscanf(fp, "MemShared: %lu %s\n", &shared, buf) != 2) + shared = 0; + + fscanf(fp, "Buffers: %lu %s\n", &buffers, buf); + fscanf(fp, "Cached: %lu %s\n", &cached, buf); + + used = total - mfree; + } + fclose(fp); + + /* read load average as a string */ + buf[0] = '\0'; + open_read_close("loadavg", buf, sizeof(buf)); + end = strchr(buf, ' '); + if (end) end = strchr(end+1, ' '); + if (end) end = strchr(end+1, ' '); + if (end) *end = '\0'; + + if (needs_conversion) { + /* convert to kilobytes */ + used /= 1024; + mfree /= 1024; + shared /= 1024; + buffers /= 1024; + cached /= 1024; + total /= 1024; + } + + /* output memory info and load average */ + /* clear screen & go to top */ + if (scr_width > sizeof(scrbuf)) + scr_width = sizeof(scrbuf); + snprintf(scrbuf, scr_width, + "Mem: %ldK used, %ldK free, %ldK shrd, %ldK buff, %ldK cached", + used, mfree, shared, buffers, cached); + + printf(OPT_BATCH_MODE ? "%s\n" : "\e[H\e[J%s\n", scrbuf); + + snprintf(scrbuf, scr_width, "Load average: %s", buf); + printf("%s\n", scrbuf); + + return total; +} + + +/* display process statuses */ +static void display_status(int count, int scr_width) +{ + enum { + bits_per_int = sizeof(int)*8 + }; + + top_status_t *s = top; + char rss_str_buf[8]; + unsigned long total_memory = display_generic(scr_width); /* or use total_rss? */ + unsigned pmem_shift, pmem_scale; + +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + unsigned pcpu_shift, pcpu_scale; + unsigned busy_jifs; + + /* what info of the processes is shown */ + printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, + " PID USER STATUS RSS PPID %CPU %MEM COMMAND"); +#define MIN_WIDTH \ + sizeof( " PID USER STATUS RSS PPID %CPU %MEM C") +#else + printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, + " PID USER STATUS RSS PPID %MEM COMMAND"); +#define MIN_WIDTH \ + sizeof( " PID USER STATUS RSS PPID %MEM C") +#endif + + /* + * MEM% = s->rss/MemTotal + */ + pmem_shift = bits_per_int-11; + pmem_scale = 1000*(1U<<(bits_per_int-11)) / total_memory; + /* s->rss is in kb. we want (s->rss * pmem_scale) to never overflow */ + while (pmem_scale >= 512) { + pmem_scale /= 4; + pmem_shift -= 2; + } +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + busy_jifs = jif.busy - prev_jif.busy; + /* This happens if there were lots of short-lived processes + * between two top updates (e.g. compilation) */ + if (total_pcpu < busy_jifs) total_pcpu = busy_jifs; + + /* + * CPU% = s->pcpu/sum(s->pcpu) * busy_cpu_ticks/total_cpu_ticks + * (pcpu is delta of sys+user time between samples) + */ + /* (jif.xxx - prev_jif.xxx) and s->pcpu are + * in 0..~64000 range (HZ*update_interval). + * we assume that unsigned is at least 32-bit. + */ + pcpu_shift = 6; + pcpu_scale = (1000*64*(uint16_t)busy_jifs ? : 1); + while (pcpu_scale < (1U<<(bits_per_int-2))) { + pcpu_scale *= 4; + pcpu_shift += 2; + } + pcpu_scale /= ( (uint16_t)(jif.total-prev_jif.total)*total_pcpu ? : 1); + /* we want (s->pcpu * pcpu_scale) to never overflow */ + while (pcpu_scale >= 1024) { + pcpu_scale /= 4; + pcpu_shift -= 2; + } + /* printf(" pmem_scale=%u pcpu_scale=%u ", pmem_scale, pcpu_scale); */ +#endif + while (count-- > 0) { + div_t pmem = div((s->rss*pmem_scale) >> pmem_shift, 10); + int col = scr_width+1; + USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE(div_t pcpu;) + + if (s->rss >= 100*1024) + sprintf(rss_str_buf, "%6ldM", s->rss/1024); + else + sprintf(rss_str_buf, "%7ld", s->rss); + USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE( + pcpu = div((s->pcpu*pcpu_scale) >> pcpu_shift, 10); + ) + col -= printf("\n%5u %-8s %s " + "%s%6u" + USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE("%3u.%c") + "%3u.%c ", + s->pid, get_cached_username(s->uid), s->state, + rss_str_buf, s->ppid, + USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE(pcpu.quot, '0'+pcpu.rem,) + pmem.quot, '0'+pmem.rem); + if (col > 0) + printf("%.*s", col, s->comm); + /* printf(" %d/%d %lld/%lld", s->pcpu, total_pcpu, + jif.busy - prev_jif.busy, jif.total - prev_jif.total); */ + s++; + } + /* printf(" %d", hist_iterations); */ + putchar(OPT_BATCH_MODE ? '\n' : '\r'); + fflush(stdout); +} + + +static void clearmems(void) +{ + clear_username_cache(); + free(top); + top = 0; + ntop = 0; +} + + +#if ENABLE_FEATURE_USE_TERMIOS +#include +#include + +static struct termios initial_settings; + +static void reset_term(void) +{ + tcsetattr(0, TCSANOW, (void *) &initial_settings); +#if ENABLE_FEATURE_CLEAN_UP + clearmems(); +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + free(prev_hist); +#endif +#endif /* FEATURE_CLEAN_UP */ +} + +static void sig_catcher(int sig ATTRIBUTE_UNUSED) +{ + reset_term(); + exit(1); +} +#endif /* FEATURE_USE_TERMIOS */ + + +int top_main(int argc, char **argv) +{ + int count, lines, col; + unsigned interval = 5; /* default update rate is 5 seconds */ + unsigned iterations = UINT_MAX; /* 2^32 iterations by default :) */ + char *sinterval, *siterations; +#if ENABLE_FEATURE_USE_TERMIOS + struct termios new_settings; + struct timeval tv; + fd_set readfds; + unsigned char c; +#endif /* FEATURE_USE_TERMIOS */ + + /* do normal option parsing */ + interval = 5; + opt_complementary = "-"; + getopt32(argc, argv, "d:n:b", &sinterval, &siterations); + if (option_mask32 & 0x1) interval = xatou(sinterval); // -d + if (option_mask32 & 0x2) iterations = xatou(siterations); // -n + //if (option_mask32 & 0x4) // -b + + /* change to /proc */ + xchdir("/proc"); +#if ENABLE_FEATURE_USE_TERMIOS + tcgetattr(0, (void *) &initial_settings); + memcpy(&new_settings, &initial_settings, sizeof(struct termios)); + /* unbuffered input, turn off echo */ + new_settings.c_lflag &= ~(ISIG | ICANON | ECHO | ECHONL); + + signal(SIGTERM, sig_catcher); + signal(SIGINT, sig_catcher); + tcsetattr(0, TCSANOW, (void *) &new_settings); + atexit(reset_term); +#endif /* FEATURE_USE_TERMIOS */ + +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + sort_function[0] = pcpu_sort; + sort_function[1] = mem_sort; + sort_function[2] = time_sort; +#else + sort_function = mem_sort; +#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ + + while (1) { + procps_status_t *p = NULL; + + /* Default to 25 lines - 5 lines for status */ + lines = 24 - 3; + col = 79; +#if ENABLE_FEATURE_USE_TERMIOS + get_terminal_width_height(0, &col, &lines); + if (lines < 5 || col < MIN_WIDTH) { + sleep(interval); + continue; + } + lines -= 3; +#endif /* FEATURE_USE_TERMIOS */ + + /* read process IDs & status for all the processes */ + while ((p = procps_scan(p, 0 + | PSSCAN_PID + | PSSCAN_PPID + | PSSCAN_RSS + | PSSCAN_STIME + | PSSCAN_UTIME + | PSSCAN_STATE + | PSSCAN_COMM + | PSSCAN_SID + | PSSCAN_UIDGID + ))) { + int n = ntop; + top = xrealloc(top, (++ntop)*sizeof(top_status_t)); + top[n].pid = p->pid; + top[n].ppid = p->ppid; + top[n].rss = p->rss; + top[n].ticks = p->stime + p->utime; + top[n].uid = p->uid; + strcpy(top[n].state, p->state); + strcpy(top[n].comm, p->comm); + } + if (ntop == 0) { + bb_error_msg_and_die("can't find process info in /proc"); + } +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + if (!prev_hist_count) { + do_stats(); + sleep(1); + clearmems(); + continue; + } + do_stats(); + qsort(top, ntop, sizeof(top_status_t), (void*)mult_lvl_cmp); +#else + qsort(top, ntop, sizeof(top_status_t), (void*)sort_function); +#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ + count = lines; + if (OPT_BATCH_MODE || count > ntop) { + count = ntop; + } + /* show status for each of the processes */ + display_status(count, col); +#if ENABLE_FEATURE_USE_TERMIOS + tv.tv_sec = interval; + tv.tv_usec = 0; + FD_ZERO(&readfds); + FD_SET(0, &readfds); + select(1, &readfds, NULL, NULL, &tv); + if (FD_ISSET(0, &readfds)) { + if (read(0, &c, 1) <= 0) { /* signal */ + return EXIT_FAILURE; + } + if (c == 'q' || c == initial_settings.c_cc[VINTR]) + break; + if (c == 'M') { +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + sort_function[0] = mem_sort; + sort_function[1] = pcpu_sort; + sort_function[2] = time_sort; +#else + sort_function = mem_sort; +#endif + } +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + if (c == 'P') { + sort_function[0] = pcpu_sort; + sort_function[1] = mem_sort; + sort_function[2] = time_sort; + } + if (c == 'T') { + sort_function[0] = time_sort; + sort_function[1] = mem_sort; + sort_function[2] = pcpu_sort; + } +#endif + if (c == 'N') { +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + sort_function[0] = pid_sort; +#else + sort_function = pid_sort; +#endif + } + } + if (!--iterations) + break; +#else + sleep(interval); +#endif /* FEATURE_USE_TERMIOS */ + clearmems(); + } + if (ENABLE_FEATURE_CLEAN_UP) + clearmems(); + putchar('\n'); + return EXIT_SUCCESS; +} diff --git a/procps/uptime.c b/procps/uptime.c new file mode 100644 index 000000000..37c9d449a --- /dev/null +++ b/procps/uptime.c @@ -0,0 +1,59 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini uptime implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +/* This version of uptime doesn't display the number of users on the system, + * since busybox init doesn't mess with utmp. For folks using utmp that are + * just dying to have # of users reported, feel free to write it as some type + * of CONFIG_FEATURE_UTMP_SUPPORT #define + */ + +/* getopt not needed */ + +#include "busybox.h" + +#ifndef FSHIFT +# define FSHIFT 16 /* nr of bits of precision */ +#endif +#define FIXED_1 (1<> FSHIFT) +#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100) + + +int uptime_main(int argc, char **argv) +{ + int updays, uphours, upminutes; + struct sysinfo info; + struct tm *current_time; + time_t current_secs; + + time(¤t_secs); + current_time = localtime(¤t_secs); + + sysinfo(&info); + + printf(" %02d:%02d:%02d up ", + current_time->tm_hour, current_time->tm_min, current_time->tm_sec); + updays = (int) info.uptime / (60*60*24); + if (updays) + printf("%d day%s, ", updays, (updays != 1) ? "s" : ""); + upminutes = (int) info.uptime / 60; + uphours = (upminutes / 60) % 24; + upminutes %= 60; + if(uphours) + printf("%2d:%02d, ", uphours, upminutes); + else + printf("%d min, ", upminutes); + + printf("load average: %ld.%02ld, %ld.%02ld, %ld.%02ld\n", + LOAD_INT(info.loads[0]), LOAD_FRAC(info.loads[0]), + LOAD_INT(info.loads[1]), LOAD_FRAC(info.loads[1]), + LOAD_INT(info.loads[2]), LOAD_FRAC(info.loads[2])); + + return EXIT_SUCCESS; +} diff --git a/runit/Config.in b/runit/Config.in new file mode 100644 index 000000000..8a7deea72 --- /dev/null +++ b/runit/Config.in @@ -0,0 +1,66 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Runit Utilities" + +config RUNSV + bool "runsv" + default n + help + runsv starts and monitors a service and optionally an appendant log + service. + +config RUNSVDIR + bool "runsvdir" + default n + help + runsvdir starts a runsv process for each subdirectory, or symlink to + a directory, in the services directory dir, up to a limit of 1000 + subdirectories, and restarts a runsv process if it terminates. + +config SV + bool "sv" + default n + help + sv reports the current status and controls the state of services + monitored by the runsv supervisor. + +config SVLOGD + bool "svlogd" + default n + help + svlogd continuously reads log data from its standard input, optionally + filters log messages, and writes the data to one or more automatically + rotated logs. + +config CHPST + bool "chpst" + default n + help + chpst changes the process state according to the given options, and + execs specified program. + +config SETUIDGID + bool "setuidgid" + help + Sets soft resource limits as specified by options + +config ENVUIDGID + bool "envuidgid" + help + Sets $UID to account's uid and $GID to account's gid + +config ENVDIR + bool "envdir" + help + Sets various environment variables as specified by files + in the given directory + +config SOFTLIMIT + bool "softlimit" + help + Sets soft resource limits as specified by options + +endmenu diff --git a/runit/Kbuild b/runit/Kbuild new file mode 100644 index 000000000..ad1706cb6 --- /dev/null +++ b/runit/Kbuild @@ -0,0 +1,12 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_RUNSV) += runsv.o runit_lib.o +lib-$(CONFIG_RUNSVDIR) += runsvdir.o runit_lib.o +lib-$(CONFIG_SV) += sv.o runit_lib.o +lib-$(CONFIG_SVLOGD) += svlogd.o runit_lib.o +lib-$(CONFIG_CHPST) += chpst.o diff --git a/runit/chpst.c b/runit/chpst.c new file mode 100644 index 000000000..f8e63031f --- /dev/null +++ b/runit/chpst.c @@ -0,0 +1,373 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denis Vlasenko */ +/* Dependencies on runit_lib.c removed */ + +#include "busybox.h" + +#include + +// Must match constants in chpst_main! +#define OPT_verbose (option_mask32 & 0x2000) +#define OPT_pgrp (option_mask32 & 0x4000) +#define OPT_nostdin (option_mask32 & 0x8000) +#define OPT_nostdout (option_mask32 & 0x10000) +#define OPT_nostderr (option_mask32 & 0x20000) + +static char *set_user; +static char *env_user; +static const char *env_dir; +static long limitd = -2; +static long limits = -2; +static long limitl = -2; +static long limita = -2; +static long limito = -2; +static long limitp = -2; +static long limitf = -2; +static long limitc = -2; +static long limitr = -2; +static long limitt = -2; +static int nicelvl; +static const char *root; + +static void suidgid(char *user) +{ + struct bb_uidgid_t ugid; + + if (!uidgid_get(&ugid, user)) { + bb_error_msg_and_die("unknown user/group: %s", user); + } + if (setgroups(1, &ugid.gid) == -1) + bb_perror_msg_and_die("setgroups"); + xsetgid(ugid.gid); + xsetuid(ugid.uid); +} + +static void euidgid(char *user) +{ + struct bb_uidgid_t ugid; + + if (!uidgid_get(&ugid, user)) { + bb_error_msg_and_die("unknown user/group: %s", user); + } + xsetenv("GID", utoa(ugid.gid)); + xsetenv("UID", utoa(ugid.uid)); +} + +static void edir(const char *directory_name) +{ + int wdir; + DIR *dir; + struct dirent *d; + int fd; + + wdir = xopen(".", O_RDONLY | O_NDELAY); + xchdir(directory_name); + dir = opendir("."); + if (!dir) + bb_perror_msg_and_die("opendir %s", directory_name); + for (;;) { + errno = 0; + d = readdir(dir); + if (!d) { + if (errno) + bb_perror_msg_and_die("readdir %s", + directory_name); + break; + } + if (d->d_name[0] == '.') continue; + fd = open(d->d_name, O_RDONLY | O_NDELAY); + if (fd < 0) { + if ((errno == EISDIR) && env_dir) { + if (OPT_verbose) + bb_perror_msg("warning: %s/%s is a directory", + directory_name, d->d_name); + continue; + } else + bb_perror_msg_and_die("open %s/%s", + directory_name, d->d_name); + } + if (fd >= 0) { + char buf[256]; + char *tail; + int size; + + size = safe_read(fd, buf, sizeof(buf)-1); + if (size < 0) + bb_perror_msg_and_die("read %s/%s", + directory_name, d->d_name); + if (size == 0) { + unsetenv(d->d_name); + continue; + } + buf[size] = '\n'; + tail = memchr(buf, '\n', sizeof(buf)); + /* skip trailing whitespace */; + while (1) { + if (tail[0]==' ') tail[0] = '\0'; + if (tail[0]=='\t') tail[0] = '\0'; + if (tail[0]=='\n') tail[0] = '\0'; + if (tail == buf) break; + tail--; + } + xsetenv(d->d_name, buf); + } + } + closedir(dir); + if (fchdir(wdir) == -1) bb_perror_msg_and_die("fchdir"); + close(wdir); +} + +static void limit(int what, long l) +{ + struct rlimit r; + + if (getrlimit(what, &r) == -1) bb_perror_msg_and_die("getrlimit"); + if ((l < 0) || (l > r.rlim_max)) + r.rlim_cur = r.rlim_max; + else + r.rlim_cur = l; + if (setrlimit(what, &r) == -1) bb_perror_msg_and_die("setrlimit"); +} + +static void slimit(void) +{ + if (limitd >= -1) { +#ifdef RLIMIT_DATA + limit(RLIMIT_DATA, limitd); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_DATA"); +#endif + } + if (limits >= -1) { +#ifdef RLIMIT_STACK + limit(RLIMIT_STACK, limits); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_STACK"); +#endif + } + if (limitl >= -1) { +#ifdef RLIMIT_MEMLOCK + limit(RLIMIT_MEMLOCK, limitl); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_MEMLOCK"); +#endif + } + if (limita >= -1) { +#ifdef RLIMIT_VMEM + limit(RLIMIT_VMEM, limita); +#else +#ifdef RLIMIT_AS + limit(RLIMIT_AS, limita); +#else + if (OPT_verbose) + bb_error_msg("system does not support %s", + "RLIMIT_VMEM"); +#endif +#endif + } + if (limito >= -1) { +#ifdef RLIMIT_NOFILE + limit(RLIMIT_NOFILE, limito); +#else +#ifdef RLIMIT_OFILE + limit(RLIMIT_OFILE, limito); +#else + if (OPT_verbose) + bb_error_msg("system does not support %s", + "RLIMIT_NOFILE"); +#endif +#endif + } + if (limitp >= -1) { +#ifdef RLIMIT_NPROC + limit(RLIMIT_NPROC, limitp); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_NPROC"); +#endif + } + if (limitf >= -1) { +#ifdef RLIMIT_FSIZE + limit(RLIMIT_FSIZE, limitf); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_FSIZE"); +#endif + } + if (limitc >= -1) { +#ifdef RLIMIT_CORE + limit(RLIMIT_CORE, limitc); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_CORE"); +#endif + } + if (limitr >= -1) { +#ifdef RLIMIT_RSS + limit(RLIMIT_RSS, limitr); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_RSS"); +#endif + } + if (limitt >= -1) { +#ifdef RLIMIT_CPU + limit(RLIMIT_CPU, limitt); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_CPU"); +#endif + } +} + +/* argv[0] */ +static void setuidgid(int, char **); +static void envuidgid(int, char **); +static void envdir(int, char **); +static void softlimit(int, char **); + +int chpst_main(int argc, char **argv) +{ + if (applet_name[3] == 'd') envdir(argc, argv); + if (applet_name[1] == 'o') softlimit(argc, argv); + if (applet_name[0] == 's') setuidgid(argc, argv); + if (applet_name[0] == 'e') envuidgid(argc, argv); + // otherwise we are chpst + + { + char *m,*d,*o,*p,*f,*c,*r,*t,*n; + getopt32(argc, argv, "+u:U:e:m:d:o:p:f:c:r:t:/:n:vP012", + &set_user,&env_user,&env_dir, + &m,&d,&o,&p,&f,&c,&r,&t,&root,&n); + // if (option_mask32 & 0x1) // -u + // if (option_mask32 & 0x2) // -U + // if (option_mask32 & 0x4) // -e + if (option_mask32 & 0x8) limits = limitl = limita = limitd = xatoul(m); // -m + if (option_mask32 & 0x10) limitd = xatoul(d); // -d + if (option_mask32 & 0x20) limito = xatoul(o); // -o + if (option_mask32 & 0x40) limitp = xatoul(p); // -p + if (option_mask32 & 0x80) limitf = xatoul(f); // -f + if (option_mask32 & 0x100) limitc = xatoul(c); // -c + if (option_mask32 & 0x200) limitr = xatoul(r); // -r + if (option_mask32 & 0x400) limitt = xatoul(t); // -t + // if (option_mask32 & 0x800) // -/ + if (option_mask32 & 0x1000) nicelvl = xatoi(n); // -n + // The below consts should match #defines at top! + //if (option_mask32 & 0x2000) OPT_verbose = 1; // -v + //if (option_mask32 & 0x4000) OPT_pgrp = 1; // -P + //if (option_mask32 & 0x8000) OPT_nostdin = 1; // -0 + //if (option_mask32 & 0x10000) OPT_nostdout = 1; // -1 + //if (option_mask32 & 0x20000) OPT_nostderr = 1; // -2 + } + argv += optind; + if (!argv || !*argv) bb_show_usage(); + + if (OPT_pgrp) setsid(); + if (env_dir) edir(env_dir); + if (root) { + xchdir(root); + if (chroot(".") == -1) + bb_perror_msg_and_die("chroot"); + } + slimit(); + if (nicelvl) { + errno = 0; + if (nice(nicelvl) == -1) + bb_perror_msg_and_die("nice"); + } + if (env_user) euidgid(env_user); + if (set_user) suidgid(set_user); + if (OPT_nostdin) close(0); + if (OPT_nostdout) close(1); + if (OPT_nostderr) close(2); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void setuidgid(int argc, char **argv) +{ + const char *account; + + account = *++argv; + if (!account) bb_show_usage(); + if (!*++argv) bb_show_usage(); + suidgid((char*)account); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void envuidgid(int argc, char **argv) +{ + const char *account; + + account = *++argv; + if (!account) bb_show_usage(); + if (!*++argv) bb_show_usage(); + euidgid((char*)account); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void envdir(int argc, char **argv) +{ + const char *dir; + + dir = *++argv; + if (!dir) bb_show_usage(); + if (!*++argv) bb_show_usage(); + edir(dir); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void softlimit(int argc, char **argv) +{ + char *a,*c,*d,*f,*l,*m,*o,*p,*r,*s,*t; + getopt32(argc, argv, "+a:c:d:f:l:m:o:p:r:s:t:", + &a,&c,&d,&f,&l,&m,&o,&p,&r,&s,&t); + if (option_mask32 & 0x001) limita = xatoul(a); // -a + if (option_mask32 & 0x002) limitc = xatoul(c); // -c + if (option_mask32 & 0x004) limitd = xatoul(d); // -d + if (option_mask32 & 0x008) limitf = xatoul(f); // -f + if (option_mask32 & 0x010) limitl = xatoul(l); // -l + if (option_mask32 & 0x020) limits = limitl = limita = limitd = xatoul(m); // -m + if (option_mask32 & 0x040) limito = xatoul(o); // -o + if (option_mask32 & 0x080) limitp = xatoul(p); // -p + if (option_mask32 & 0x100) limitr = xatoul(r); // -r + if (option_mask32 & 0x200) limits = xatoul(s); // -s + if (option_mask32 & 0x400) limitt = xatoul(t); // -t + argv += optind; + if (!argv[0]) bb_show_usage(); + slimit(); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} diff --git a/runit/runit_lib.c b/runit/runit_lib.c new file mode 100644 index 000000000..5ebbc5840 --- /dev/null +++ b/runit/runit_lib.c @@ -0,0 +1,982 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denis Vlasenko */ +/* Collected into one file from runit's many tiny files */ +/* TODO: review, eliminate unneeded stuff, move good stuff to libbb */ + +#include +#include +#include "libbb.h" +#include "runit_lib.h" + +/*** buffer.c ***/ + +void buffer_init(buffer *s,int (*op)(int fd,char *buf,unsigned len),int fd,char *buf,unsigned len) +{ + s->x = buf; + s->fd = fd; + s->op = op; + s->p = 0; + s->n = len; +} + + +/*** buffer_get.c ***/ + +static int oneread(int (*op)(int fd,char *buf,unsigned len),int fd,char *buf,unsigned len) +{ + int r; + + for (;;) { + r = op(fd,buf,len); + if (r == -1) if (errno == EINTR) continue; + return r; + } +} + +static int getthis(buffer *s,char *buf,unsigned len) +{ + if (len > s->p) len = s->p; + s->p -= len; + memcpy(buf,s->x + s->n,len); + s->n += len; + return len; +} + +int buffer_feed(buffer *s) +{ + int r; + + if (s->p) return s->p; + r = oneread(s->op,s->fd,s->x,s->n); + if (r <= 0) return r; + s->p = r; + s->n -= r; + if (s->n > 0) memmove(s->x + s->n,s->x,r); + return r; +} + +int buffer_bget(buffer *s,char *buf,unsigned len) +{ + int r; + + if (s->p > 0) return getthis(s,buf,len); + if (s->n <= len) return oneread(s->op,s->fd,buf,s->n); + r = buffer_feed(s); if (r <= 0) return r; + return getthis(s,buf,len); +} + +int buffer_get(buffer *s,char *buf,unsigned len) +{ + int r; + + if (s->p > 0) return getthis(s,buf,len); + if (s->n <= len) return oneread(s->op,s->fd,buf,len); + r = buffer_feed(s); if (r <= 0) return r; + return getthis(s,buf,len); +} + +char *buffer_peek(buffer *s) +{ + return s->x + s->n; +} + +void buffer_seek(buffer *s,unsigned len) +{ + s->n += len; + s->p -= len; +} + + +/*** buffer_put.c ***/ + +static int allwrite(int (*op)(int fd,char *buf,unsigned len),int fd,const char *buf,unsigned len) +{ + int w; + + while (len) { + w = op(fd,(char*)buf,len); + if (w == -1) { + if (errno == EINTR) continue; + return -1; /* note that some data may have been written */ + } + if (w == 0) ; /* luser's fault */ + buf += w; + len -= w; + } + return 0; +} + +int buffer_flush(buffer *s) +{ + int p; + + p = s->p; + if (!p) return 0; + s->p = 0; + return allwrite(s->op,s->fd,s->x,p); +} + +int buffer_putalign(buffer *s,const char *buf,unsigned len) +{ + unsigned n; + + while (len > (n = s->n - s->p)) { + memcpy(s->x + s->p,buf,n); + s->p += n; + buf += n; + len -= n; + if (buffer_flush(s) == -1) return -1; + } + /* now len <= s->n - s->p */ + memcpy(s->x + s->p,buf,len); + s->p += len; + return 0; +} + +int buffer_put(buffer *s,const char *buf,unsigned len) +{ + unsigned n; + + n = s->n; + if (len > n - s->p) { + if (buffer_flush(s) == -1) return -1; + /* now s->p == 0 */ + if (n < BUFFER_OUTSIZE) n = BUFFER_OUTSIZE; + while (len > s->n) { + if (n > len) n = len; + if (allwrite(s->op,s->fd,buf,n) == -1) return -1; + buf += n; + len -= n; + } + } + /* now len <= s->n - s->p */ + memcpy(s->x + s->p,buf,len); + s->p += len; + return 0; +} + +int buffer_putflush(buffer *s,const char *buf,unsigned len) +{ + if (buffer_flush(s) == -1) return -1; + return allwrite(s->op,s->fd,buf,len); +} + +int buffer_putsalign(buffer *s,const char *buf) +{ + return buffer_putalign(s,buf,strlen(buf)); +} + +int buffer_puts(buffer *s,const char *buf) +{ + return buffer_put(s,buf,strlen(buf)); +} + +int buffer_putsflush(buffer *s,const char *buf) +{ + return buffer_putflush(s,buf,strlen(buf)); +} + + +/*** buffer_read.c ***/ + +int buffer_unixread(int fd,char *buf,unsigned len) +{ + return read(fd,buf,len); +} + + +/*** buffer_write.c ***/ + +int buffer_unixwrite(int fd,char *buf,unsigned len) +{ + return write(fd,buf,len); +} + + +/*** byte_chr.c ***/ + +unsigned byte_chr(char *s,unsigned n,int c) +{ + char ch; + char *t; + + ch = c; + t = s; + for (;;) { + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + } + return t - s; +} + + +/*** coe.c ***/ + +int coe(int fd) +{ + return fcntl(fd,F_SETFD,FD_CLOEXEC); +} + + +/*** fd_copy.c ***/ + +int fd_copy(int to,int from) +{ + if (to == from) return 0; + if (fcntl(from,F_GETFL,0) == -1) return -1; + close(to); + if (fcntl(from,F_DUPFD,to) == -1) return -1; + return 0; +} + + +/*** fd_move.c ***/ + +int fd_move(int to,int from) +{ + if (to == from) return 0; + if (fd_copy(to,from) == -1) return -1; + close(from); + return 0; +} + + +/*** fifo.c ***/ + +int fifo_make(const char *fn,int mode) { return mkfifo(fn,mode); } + + +/*** fmt_ptime.c ***/ + +unsigned fmt_ptime(char *s, struct taia *ta) { + struct tm *t; + unsigned long u; + + if (ta->sec.x < 4611686018427387914ULL) return 0; /* impossible? */ + u = ta->sec.x -4611686018427387914ULL; + if (!(t = gmtime((time_t*)&u))) return 0; + fmt_ulong(s, 1900 + t->tm_year); + s[4] = '-'; fmt_uint0(&s[5], t->tm_mon+1, 2); + s[7] = '-'; fmt_uint0(&s[8], t->tm_mday, 2); + s[10] = '_'; fmt_uint0(&s[11], t->tm_hour, 2); + s[13] = ':'; fmt_uint0(&s[14], t->tm_min, 2); + s[16] = ':'; fmt_uint0(&s[17], t->tm_sec, 2); + s[19] = '.'; fmt_uint0(&s[20], ta->nano, 9); + return 25; +} + +unsigned fmt_taia(char *s, struct taia *t) { + static char hex[16] = "0123456789abcdef"; + static char pack[TAIA_PACK]; + int i; + + taia_pack(pack, t); + s[0] = '@'; + for (i = 0; i < 12; ++i) { + s[i*2+1] = hex[(pack[i] >> 4) &15]; + s[i*2+2] = hex[pack[i] &15]; + } + return 25; +} + + +/*** fmt_uint.c ***/ + +unsigned fmt_uint(char *s,unsigned u) +{ + return fmt_ulong(s,u); +} + + +/*** fmt_uint0.c ***/ + +unsigned fmt_uint0(char *s,unsigned u,unsigned n) +{ + unsigned len; + len = fmt_uint(FMT_LEN,u); + while (len < n) { if (s) *s++ = '0'; ++len; } + if (s) fmt_uint(s,u); + return len; +} + + +/*** fmt_ulong.c ***/ + +unsigned fmt_ulong(char *s,unsigned long u) +{ + unsigned len; unsigned long q; + len = 1; q = u; + while (q > 9) { ++len; q /= 10; } + if (s) { + s += len; + do { *--s = '0' + (u % 10); u /= 10; } while(u); /* handles u == 0 */ + } + return len; +} + + +/*** tai_now.c ***/ + +void tai_now(struct tai *t) +{ + tai_unix(t,time((time_t *) 0)); +} + + +/*** tai_pack.c ***/ + +void tai_pack(char *s,const struct tai *t) +{ + uint64_t x; + + x = t->x; + s[7] = x & 255; x >>= 8; + s[6] = x & 255; x >>= 8; + s[5] = x & 255; x >>= 8; + s[4] = x & 255; x >>= 8; + s[3] = x & 255; x >>= 8; + s[2] = x & 255; x >>= 8; + s[1] = x & 255; x >>= 8; + s[0] = x; +} + + +/*** tai_sub.c ***/ + +void tai_sub(struct tai *t,const struct tai *u,const struct tai *v) +{ + t->x = u->x - v->x; +} + + +/*** tai_unpack.c ***/ + +void tai_unpack(const char *s,struct tai *t) +{ + uint64_t x; + + x = (unsigned char) s[0]; + x <<= 8; x += (unsigned char) s[1]; + x <<= 8; x += (unsigned char) s[2]; + x <<= 8; x += (unsigned char) s[3]; + x <<= 8; x += (unsigned char) s[4]; + x <<= 8; x += (unsigned char) s[5]; + x <<= 8; x += (unsigned char) s[6]; + x <<= 8; x += (unsigned char) s[7]; + t->x = x; +} + + +/*** taia_add.c ***/ + +/* XXX: breaks tai encapsulation */ + +void taia_add(struct taia *t,const struct taia *u,const struct taia *v) +{ + t->sec.x = u->sec.x + v->sec.x; + t->nano = u->nano + v->nano; + t->atto = u->atto + v->atto; + if (t->atto > 999999999UL) { + t->atto -= 1000000000UL; + ++t->nano; + } + if (t->nano > 999999999UL) { + t->nano -= 1000000000UL; + ++t->sec.x; + } +} + + +/*** taia_approx.c ***/ + +double taia_approx(const struct taia *t) +{ + return tai_approx(&t->sec) + taia_frac(t); +} + + +/*** taia_frac.c ***/ + +double taia_frac(const struct taia *t) +{ + return (t->atto * 0.000000001 + t->nano) * 0.000000001; +} + + +/*** taia_less.c ***/ + +/* XXX: breaks tai encapsulation */ + +int taia_less(const struct taia *t,const struct taia *u) +{ + if (t->sec.x < u->sec.x) return 1; + if (t->sec.x > u->sec.x) return 0; + if (t->nano < u->nano) return 1; + if (t->nano > u->nano) return 0; + return t->atto < u->atto; +} + + +/*** taia_now.c ***/ + +void taia_now(struct taia *t) +{ + struct timeval now; + gettimeofday(&now,(struct timezone *) 0); + tai_unix(&t->sec,now.tv_sec); + t->nano = 1000 * now.tv_usec + 500; + t->atto = 0; +} + + +/*** taia_pack.c ***/ + +void taia_pack(char *s,const struct taia *t) +{ + unsigned long x; + + tai_pack(s,&t->sec); + s += 8; + + x = t->atto; + s[7] = x & 255; x >>= 8; + s[6] = x & 255; x >>= 8; + s[5] = x & 255; x >>= 8; + s[4] = x; + x = t->nano; + s[3] = x & 255; x >>= 8; + s[2] = x & 255; x >>= 8; + s[1] = x & 255; x >>= 8; + s[0] = x; +} + + +/*** taia_sub.c ***/ + +/* XXX: breaks tai encapsulation */ + +void taia_sub(struct taia *t,const struct taia *u,const struct taia *v) +{ + unsigned long unano = u->nano; + unsigned long uatto = u->atto; + + t->sec.x = u->sec.x - v->sec.x; + t->nano = unano - v->nano; + t->atto = uatto - v->atto; + if (t->atto > uatto) { + t->atto += 1000000000UL; + --t->nano; + } + if (t->nano > unano) { + t->nano += 1000000000UL; + --t->sec.x; + } +} + + +/*** taia_uint.c ***/ + +/* XXX: breaks tai encapsulation */ + +void taia_uint(struct taia *t,unsigned s) +{ + t->sec.x = s; + t->nano = 0; + t->atto = 0; +} + + +/*** stralloc_cat.c ***/ +#if 0 + +int stralloc_cat(stralloc *sato,const stralloc *safrom) +{ + return stralloc_catb(sato,safrom->s,safrom->len); +} + + +/*** stralloc_catb.c ***/ + +int stralloc_catb(stralloc *sa,const char *s,unsigned n) +{ + if (!sa->s) return stralloc_copyb(sa,s,n); + if (!stralloc_readyplus(sa,n + 1)) return 0; + memcpy(sa->s + sa->len,s,n); + sa->len += n; + sa->s[sa->len] = 'Z'; /* ``offensive programming'' */ + return 1; +} + + +/*** stralloc_cats.c ***/ + +int stralloc_cats(stralloc *sa,const char *s) +{ + return stralloc_catb(sa,s,strlen(s)); +} + + +/*** stralloc_eady.c ***/ + +GEN_ALLOC_ready(stralloc,char,s,len,a,i,n,x,30,stralloc_ready) +GEN_ALLOC_readyplus(stralloc,char,s,len,a,i,n,x,30,stralloc_readyplus) + + +/*** stralloc_opyb.c ***/ + +int stralloc_copyb(stralloc *sa,const char *s,unsigned n) +{ + if (!stralloc_ready(sa,n + 1)) return 0; + memcpy(sa->s,s,n); + sa->len = n; + sa->s[n] = 'Z'; /* ``offensive programming'' */ + return 1; +} + + +/*** stralloc_opys.c ***/ + +int stralloc_copys(stralloc *sa,const char *s) +{ + return stralloc_copyb(sa,s,strlen(s)); +} + + +/*** stralloc_pend.c ***/ + +GEN_ALLOC_append(stralloc,char,s,len,a,i,n,x,30,stralloc_readyplus,stralloc_append) + +#endif /* stralloc */ + +/*** iopause.c ***/ + +void iopause(iopause_fd *x,unsigned len,struct taia *deadline,struct taia *stamp) +{ + struct taia t; + int millisecs; + double d; + int i; + + if (taia_less(deadline,stamp)) + millisecs = 0; + else { + t = *stamp; + taia_sub(&t,deadline,&t); + d = taia_approx(&t); + if (d > 1000.0) d = 1000.0; + millisecs = d * 1000.0 + 20.0; + } + + for (i = 0;i < len;++i) + x[i].revents = 0; + + poll(x,len,millisecs); + /* XXX: some kernels apparently need x[0] even if len is 0 */ + /* XXX: how to handle EAGAIN? are kernels really this dumb? */ + /* XXX: how to handle EINVAL? when exactly can this happen? */ +} + + +/*** lock_ex.c ***/ + +int lock_ex(int fd) +{ + return flock(fd,LOCK_EX); +} + + +/*** lock_exnb.c ***/ + +int lock_exnb(int fd) +{ + return flock(fd,LOCK_EX | LOCK_NB); +} + + +/*** open_append.c ***/ + +int open_append(const char *fn) +{ + return open(fn, O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); +} + + +/*** open_read.c ***/ + +int open_read(const char *fn) +{ + return open(fn, O_RDONLY|O_NDELAY); +} + + +/*** open_trunc.c ***/ + +int open_trunc(const char *fn) +{ + return open(fn,O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT,0644); +} + + +/*** open_write.c ***/ + +int open_write(const char *fn) +{ + return open(fn, O_WRONLY|O_NDELAY); +} + + +/*** openreadclose.c ***/ +#if 0 +int openreadclose(const char *fn,stralloc *sa,unsigned bufsize) +{ + int fd; + fd = open_read(fn); + if (fd == -1) { + if (errno == ENOENT) return 0; + return -1; + } + if (readclose(fd,sa,bufsize) == -1) return -1; + return 1; +} +#endif + + +/*** pathexec_env.c ***/ +#if 0 +static stralloc plus; +static stralloc tmp; + +int pathexec_env(const char *s,const char *t) +{ + if (!s) return 1; + if (!stralloc_copys(&tmp,s)) return 0; + if (t) { + if (!stralloc_cats(&tmp,"=")) return 0; + if (!stralloc_cats(&tmp,t)) return 0; + } + if (!stralloc_0(&tmp)) return 0; + return stralloc_cat(&plus,&tmp); +} + +void pathexec(char **argv) +{ + char **e; + unsigned elen; + unsigned i; + unsigned j; + unsigned split; + unsigned t; + + if (!stralloc_cats(&plus,"")) return; + + elen = 0; + for (i = 0;environ[i];++i) + ++elen; + for (i = 0;i < plus.len;++i) + if (!plus.s[i]) + ++elen; + + e = malloc((elen + 1) * sizeof(char *)); + if (!e) return; + + elen = 0; + for (i = 0;environ[i];++i) + e[elen++] = environ[i]; + + j = 0; + for (i = 0;i < plus.len;++i) + if (!plus.s[i]) { + split = str_chr(plus.s + j,'='); + for (t = 0;t < elen;++t) + if (memcmp(plus.s + j,e[t],split) == 0) + if (e[t][split] == '=') { + --elen; + e[t] = e[elen]; + break; + } + if (plus.s[j + split]) + e[elen++] = plus.s + j; + j = i + 1; + } + e[elen] = 0; + + pathexec_run(*argv,argv,e); + free(e); +} +#endif + +/*** pathexec_run.c ***/ +#if 0 +static stralloc tmp; + +void pathexec_run(const char *file,char *const *argv,char *const *envp) +{ + const char *path; + unsigned split; + int savederrno; + + if (file[str_chr(file,'/')]) { + execve(file,argv,envp); + return; + } + + path = getenv("PATH"); + if (!path) path = "/bin:/usr/bin"; + + savederrno = 0; + for (;;) { + split = str_chr(path,':'); + if (!stralloc_copyb(&tmp,path,split)) return; + if (!split) + if (!stralloc_cats(&tmp,".")) return; + if (!stralloc_cats(&tmp,"/")) return; + if (!stralloc_cats(&tmp,file)) return; + if (!stralloc_0(&tmp)) return; + + execve(tmp.s,argv,envp); + if (errno != ENOENT) { + savederrno = errno; + if ((errno != EACCES) && (errno != EPERM) && (errno != EISDIR)) return; + } + + if (!path[split]) { + if (savederrno) errno = savederrno; + return; + } + path += split; + path += 1; + } +} +#endif + +/*** pmatch.c ***/ + +unsigned pmatch(const char *p, const char *s, unsigned len) { + for (;;) { + char c = *p++; + if (!c) return !len; + switch (c) { + case '*': + if (!(c = *p)) return 1; + for (;;) { + if (!len) return 0; + if (*s == c) break; + ++s; --len; + } + continue; + case '+': + if ((c = *p++) != *s) return 0; + for (;;) { + if (!len) return 1; + if (*s != c) break; + ++s; --len; + } + continue; + /* + case '?': + if (*p == '?') { + if (*s != '?') return 0; + ++p; + } + ++s; --len; + continue; + */ + default: + if (!len) return 0; + if (*s != c) return 0; + ++s; --len; + continue; + } + } + return 0; +} + + +/*** prot.c ***/ + +int prot_gid(int gid) +{ + gid_t x = gid; + if (setgroups(1,&x) == -1) return -1; + return setgid(gid); /* _should_ be redundant, but on some systems it isn't */ +} + +int prot_uid(int uid) +{ + return setuid(uid); +} + + +/*** readclose.c ***/ +#if 0 +int readclose_append(int fd,stralloc *sa,unsigned bufsize) +{ + int r; + for (;;) { + if (!stralloc_readyplus(sa,bufsize)) { close(fd); return -1; } + r = read(fd,sa->s + sa->len,bufsize); + if (r == -1) if (errno == EINTR) continue; + if (r <= 0) { close(fd); return r; } + sa->len += r; + } +} + +int readclose(int fd,stralloc *sa,unsigned bufsize) +{ + if (!stralloc_copys(sa,"")) { close(fd); return -1; } + return readclose_append(fd,sa,bufsize); +} +#endif + +/*** scan_ulong.c ***/ + +unsigned scan_ulong(const char *s,unsigned long *u) +{ + unsigned pos = 0; + unsigned long result = 0; + unsigned long c; + while ((c = (unsigned long) (unsigned char) (s[pos] - '0')) < 10) { + result = result * 10 + c; + ++pos; + } + *u = result; + return pos; +} + + +/*** seek_set.c ***/ + +int seek_set(int fd,seek_pos pos) +{ + if (lseek(fd,(off_t) pos,SEEK_SET) == -1) return -1; return 0; +} + + +/*** sig.c ***/ + +int sig_alarm = SIGALRM; +int sig_child = SIGCHLD; +int sig_cont = SIGCONT; +int sig_hangup = SIGHUP; +int sig_int = SIGINT; +int sig_pipe = SIGPIPE; +int sig_term = SIGTERM; + +void (*sig_defaulthandler)(int) = SIG_DFL; +void (*sig_ignorehandler)(int) = SIG_IGN; + + +/*** sig_block.c ***/ + +void sig_block(int sig) +{ + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss,sig); + sigprocmask(SIG_BLOCK,&ss,(sigset_t *) 0); +} + +void sig_unblock(int sig) +{ + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss,sig); + sigprocmask(SIG_UNBLOCK,&ss,(sigset_t *) 0); +} + +void sig_blocknone(void) +{ + sigset_t ss; + sigemptyset(&ss); + sigprocmask(SIG_SETMASK,&ss,(sigset_t *) 0); +} + + +/*** sig_catch.c ***/ + +void sig_catch(int sig,void (*f)(int)) +{ + struct sigaction sa; + sa.sa_handler = f; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(sig,&sa,(struct sigaction *) 0); +} + + +/*** sig_pause.c ***/ + +void sig_pause(void) +{ + sigset_t ss; + sigemptyset(&ss); + sigsuspend(&ss); +} + + +/*** str_chr.c ***/ + +unsigned str_chr(const char *s,int c) +{ + char ch; + const char *t; + + ch = c; + t = s; + for (;;) { + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + } + return t - s; +} + + +/*** wait_nohang.c ***/ + +int wait_nohang(int *wstat) +{ + return waitpid(-1,wstat,WNOHANG); +} + + +/*** wait_pid.c ***/ + +int wait_pid(int *wstat, int pid) +{ + int r; + + do + r = waitpid(pid,wstat,0); + while ((r == -1) && (errno == EINTR)); + return r; +} diff --git a/runit/runit_lib.h b/runit/runit_lib.h new file mode 100644 index 000000000..1f911919d --- /dev/null +++ b/runit/runit_lib.h @@ -0,0 +1,403 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/*** buffer.h ***/ + +typedef struct buffer { + char *x; + unsigned p; + unsigned n; + int fd; + int (*op)(int fd,char *buf,unsigned len); +} buffer; + +#define BUFFER_INIT(op,fd,buf,len) { (buf), 0, (len), (fd), (op) } +#define BUFFER_INSIZE 8192 +#define BUFFER_OUTSIZE 8192 + +extern void buffer_init(buffer *,int (*)(int fd,char *buf,unsigned len),int,char *,unsigned); + +extern int buffer_flush(buffer *); +extern int buffer_put(buffer *,const char *,unsigned); +extern int buffer_putalign(buffer *,const char *,unsigned); +extern int buffer_putflush(buffer *,const char *,unsigned); +extern int buffer_puts(buffer *,const char *); +extern int buffer_putsalign(buffer *,const char *); +extern int buffer_putsflush(buffer *,const char *); + +#define buffer_PUTC(s,c) \ + ( ((s)->n != (s)->p) \ + ? ( (s)->x[(s)->p++] = (c), 0 ) \ + : buffer_put((s),&(c),1) \ + ) + +extern int buffer_get(buffer *,char *,unsigned); +extern int buffer_bget(buffer *,char *,unsigned); +extern int buffer_feed(buffer *); + +extern char *buffer_peek(buffer *); +extern void buffer_seek(buffer *,unsigned); + +#define buffer_PEEK(s) ( (s)->x + (s)->n ) +#define buffer_SEEK(s,len) ( ( (s)->p -= (len) ) , ( (s)->n += (len) ) ) + +#define buffer_GETC(s,c) \ + ( ((s)->p > 0) \ + ? ( *(c) = (s)->x[(s)->n], buffer_SEEK((s),1), 1 ) \ + : buffer_get((s),(c),1) \ + ) + +extern int buffer_copy(buffer *,buffer *); + +extern int buffer_unixread(int,char *,unsigned); +/* Actually, int buffer_unixwrite(int,const char *,unsigned), + but that 'const' will produce warnings... oh well */ +extern int buffer_unixwrite(int,char *,unsigned); + + +/*** byte.h ***/ + +extern unsigned byte_chr(char *s,unsigned n,int c); + + +/*** coe.h ***/ + +extern int coe(int); + + +/*** direntry.h ***/ + +#define direntry struct dirent + + +/*** fd.h ***/ + +extern int fd_copy(int,int); +extern int fd_move(int,int); + + +/*** fifo.h ***/ + +extern int fifo_make(const char *,int); + + +/*** fmt.h ***/ + +#define FMT_ULONG 40 /* enough space to hold 2^128 - 1 in decimal, plus \0 */ +#define FMT_LEN ((char *) 0) /* convenient abbreviation */ + +extern unsigned fmt_uint(char *,unsigned); +extern unsigned fmt_uint0(char *,unsigned,unsigned); +extern unsigned fmt_xint(char *,unsigned); +extern unsigned fmt_nbbint(char *,unsigned,unsigned,unsigned,unsigned); +extern unsigned fmt_ushort(char *,unsigned short); +extern unsigned fmt_xshort(char *,unsigned short); +extern unsigned fmt_nbbshort(char *,unsigned,unsigned,unsigned,unsigned short); +extern unsigned fmt_ulong(char *,unsigned long); +extern unsigned fmt_xlong(char *,unsigned long); +extern unsigned fmt_nbblong(char *,unsigned,unsigned,unsigned,unsigned long); + +extern unsigned fmt_plusminus(char *,int); +extern unsigned fmt_minus(char *,int); +extern unsigned fmt_0x(char *,int); + +extern unsigned fmt_str(char *,const char *); +extern unsigned fmt_strn(char *,const char *,unsigned); + + +/*** tai.h ***/ + +struct tai { + uint64_t x; +} ; + +#define tai_unix(t,u) ((void) ((t)->x = 4611686018427387914ULL + (uint64_t) (u))) + +extern void tai_now(struct tai *); + +#define tai_approx(t) ((double) ((t)->x)) + +extern void tai_add(struct tai *,const struct tai *,const struct tai *); +extern void tai_sub(struct tai *,const struct tai *,const struct tai *); +#define tai_less(t,u) ((t)->x < (u)->x) + +#define TAI_PACK 8 +extern void tai_pack(char *,const struct tai *); +extern void tai_unpack(const char *,struct tai *); + +extern void tai_uint(struct tai *,unsigned); + + +/*** taia.h ***/ + +struct taia { + struct tai sec; + unsigned long nano; /* 0...999999999 */ + unsigned long atto; /* 0...999999999 */ +} ; + +extern void taia_tai(const struct taia *,struct tai *); + +extern void taia_now(struct taia *); + +extern double taia_approx(const struct taia *); +extern double taia_frac(const struct taia *); + +extern void taia_add(struct taia *,const struct taia *,const struct taia *); +extern void taia_addsec(struct taia *,const struct taia *,int); +extern void taia_sub(struct taia *,const struct taia *,const struct taia *); +extern void taia_half(struct taia *,const struct taia *); +extern int taia_less(const struct taia *,const struct taia *); + +#define TAIA_PACK 16 +extern void taia_pack(char *,const struct taia *); +extern void taia_unpack(const char *,struct taia *); + +#define TAIA_FMTFRAC 19 +extern unsigned taia_fmtfrac(char *,const struct taia *); + +extern void taia_uint(struct taia *,unsigned); + + +/*** fmt_ptime.h ***/ + +#define FMT_PTIME 30 + +extern unsigned fmt_ptime(char *, struct taia *); +extern unsigned fmt_taia(char *, struct taia *); + + +/*** gen_alloc.h ***/ + +#define GEN_ALLOC_typedef(ta,type,field,len,a) \ + typedef struct ta { type *field; unsigned len; unsigned a; } ta; + + +/*** gen_allocdefs.h ***/ + +#define GEN_ALLOC_ready(ta,type,field,len,a,i,n,x,base,ta_ready) \ +int ta_ready(ta *x,unsigned n) \ +{ unsigned i; \ + if (x->field) { \ + i = x->a; \ + if (n > i) { \ + x->a = base + n + (n >> 3); \ + x->field = realloc(x->field,x->a * sizeof(type)); \ + if (x->field) return 1; \ + x->a = i; return 0; } \ + return 1; } \ + x->len = 0; \ + return !!(x->field = malloc((x->a = n) * sizeof(type))); } + +#define GEN_ALLOC_readyplus(ta,type,field,len,a,i,n,x,base,ta_rplus) \ +int ta_rplus(ta *x,unsigned n) \ +{ unsigned i; \ + if (x->field) { \ + i = x->a; n += x->len; \ + if (n > i) { \ + x->a = base + n + (n >> 3); \ + x->field = realloc(x->field,x->a * sizeof(type)); \ + if (x->field) return 1; \ + x->a = i; return 0; } \ + return 1; } \ + x->len = 0; \ + return !!(x->field = malloc((x->a = n) * sizeof(type))); } + +#define GEN_ALLOC_append(ta,type,field,len,a,i,n,x,base,ta_rplus,ta_append) \ +int ta_append(ta *x,const type *i) \ +{ if (!ta_rplus(x,1)) return 0; x->field[x->len++] = *i; return 1; } + + +/*** stralloc.h ***/ +#if 0 +GEN_ALLOC_typedef(stralloc,char,s,len,a) + +extern int stralloc_ready(stralloc *,unsigned); +extern int stralloc_readyplus(stralloc *,unsigned); +extern int stralloc_copy(stralloc *,const stralloc *); +extern int stralloc_cat(stralloc *,const stralloc *); +extern int stralloc_copys(stralloc *,const char *); +extern int stralloc_cats(stralloc *,const char *); +extern int stralloc_copyb(stralloc *,const char *,unsigned); +extern int stralloc_catb(stralloc *,const char *,unsigned); +extern int stralloc_append(stralloc *,const char *); /* beware: this takes a pointer to 1 char */ +extern int stralloc_starts(stralloc *,const char *); + +#define stralloc_0(sa) stralloc_append(sa,"") + +extern int stralloc_catulong0(stralloc *,unsigned long,unsigned); +extern int stralloc_catlong0(stralloc *,long,unsigned); + +#define stralloc_catlong(sa,l) (stralloc_catlong0((sa),(l),0)) +#define stralloc_catuint0(sa,i,n) (stralloc_catulong0((sa),(i),(n))) +#define stralloc_catint0(sa,i,n) (stralloc_catlong0((sa),(i),(n))) +#define stralloc_catint(sa,i) (stralloc_catlong0((sa),(i),0)) +#endif + +/*** iopause.h ***/ + +typedef struct pollfd iopause_fd; +#define IOPAUSE_READ POLLIN +#define IOPAUSE_WRITE POLLOUT + +extern void iopause(iopause_fd *,unsigned,struct taia *,struct taia *); + + +/*** lock.h ***/ + +extern int lock_ex(int); +extern int lock_un(int); +extern int lock_exnb(int); + + +/*** ndelay.h ***/ + +extern int ndelay_on(int); +extern int ndelay_off(int); + + +/*** open.h ***/ + +extern int open_read(const char *); +extern int open_excl(const char *); +extern int open_append(const char *); +extern int open_trunc(const char *); +extern int open_write(const char *); + + +/*** openreadclose.h ***/ +#if 0 +extern int openreadclose(const char *,stralloc *,unsigned); +#endif + +/*** pathexec.h ***/ + +extern void pathexec_run(const char *,char *const *,char *const *); +extern int pathexec_env(const char *,const char *); +extern void pathexec(char **); + + +/*** pmatch.h ***/ + +extern unsigned pmatch(const char *, const char *, unsigned); + + +/*** prot.h ***/ + +extern int prot_gid(int); +extern int prot_uid(int); + + +/*** readclose.h ***/ +#if 0 +extern int readclose_append(int,stralloc *,unsigned); +extern int readclose(int,stralloc *,unsigned); +#endif + +/*** scan.h ***/ + +extern unsigned scan_uint(const char *,unsigned *); +extern unsigned scan_xint(const char *,unsigned *); +extern unsigned scan_nbbint(const char *,unsigned,unsigned,unsigned,unsigned *); +extern unsigned scan_ushort(const char *,unsigned short *); +extern unsigned scan_xshort(const char *,unsigned short *); +extern unsigned scan_nbbshort(const char *,unsigned,unsigned,unsigned,unsigned short *); +extern unsigned scan_ulong(const char *,unsigned long *); +extern unsigned scan_xlong(const char *,unsigned long *); +extern unsigned scan_nbblong(const char *,unsigned,unsigned,unsigned,unsigned long *); + +extern unsigned scan_plusminus(const char *,int *); +extern unsigned scan_0x(const char *,unsigned *); + +extern unsigned scan_whitenskip(const char *,unsigned); +extern unsigned scan_nonwhitenskip(const char *,unsigned); +extern unsigned scan_charsetnskip(const char *,const char *,unsigned); +extern unsigned scan_noncharsetnskip(const char *,const char *,unsigned); + +extern unsigned scan_strncmp(const char *,const char *,unsigned); +extern unsigned scan_memcmp(const char *,const char *,unsigned); + +extern unsigned scan_long(const char *,long *); +extern unsigned scan_8long(const char *,unsigned long *); + + +/*** seek.h ***/ + +typedef unsigned long seek_pos; + +extern seek_pos seek_cur(int); + +extern int seek_set(int,seek_pos); +extern int seek_end(int); + +extern int seek_trunc(int,seek_pos); + +#define seek_begin(fd) (seek_set((fd),(seek_pos) 0)) + + +/*** sig.h ***/ + +extern int sig_alarm; +extern int sig_child; +extern int sig_cont; +extern int sig_hangup; +extern int sig_int; +extern int sig_pipe; +extern int sig_term; + +extern void (*sig_defaulthandler)(int); +extern void (*sig_ignorehandler)(int); + +extern void sig_catch(int,void (*)(int)); +#define sig_ignore(s) (sig_catch((s),sig_ignorehandler)) +#define sig_uncatch(s) (sig_catch((s),sig_defaulthandler)) + +extern void sig_block(int); +extern void sig_unblock(int); +extern void sig_blocknone(void); +extern void sig_pause(void); + +extern void sig_dfl(int); + + +/*** str.h ***/ + +extern unsigned str_chr(const char *,int); /* never returns NULL */ + +#define str_diff(s,t) strcmp((s),(t)) +#define str_equal(s,t) (!strcmp((s),(t))) + + +/*** wait.h ***/ + +extern int wait_pid(int *wstat, int pid); +extern int wait_nohang(int *wstat); + +#define wait_crashed(w) ((w) & 127) +#define wait_exitcode(w) ((w) >> 8) +#define wait_stopsig(w) ((w) >> 8) +#define wait_stopped(w) (((w) & 127) == 127) diff --git a/runit/runsv.c b/runit/runsv.c new file mode 100644 index 000000000..e1b5459fb --- /dev/null +++ b/runit/runsv.c @@ -0,0 +1,613 @@ +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +static int selfpipe[2]; + +/* state */ +#define S_DOWN 0 +#define S_RUN 1 +#define S_FINISH 2 +/* ctrl */ +#define C_NOOP 0 +#define C_TERM 1 +#define C_PAUSE 2 +/* want */ +#define W_UP 0 +#define W_DOWN 1 +#define W_EXIT 2 + +struct svdir { + int pid; + int state; + int ctrl; + int want; + struct taia start; + int fdlock; + int fdcontrol; + int fdcontrolwrite; + int islog; +}; +static struct svdir svd[2]; + +static int sigterm = 0; +static int haslog = 0; +static int pidchanged = 1; +static int logpipe[2]; +static char *dir; + +#define usage() bb_show_usage() + +static void fatal2_cannot(char *m1, char *m2) +{ + bb_perror_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2); + /* was exiting 111 */ +} +static void fatal_cannot(char *m) +{ + fatal2_cannot(m, ""); + /* was exiting 111 */ +} +static void fatal2x_cannot(char *m1, char *m2) +{ + bb_error_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2); + /* was exiting 111 */ +} +static void warn_cannot(char *m) +{ + bb_perror_msg("%s: warning: cannot %s", dir, m); +} +static void warnx_cannot(char *m) +{ + bb_error_msg("%s: warning: cannot %s", dir, m); +} + +static void stopservice(struct svdir *); + +static void s_child(int sig_no) +{ + write(selfpipe[1], "", 1); +} + +static void s_term(int sig_no) +{ + sigterm = 1; + write(selfpipe[1], "", 1); /* XXX */ +} + +static char *add_str(char *p, const char *to_add) +{ + while ((*p = *to_add) != '\0') { + p++; + to_add++; + } + return p; +} + +static int open_trunc_or_warn(const char *name) +{ + int fd = open_trunc(name); + if (fd < 0) + bb_perror_msg("%s: warning: cannot open %s", + dir, name); + return fd; +} + +static int rename_or_warn(const char *old, const char *new) +{ + if (rename(old, new) == -1) { + bb_perror_msg("%s: warning: cannot rename %s to %s", + dir, old, new); + return -1; + } + return 0; +} + +static void update_status(struct svdir *s) +{ + unsigned long l; + int fd; + char status[20]; + + /* pid */ + if (pidchanged) { + fd = open_trunc_or_warn("supervise/pid.new"); + if (fd < 0) + return; + if (s->pid) { + char spid[sizeof(s->pid)*3 + 2]; + int size = sprintf(spid, "%d\n", s->pid); + write(fd, spid, size); + } + close(fd); + if (s->islog) { + if (rename_or_warn("supervise/pid.new", "log/supervise/pid")) + return; + } else if (rename_or_warn("supervise/pid.new", "supervise/pid")) { + return; + } + pidchanged = 0; + } + + /* stat */ + fd = open_trunc_or_warn("supervise/stat.new"); + if (fd < -1) + return; + + { + char stat_buf[sizeof("finish, paused, got TERM, want down\n")]; + char *p = stat_buf; + switch (s->state) { + case S_DOWN: + p = add_str(p, "down"); + break; + case S_RUN: + p = add_str(p, "run"); + break; + case S_FINISH: + p = add_str(p, "finish"); + break; + } + if (s->ctrl & C_PAUSE) p = add_str(p, ", paused"); + if (s->ctrl & C_TERM) p = add_str(p, ", got TERM"); + if (s->state != S_DOWN) + switch(s->want) { + case W_DOWN: + p = add_str(p, ", want down"); + break; + case W_EXIT: + p = add_str(p, ", want exit"); + break; + } + *p++ = '\n'; + write(fd, stat_buf, p - stat_buf); + close(fd); + } + + if (s->islog) { + rename_or_warn("supervise/stat.new", "log/supervise/stat"); + } else { + rename_or_warn("supervise/stat.new", "log/supervise/stat"+4); + } + + /* supervise compatibility */ + taia_pack(status, &s->start); + l = (unsigned long)s->pid; + status[12] = l; l >>=8; + status[13] = l; l >>=8; + status[14] = l; l >>=8; + status[15] = l; + if (s->ctrl & C_PAUSE) + status[16] = 1; + else + status[16] = 0; + if (s->want == W_UP) + status[17] = 'u'; + else + status[17] = 'd'; + if (s->ctrl & C_TERM) + status[18] = 1; + else + status[18] = 0; + status[19] = s->state; + fd = open_trunc_or_warn("supervise/status.new"); + if (fd < 0) + return; + l = write(fd, status, sizeof status); + if (l < 0) { + warn_cannot("write supervise/status.new"); + close(fd); + unlink("supervise/status.new"); + return; + } + close(fd); + if (l < sizeof status) { + warnx_cannot("write supervise/status.new: partial write"); + return; + } + if (s->islog) { + rename_or_warn("supervise/status.new", "log/supervise/status"); + } else { + rename_or_warn("supervise/status.new", "log/supervise/status"+4); + } +} + +static unsigned custom(struct svdir *s, char c) +{ + int pid; + int w; + char a[10]; + struct stat st; + char *prog[2]; + + if (s->islog) return 0; + memcpy(a, "control/?", 10); + a[8] = c; + if (stat(a, &st) == 0) { + if (st.st_mode & S_IXUSR) { + pid = fork(); + if (pid == -1) { + warn_cannot("fork for control/?"); + return 0; + } + if (!pid) { + if (haslog && fd_copy(1, logpipe[1]) == -1) + warn_cannot("setup stdout for control/?"); + prog[0] = a; + prog[1] = 0; + execve(a, prog, environ); + fatal_cannot("run control/?"); + } + while (wait_pid(&w, pid) == -1) { + if (errno == EINTR) continue; + warn_cannot("wait for child control/?"); + return 0; + } + return !wait_exitcode(w); + } + } + else { + if (errno == ENOENT) return 0; + warn_cannot("stat control/?"); + } + return 0; +} + +static void stopservice(struct svdir *s) +{ + if (s->pid && ! custom(s, 't')) { + kill(s->pid, SIGTERM); + s->ctrl |=C_TERM; + update_status(s); + } + if (s->want == W_DOWN) { + kill(s->pid, SIGCONT); + custom(s, 'd'); return; + } + if (s->want == W_EXIT) { + kill(s->pid, SIGCONT); + custom(s, 'x'); + } +} + +static void startservice(struct svdir *s) +{ + int p; + char *run[2]; + + if (s->state == S_FINISH) + run[0] = "./finish"; + else { + run[0] = "./run"; + custom(s, 'u'); + } + run[1] = 0; + + if (s->pid != 0) stopservice(s); /* should never happen */ + while ((p = fork()) == -1) { + warn_cannot("fork, sleeping"); + sleep(5); + } + if (p == 0) { + /* child */ + if (haslog) { + if (s->islog) { + if (fd_copy(0, logpipe[0]) == -1) + fatal_cannot("setup filedescriptor for ./log/run"); + close(logpipe[1]); + if (chdir("./log") == -1) + fatal_cannot("change directory to ./log"); + } else { + if (fd_copy(1, logpipe[1]) == -1) + fatal_cannot("setup filedescriptor for ./run"); + close(logpipe[0]); + } + } + sig_uncatch(sig_child); + sig_unblock(sig_child); + sig_uncatch(sig_term); + sig_unblock(sig_term); + execve(*run, run, environ); + if (s->islog) + fatal2_cannot("start log/", *run); + else + fatal2_cannot("start ", *run); + } + if (s->state != S_FINISH) { + taia_now(&s->start); + s->state = S_RUN; + } + s->pid = p; + pidchanged = 1; + s->ctrl = C_NOOP; + update_status(s); +} + +static int ctrl(struct svdir *s, char c) +{ + switch(c) { + case 'd': /* down */ + s->want = W_DOWN; + update_status(s); + if (s->pid && s->state != S_FINISH) stopservice(s); + break; + case 'u': /* up */ + s->want = W_UP; + update_status(s); + if (s->pid == 0) startservice(s); + break; + case 'x': /* exit */ + if (s->islog) break; + s->want = W_EXIT; + update_status(s); + if (s->pid && s->state != S_FINISH) stopservice(s); + break; + case 't': /* sig term */ + if (s->pid && s->state != S_FINISH) stopservice(s); + break; + case 'k': /* sig kill */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGKILL); + s->state = S_DOWN; + break; + case 'p': /* sig pause */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGSTOP); + s->ctrl |=C_PAUSE; + update_status(s); + break; + case 'c': /* sig cont */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGCONT); + if (s->ctrl & C_PAUSE) s->ctrl &=~C_PAUSE; + update_status(s); + break; + case 'o': /* once */ + s->want = W_DOWN; + update_status(s); + if (!s->pid) startservice(s); + break; + case 'a': /* sig alarm */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGALRM); + break; + case 'h': /* sig hup */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGHUP); + break; + case 'i': /* sig int */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGINT); + break; + case 'q': /* sig quit */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGQUIT); + break; + case '1': /* sig usr1 */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGUSR1); + break; + case '2': /* sig usr2 */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGUSR2); + break; + } + return 1; +} + +int runsv_main(int argc, char **argv) +{ + struct stat s; + int fd; + int r; + char buf[256]; + + if (!argv[1] || argv[2]) usage(); + dir = argv[1]; + + if (pipe(selfpipe) == -1) fatal_cannot("create selfpipe"); + coe(selfpipe[0]); + coe(selfpipe[1]); + ndelay_on(selfpipe[0]); + ndelay_on(selfpipe[1]); + + sig_block(sig_child); + sig_catch(sig_child, s_child); + sig_block(sig_term); + sig_catch(sig_term, s_term); + + xchdir(dir); + svd[0].pid = 0; + svd[0].state = S_DOWN; + svd[0].ctrl = C_NOOP; + svd[0].want = W_UP; + svd[0].islog = 0; + svd[1].pid = 0; + taia_now(&svd[0].start); + if (stat("down", &s) != -1) svd[0].want = W_DOWN; + + if (stat("log", &s) == -1) { + if (errno != ENOENT) + warn_cannot("stat ./log"); + } else { + if (!S_ISDIR(s.st_mode)) + warnx_cannot("stat log/down: log is not a directory"); + else { + haslog = 1; + svd[1].state = S_DOWN; + svd[1].ctrl = C_NOOP; + svd[1].want = W_UP; + svd[1].islog = 1; + taia_now(&svd[1].start); + if (stat("log/down", &s) != -1) + svd[1].want = W_DOWN; + if (pipe(logpipe) == -1) + fatal_cannot("create log pipe"); + coe(logpipe[0]); + coe(logpipe[1]); + } + } + + if (mkdir("supervise", 0700) == -1) { + r = readlink("supervise", buf, 256); + if (r != -1) { + if (r == 256) + fatal2x_cannot("readlink ./supervise: ", "name too long"); + buf[r] = 0; + mkdir(buf, 0700); + } else { + if ((errno != ENOENT) && (errno != EINVAL)) + fatal_cannot("readlink ./supervise"); + } + } + svd[0].fdlock = xopen3("log/supervise/lock"+4, + O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if (lock_exnb(svd[0].fdlock) == -1) + fatal_cannot("lock supervise/lock"); + coe(svd[0].fdlock); + if (haslog) { + if (mkdir("log/supervise", 0700) == -1) { + r = readlink("log/supervise", buf, 256); + if (r != -1) { + if (r == 256) + fatal2x_cannot("readlink ./log/supervise: ", "name too long"); + buf[r] = 0; + fd = xopen(".", O_RDONLY|O_NDELAY); + xchdir("./log"); + mkdir(buf, 0700); + if (fchdir(fd) == -1) + fatal_cannot("change back to service directory"); + close(fd); + } + else { + if ((errno != ENOENT) && (errno != EINVAL)) + fatal_cannot("readlink ./log/supervise"); + } + } + svd[1].fdlock = xopen3("log/supervise/lock", + O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if (lock_ex(svd[1].fdlock) == -1) + fatal_cannot("lock log/supervise/lock"); + coe(svd[1].fdlock); + } + + fifo_make("log/supervise/control"+4, 0600); + svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY); + coe(svd[0].fdcontrol); + svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY); + coe(svd[0].fdcontrolwrite); + update_status(&svd[0]); + if (haslog) { + fifo_make("log/supervise/control", 0600); + svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY); + coe(svd[1].fdcontrol); + svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY); + coe(svd[1].fdcontrolwrite); + update_status(&svd[1]); + } + fifo_make("log/supervise/ok"+4, 0600); + fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY); + coe(fd); + if (haslog) { + fifo_make("log/supervise/ok", 0600); + fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY); + coe(fd); + } + for (;;) { + iopause_fd x[3]; + struct taia deadline; + struct taia now; + char ch; + + if (haslog) + if (!svd[1].pid && svd[1].want == W_UP) + startservice(&svd[1]); + if (!svd[0].pid) + if (svd[0].want == W_UP || svd[0].state == S_FINISH) + startservice(&svd[0]); + + x[0].fd = selfpipe[0]; + x[0].events = IOPAUSE_READ; + x[1].fd = svd[0].fdcontrol; + x[1].events = IOPAUSE_READ; + if (haslog) { + x[2].fd = svd[1].fdcontrol; + x[2].events = IOPAUSE_READ; + } + taia_now(&now); + taia_uint(&deadline, 3600); + taia_add(&deadline, &now, &deadline); + + sig_unblock(sig_term); + sig_unblock(sig_child); + iopause(x, 2+haslog, &deadline, &now); + sig_block(sig_term); + sig_block(sig_child); + + while (read(selfpipe[0], &ch, 1) == 1) + ; + for (;;) { + int child; + int wstat; + + child = wait_nohang(&wstat); + if (!child) break; + if ((child == -1) && (errno != EINTR)) break; + if (child == svd[0].pid) { + svd[0].pid = 0; + pidchanged = 1; + svd[0].ctrl &=~C_TERM; + if (svd[0].state != S_FINISH) + fd = open_read("finish"); + if (fd != -1) { + close(fd); + svd[0].state = S_FINISH; + update_status(&svd[0]); + continue; + } + svd[0].state = S_DOWN; + taia_uint(&deadline, 1); + taia_add(&deadline, &svd[0].start, &deadline); + taia_now(&svd[0].start); + update_status(&svd[0]); + if (taia_less(&svd[0].start, &deadline)) sleep(1); + } + if (haslog) { + if (child == svd[1].pid) { + svd[1].pid = 0; + pidchanged = 1; + svd[1].state = S_DOWN; + svd[1].ctrl &=~C_TERM; + taia_uint(&deadline, 1); + taia_add(&deadline, &svd[1].start, &deadline); + taia_now(&svd[1].start); + update_status(&svd[1]); + if (taia_less(&svd[1].start, &deadline)) sleep(1); + } + } + } + if (read(svd[0].fdcontrol, &ch, 1) == 1) + ctrl(&svd[0], ch); + if (haslog) + if (read(svd[1].fdcontrol, &ch, 1) == 1) + ctrl(&svd[1], ch); + + if (sigterm) { + ctrl(&svd[0], 'x'); + sigterm = 0; + } + + if (svd[0].want == W_EXIT && svd[0].state == S_DOWN) { + if (svd[1].pid == 0) + _exit(0); + if (svd[1].want != W_EXIT) { + svd[1].want = W_EXIT; + /* stopservice(&svd[1]); */ + update_status(&svd[1]); + close(logpipe[1]); + close(logpipe[0]); + //if (close(logpipe[1]) == -1) + // warn_cannot("close logpipe[1]"); + //if (close(logpipe[0]) == -1) + // warn_cannot("close logpipe[0]"); + } + } + } + /* not reached */ + return 0; +} diff --git a/runit/runsvdir.c b/runit/runsvdir.c new file mode 100644 index 000000000..9238eec82 --- /dev/null +++ b/runit/runsvdir.c @@ -0,0 +1,306 @@ +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +#define MAXSERVICES 1000 + +static char *svdir; +static unsigned long dev; +static unsigned long ino; +static struct service { + unsigned long dev; + unsigned long ino; + int pid; + int isgone; +} *sv; +static int svnum; +static int check = 1; +static char *rplog; +static int rploglen; +static int logpipe[2]; +static iopause_fd io[1]; +static struct taia stamplog; +static int exitsoon; +static int pgrp; + +#define usage() bb_show_usage() +static void fatal2_cannot(char *m1, char *m2) +{ + bb_perror_msg_and_die("%s: fatal: cannot %s%s", svdir, m1, m2); + /* was exiting 100 */ +} +static void warn3x(char *m1, char *m2, char *m3) +{ + bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3); +} +static void warn2_cannot(char *m1, char *m2) +{ + warn3x("cannot ", m1, m2); +} +static void warnx(char *m1) +{ + warn3x(m1, "", ""); +} + +static void s_term(int sig_no) +{ + exitsoon = 1; +} +static void s_hangup(int sig_no) +{ + exitsoon = 2; +} + +static void runsv(int no, char *name) +{ + int pid = fork(); + + if (pid == -1) { + warn2_cannot("fork for ", name); + return; + } + if (pid == 0) { + /* child */ + char *prog[3]; + + prog[0] = "runsv"; + prog[1] = name; + prog[2] = 0; + sig_uncatch(sig_hangup); + sig_uncatch(sig_term); + if (pgrp) setsid(); + execvp(prog[0], prog); + //pathexec_run(*prog, prog, (char* const*)environ); + fatal2_cannot("start runsv ", name); + } + sv[no].pid = pid; +} + +static void runsvdir(void) +{ + DIR *dir; + direntry *d; + int i; + struct stat s; + + dir = opendir("."); + if (!dir) { + warn2_cannot("open directory ", svdir); + return; + } + for (i = 0; i < svnum; i++) + sv[i].isgone = 1; + errno = 0; + while ((d = readdir(dir))) { + if (d->d_name[0] == '.') continue; + if (stat(d->d_name, &s) == -1) { + warn2_cannot("stat ", d->d_name); + errno = 0; + continue; + } + if (!S_ISDIR(s.st_mode)) continue; + for (i = 0; i < svnum; i++) { + if ((sv[i].ino == s.st_ino) && (sv[i].dev == s.st_dev)) { + sv[i].isgone = 0; + if (!sv[i].pid) + runsv(i, d->d_name); + break; + } + } + if (i == svnum) { + /* new service */ + struct service *svnew = realloc(sv, (i+1) * sizeof(*sv)); + if (!svnew) { + warn3x("cannot start runsv ", d->d_name, + " too many services"); + continue; + } + sv = svnew; + svnum++; + memset(&sv[i], 0, sizeof(sv[i])); + sv[i].ino = s.st_ino; + sv[i].dev = s.st_dev; + //sv[i].pid = 0; + //sv[i].isgone = 0; + runsv(i, d->d_name); + check = 1; + } + } + if (errno) { + warn2_cannot("read directory ", svdir); + closedir(dir); + check = 1; + return; + } + closedir(dir); + + /* SIGTERM removed runsv's */ + for (i = 0; i < svnum; i++) { + if (!sv[i].isgone) + continue; + if (sv[i].pid) + kill(sv[i].pid, SIGTERM); + sv[i] = sv[--svnum]; + check = 1; + } +} + +static int setup_log(void) +{ + rploglen = strlen(rplog); + if (rploglen < 7) { + warnx("log must have at least seven characters"); + return 0; + } + if (pipe(logpipe) == -1) { + warnx("cannot create pipe for log"); + return -1; + } + coe(logpipe[1]); + coe(logpipe[0]); + ndelay_on(logpipe[0]); + ndelay_on(logpipe[1]); + if (fd_copy(2, logpipe[1]) == -1) { + warnx("cannot set filedescriptor for log"); + return -1; + } + io[0].fd = logpipe[0]; + io[0].events = IOPAUSE_READ; + taia_now(&stamplog); + return 1; +} + +int runsvdir_main(int argc, char **argv) +{ + struct stat s; + time_t mtime = 0; + int wstat; + int curdir; + int pid; + struct taia deadline; + struct taia now; + struct taia stampcheck; + char ch; + int i; + + argv++; + if (!argv || !*argv) usage(); + if (**argv == '-') { + switch (*(*argv + 1)) { + case 'P': pgrp = 1; + case '-': ++argv; + } + if (!argv || !*argv) usage(); + } + + sig_catch(sig_term, s_term); + sig_catch(sig_hangup, s_hangup); + svdir = *argv++; + if (argv && *argv) { + rplog = *argv; + if (setup_log() != 1) { + rplog = 0; + warnx("log service disabled"); + } + } + curdir = open_read("."); + if (curdir == -1) + fatal2_cannot("open current directory", ""); + coe(curdir); + + taia_now(&stampcheck); + + for (;;) { + /* collect children */ + for (;;) { + pid = wait_nohang(&wstat); + if (pid <= 0) break; + for (i = 0; i < svnum; i++) { + if (pid == sv[i].pid) { + /* runsv has gone */ + sv[i].pid = 0; + check = 1; + break; + } + } + } + + taia_now(&now); + if (now.sec.x < (stampcheck.sec.x - 3)) { + /* time warp */ + warnx("time warp: resetting time stamp"); + taia_now(&stampcheck); + taia_now(&now); + if (rplog) taia_now(&stamplog); + } + if (taia_less(&now, &stampcheck) == 0) { + /* wait at least a second */ + taia_uint(&deadline, 1); + taia_add(&stampcheck, &now, &deadline); + + if (stat(svdir, &s) != -1) { + if (check || s.st_mtime != mtime + || s.st_ino != ino || s.st_dev != dev + ) { + /* svdir modified */ + if (chdir(svdir) != -1) { + mtime = s.st_mtime; + dev = s.st_dev; + ino = s.st_ino; + check = 0; + if (now.sec.x <= (4611686018427387914ULL + (uint64_t)mtime)) + sleep(1); + runsvdir(); + while (fchdir(curdir) == -1) { + warn2_cannot("change directory, pausing", ""); + sleep(5); + } + } else + warn2_cannot("change directory to ", svdir); + } + } else + warn2_cannot("stat ", svdir); + } + + if (rplog) { + if (taia_less(&now, &stamplog) == 0) { + write(logpipe[1], ".", 1); + taia_uint(&deadline, 900); + taia_add(&stamplog, &now, &deadline); + } + } + taia_uint(&deadline, check ? 1 : 5); + taia_add(&deadline, &now, &deadline); + + sig_block(sig_child); + if (rplog) + iopause(io, 1, &deadline, &now); + else + iopause(0, 0, &deadline, &now); + sig_unblock(sig_child); + + if (rplog && (io[0].revents | IOPAUSE_READ)) + while (read(logpipe[0], &ch, 1) > 0) + if (ch) { + for (i = 6; i < rploglen; i++) + rplog[i-1] = rplog[i]; + rplog[rploglen-1] = ch; + } + + switch (exitsoon) { + case 1: + _exit(0); + case 2: + for (i = 0; i < svnum; i++) + if (sv[i].pid) + kill(sv[i].pid, SIGTERM); + _exit(111); + } + } + /* not reached */ + return 0; +} diff --git a/runit/sv.c b/runit/sv.c new file mode 100644 index 000000000..819f31419 --- /dev/null +++ b/runit/sv.c @@ -0,0 +1,360 @@ +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +static char *action; +static char *acts; +static char *varservice = "/var/service/"; +static char **service; +static char **servicex; +static unsigned services; +static unsigned rc = 0; +static unsigned verbose = 0; +static unsigned long waitsec = 7; +static unsigned kll = 0; +static struct taia tstart, tnow, tdiff; +static struct tai tstatus; + +static int (*act)(char*) = 0; +static int (*cbk)(char*) = 0; + +static int curdir, fd, r; +static char svstatus[20]; + +#define usage() bb_show_usage() + +static void fatal_cannot(char *m1) +{ + bb_perror_msg("fatal: cannot %s", m1); + _exit(151); +} + +static void out(char *p, char *m1) +{ + printf("%s%s: %s", p, *service, m1); + if (errno) { + printf(": %s", strerror(errno)); + } + puts(""); /* will also flush the output */ +} + +#define FAIL "fail: " +#define WARN "warning: " +#define OK "ok: " +#define RUN "run: " +#define FINISH "finish: " +#define DOWN "down: " +#define TIMEOUT "timeout: " +#define KILL "kill: " + +static void fail(char *m1) { ++rc; out(FAIL, m1); } +static void failx(char *m1) { errno = 0; fail(m1); } +static void warn_cannot(char *m1) { ++rc; out("warning: cannot ", m1); } +static void warnx_cannot(char *m1) { errno = 0; warn_cannot(m1); } +static void ok(char *m1) { errno = 0; out(OK, m1); } + +static int svstatus_get(void) +{ + if ((fd = open_write("supervise/ok")) == -1) { + if (errno == ENODEV) { + *acts == 'x' ? ok("runsv not running") + : failx("runsv not running"); + return 0; + } + warn_cannot("open supervise/ok"); + return -1; + } + close(fd); + if ((fd = open_read("supervise/status")) == -1) { + warn_cannot("open supervise/status"); + return -1; + } + r = read(fd, svstatus, 20); + close(fd); + switch(r) { + case 20: break; + case -1: warn_cannot("read supervise/status"); return -1; + default: warnx_cannot("read supervise/status: bad format"); return -1; + } + return 1; +} + +static unsigned svstatus_print(char *m) +{ + int pid; + int normallyup = 0; + struct stat s; + + if (stat("down", &s) == -1) { + if (errno != ENOENT) { + bb_perror_msg(WARN"cannot stat %s/down", *service); + return 0; + } + normallyup = 1; + } + pid = (unsigned char) svstatus[15]; + pid <<= 8; pid += (unsigned char)svstatus[14]; + pid <<= 8; pid += (unsigned char)svstatus[13]; + pid <<= 8; pid += (unsigned char)svstatus[12]; + tai_unpack(svstatus, &tstatus); + if (pid) { + switch (svstatus[19]) { + case 1: printf(RUN); break; + case 2: printf(FINISH); break; + } + printf("%s: (pid %d) ", m, pid); + } + else { + printf(DOWN"%s: ", m); + } + printf("%lus", (unsigned long)(tnow.sec.x < tstatus.x ? 0 : tnow.sec.x-tstatus.x)); + if (pid && !normallyup) printf(", normally down"); + if (!pid && normallyup) printf(", normally up"); + if (pid && svstatus[16]) printf(", paused"); + if (!pid && (svstatus[17] == 'u')) printf(", want up"); + if (pid && (svstatus[17] == 'd')) printf(", want down"); + if (pid && svstatus[18]) printf(", got TERM"); + return pid ? 1 : 2; +} + +static int status(char *unused) +{ + r = svstatus_get(); + switch(r) { case -1: case 0: return 0; } + r = svstatus_print(*service); + if (chdir("log") == -1) { + if (errno != ENOENT) { + printf("; log: "WARN"cannot change to log service directory: %s", + strerror(errno)); + } + } else if (svstatus_get()) { + printf("; "); + svstatus_print("log"); + } + puts(""); /* will also flush the output */ + return r; +} + +static int checkscript(void) +{ + char *prog[2]; + struct stat s; + int pid, w; + + if (stat("check", &s) == -1) { + if (errno == ENOENT) return 1; + bb_perror_msg(WARN"cannot stat %s/check", *service); + return 0; + } + /* if (!(s.st_mode & S_IXUSR)) return 1; */ + if ((pid = fork()) == -1) { + bb_perror_msg(WARN"cannot fork for %s/check", *service); + return 0; + } + if (!pid) { + prog[0] = "./check"; + prog[1] = 0; + close(1); + execve("check", prog, environ); + bb_perror_msg(WARN"cannot run %s/check", *service); + _exit(0); + } + while (wait_pid(&w, pid) == -1) { + if (errno == EINTR) continue; + bb_perror_msg(WARN"cannot wait for child %s/check", *service); + return 0; + } + return !wait_exitcode(w); +} + +static int check(char *a) +{ + unsigned pid; + + if ((r = svstatus_get()) == -1) return -1; + if (r == 0) { if (*a == 'x') return 1; return -1; } + pid = (unsigned char)svstatus[15]; + pid <<= 8; pid += (unsigned char)svstatus[14]; + pid <<= 8; pid += (unsigned char)svstatus[13]; + pid <<= 8; pid += (unsigned char)svstatus[12]; + switch (*a) { + case 'x': return 0; + case 'u': + if (!pid || svstatus[19] != 1) return 0; + if (!checkscript()) return 0; + break; + case 'd': if (pid) return 0; break; + case 'c': if (pid) if (!checkscript()) return 0; break; + case 't': + if (!pid && svstatus[17] == 'd') break; + tai_unpack(svstatus, &tstatus); + if ((tstart.sec.x > tstatus.x) || !pid || svstatus[18] || !checkscript()) + return 0; + break; + case 'o': + tai_unpack(svstatus, &tstatus); + if ((!pid && tstart.sec.x > tstatus.x) || (pid && svstatus[17] != 'd')) + return 0; + } + printf(OK); svstatus_print(*service); puts(""); /* will also flush the output */ + return 1; +} + +static int control(char *a) +{ + if (svstatus_get() <= 0) return -1; + if (svstatus[17] == *a) return 0; + if ((fd = open_write("supervise/control")) == -1) { + if (errno != ENODEV) + warn_cannot("open supervise/control"); + else + *a == 'x' ? ok("runsv not running") : failx("runsv not running"); + return -1; + } + r = write(fd, a, strlen(a)); + close(fd); + if (r != strlen(a)) { + warn_cannot("write to supervise/control"); + return -1; + } + return 1; +} + +int sv_main(int argc, char **argv) +{ + unsigned opt; + unsigned i, want_exit; + char *x; + + for (i = strlen(*argv); i; --i) + if ((*argv)[i-1] == '/') + break; + *argv += i; + service = argv; + services = 1; + if ((x = getenv("SVDIR"))) varservice = x; + if ((x = getenv("SVWAIT"))) waitsec = xatoul(x); + /* TODO: V can be handled internally by getopt_ulflags */ + opt = getopt32(argc, argv, "w:vV", &x); + if (opt & 1) waitsec = xatoul(x); + if (opt & 2) verbose = 1; + if (opt & 4) usage(); + if (!(action = *argv++)) usage(); + --argc; + service = argv; services = argc; + if (!*service) usage(); + + taia_now(&tnow); tstart = tnow; + if ((curdir = open_read(".")) == -1) + fatal_cannot("open current directory"); + + act = &control; acts = "s"; + if (verbose) cbk = ✓ + switch (*action) { + case 'x': case 'e': + acts = "x"; break; + case 'X': case 'E': + acts = "x"; kll = 1; cbk = ✓ break; + case 'D': + acts = "d"; kll = 1; cbk = ✓ break; + case 'T': + acts = "tc"; kll = 1; cbk = ✓ break; + case 'c': + if (!str_diff(action, "check")) { + act = 0; + acts = "c"; + cbk = ✓ + break; + } + case 'u': case 'd': case 'o': case 't': case 'p': case 'h': + case 'a': case 'i': case 'k': case 'q': case '1': case '2': + action[1] = 0; acts = action; break; + case 's': + if (!str_diff(action, "shutdown")) { + acts = "x"; + cbk = ✓ + break; + } + if (!str_diff(action, "start")) { + acts = "u"; + cbk = ✓ + break; + } + if (!str_diff(action, "stop")) { + acts = "d"; + cbk = ✓ + break; + } + act = &status; cbk = 0; break; + case 'r': + if (!str_diff(action, "restart")) { + acts = "tcu"; + cbk = ✓ + break; + } + usage(); + case 'f': + if (!str_diff(action, "force-reload")) + { acts = "tc"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-restart")) + { acts = "tcu"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-shutdown")) + { acts = "x"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-stop")) + { acts = "d"; kll = 1; cbk = ✓ break; } + default: + usage(); + } + + servicex = service; + for (i = 0; i < services; ++i) { + if ((**service != '/') && (**service != '.')) { + if ((chdir(varservice) == -1) || (chdir(*service) == -1)) { + fail("cannot change to service directory"); + *service = 0; + } + } else if (chdir(*service) == -1) { + fail("cannot change to service directory"); + *service = 0; + } + if (*service) if (act && (act(acts) == -1)) *service = 0; + if (fchdir(curdir) == -1) fatal_cannot("change to original directory"); + service++; + } + + if (*cbk) + for (;;) { + taia_sub(&tdiff, &tnow, &tstart); + service = servicex; want_exit = 1; + for (i = 0; i < services; ++i, ++service) { + if (!*service) continue; + if ((**service != '/') && (**service != '.')) { + if ((chdir(varservice) == -1) || (chdir(*service) == -1)) { + fail("cannot change to service directory"); + *service = 0; + } + } else if (chdir(*service) == -1) { + fail("cannot change to service directory"); + *service = 0; + } + if (*service) { if (cbk(acts) != 0) *service = 0; else want_exit = 0; } + if (*service && taia_approx(&tdiff) > waitsec) { + kll ? printf(KILL) : printf(TIMEOUT); + if (svstatus_get() > 0) { svstatus_print(*service); ++rc; } + puts(""); /* will also flush the output */ + if (kll) control("k"); + *service = 0; + } + if (fchdir(curdir) == -1) + fatal_cannot("change to original directory"); + } + if (want_exit) break; + usleep(420000); + taia_now(&tnow); + } + return rc > 99 ? 99 : rc; +} diff --git a/runit/svlogd.c b/runit/svlogd.c new file mode 100644 index 000000000..7024c3db4 --- /dev/null +++ b/runit/svlogd.c @@ -0,0 +1,878 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +static unsigned verbose; +static int linemax = 1000; +static int buflen = 1024; +static int linelen; + +static char **fndir; +static int fdwdir; +static int wstat; +static struct taia trotate; + +static char *line; +static unsigned exitasap; +static unsigned rotateasap; +static unsigned reopenasap; +static unsigned linecomplete = 1; +static unsigned tmaxflag; +static iopause_fd in; + +static const char *replace = ""; +static char repl; + +static struct logdir { + char *btmp; + /* pattern list to match, in "aa\0bb\0\cc\0\0" form */ + char *inst; + char *processor; + char *name; + unsigned size; + unsigned sizemax; + unsigned nmax; + unsigned nmin; + /* int (not long) because of taia_uint() usage: */ + unsigned tmax; + int ppid; + int fddir; + int fdcur; + int fdlock; + struct taia trotate; + char fnsave[FMT_PTIME]; + char match; + char matcherr; +} *dir; +static unsigned dirn = 0; + +#define FATAL "fatal: " +#define WARNING "warning: " +#define PAUSE "pausing: " +#define INFO "info: " + +#define usage() bb_show_usage() +static void fatalx(char *m0) +{ + bb_error_msg_and_die(FATAL"%s", m0); +} +static void warn(char *m0) { + bb_perror_msg(WARNING"%s", m0); +} +static void warn2(char *m0, char *m1) +{ + bb_perror_msg(WARNING"%s: %s", m0, m1); +} +static void warnx(char *m0, char *m1) +{ + bb_error_msg(WARNING"%s: %s", m0, m1); +} +static void pause_nomem(void) +{ + bb_error_msg(PAUSE"out of memory"); sleep(3); +} +static void pause1cannot(char *m0) +{ + bb_perror_msg(PAUSE"cannot %s", m0); sleep(3); +} +static void pause2cannot(char *m0, char *m1) +{ + bb_perror_msg(PAUSE"cannot %s %s", m0, m1); + sleep(3); +} + +static char* wstrdup(const char *str) +{ + char *s; + while (!(s = strdup(str))) pause_nomem(); + return s; +} + +static unsigned processorstart(struct logdir *ld) +{ + int pid; + + if (!ld->processor) return 0; + if (ld->ppid) { + warnx("processor already running", ld->name); + return 0; + } + while ((pid = fork()) == -1) + pause2cannot("fork for processor", ld->name); + if (!pid) { + char *prog[4]; + int fd; + + /* child */ + sig_uncatch(sig_term); + sig_uncatch(sig_alarm); + sig_uncatch(sig_hangup); + sig_unblock(sig_term); + sig_unblock(sig_alarm); + sig_unblock(sig_hangup); + + if (verbose) + bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave); + fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY); + if (fd_move(0, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + ld->fnsave[26] = 't'; + fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT); + if (fd_move(1, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + fd = open_read("state"); + if (fd == -1) { + if (errno != ENOENT) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "open state for", ld->name); + close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT)); + fd = xopen("state", O_RDONLY|O_NDELAY); + } + if (fd_move(4, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT); + if (fd_move(5, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + + prog[0] = "sh"; + prog[1] = "-c"; + prog[2] = ld->processor; + prog[3] = '\0'; + execve("/bin/sh", prog, environ); + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "run", ld->name); + } + ld->ppid = pid; + return 1; +} + +static unsigned processorstop(struct logdir *ld) +{ + char f[28]; + + if (ld->ppid) { + sig_unblock(sig_hangup); + while (wait_pid(&wstat, ld->ppid) == -1) + pause2cannot("wait for processor", ld->name); + sig_block(sig_hangup); + ld->ppid = 0; + } + if (ld->fddir == -1) return 1; + while (fchdir(ld->fddir) == -1) + pause2cannot("change directory, want processor", ld->name); + if (wait_exitcode(wstat) != 0) { + warnx("processor failed, restart", ld->name); + ld->fnsave[26] = 't'; + unlink(ld->fnsave); + ld->fnsave[26] = 'u'; + processorstart(ld); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return ld->processor ? 0 : 1; + } + ld->fnsave[26] = 't'; + memcpy(f, ld->fnsave, 26); + f[26] = 's'; + f[27] = '\0'; + while (rename(ld->fnsave, f) == -1) + pause2cannot("rename processed", ld->name); + while (chmod(f, 0744) == -1) + pause2cannot("set mode of processed", ld->name); + ld->fnsave[26] = 'u'; + if (unlink(ld->fnsave) == -1) + bb_error_msg(WARNING"cannot unlink: %s/%s", ld->name, ld->fnsave); + while (rename("newstate", "state") == -1) + pause2cannot("rename state", ld->name); + if (verbose) bb_error_msg(INFO"processed: %s/%s", ld->name, f); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 1; +} + +static void rmoldest(struct logdir *ld) +{ + DIR *d; + struct dirent *f; + char oldest[FMT_PTIME]; + int n = 0; + + oldest[0] = 'A'; oldest[1] = oldest[27] = 0; + while (!(d = opendir("."))) + pause2cannot("open directory, want rotate", ld->name); + errno = 0; + while ((f = readdir(d))) { + if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) { + if (f->d_name[26] == 't') { + if (unlink(f->d_name) == -1) + warn2("cannot unlink processor leftover", f->d_name); + } else { + ++n; + if (strcmp(f->d_name, oldest) < 0) + memcpy(oldest, f->d_name, 27); + } + errno = 0; + } + } + if (errno) warn2("cannot read directory", ld->name); + closedir(d); + + if (ld->nmax && (n > ld->nmax)) { + if (verbose) bb_error_msg(INFO"delete: %s/%s", ld->name, oldest); + if ((*oldest == '@') && (unlink(oldest) == -1)) + warn2("cannot unlink oldest logfile", ld->name); + } +} + +static unsigned rotate(struct logdir *ld) +{ + struct stat st; + struct taia now; + + if (ld->fddir == -1) { + ld->tmax = 0; + return 0; + } + if (ld->ppid) + while(!processorstop(ld)) + /* wait */; + + while (fchdir(ld->fddir) == -1) + pause2cannot("change directory, want rotate", ld->name); + + /* create new filename */ + ld->fnsave[25] = '.'; + ld->fnsave[26] = 's'; + if (ld->processor) + ld->fnsave[26] = 'u'; + ld->fnsave[27] = '\0'; + do { + taia_now(&now); + fmt_taia(ld->fnsave, &now); + errno = 0; + } while ((stat(ld->fnsave, &st) != -1) || (errno != ENOENT)); + + if (ld->tmax && taia_less(&ld->trotate, &now)) { + taia_uint(&ld->trotate, ld->tmax); + taia_add(&ld->trotate, &now, &ld->trotate); + if (taia_less(&ld->trotate, &trotate)) + trotate = ld->trotate; + } + + if (ld->size > 0) { + while (fsync(ld->fdcur) == -1) + pause2cannot("fsync current logfile", ld->name); + while (fchmod(ld->fdcur, 0744) == -1) + pause2cannot("set mode of current", ld->name); + close(ld->fdcur); + if (verbose) { + bb_error_msg(INFO"rename: %s/current %s %u", ld->name, + ld->fnsave, ld->size); + } + while (rename("current", ld->fnsave) == -1) + pause2cannot("rename current", ld->name); + while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1) + pause2cannot("create new current", ld->name); + coe(ld->fdcur); + ld->size = 0; + while (fchmod(ld->fdcur, 0644) == -1) + pause2cannot("set mode of current", ld->name); + rmoldest(ld); + processorstart(ld); + } + + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 1; +} + +static int buffer_pwrite(int n, char *s, unsigned len) +{ + int i; + struct logdir *ld = &dir[n]; + + if (ld->sizemax) { + if (ld->size >= ld->sizemax) + rotate(ld); + if (len > (ld->sizemax - ld->size)) + len = ld->sizemax - ld->size; + } + while ((i = write(ld->fdcur, s, len)) == -1) { + if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) { + DIR *d; + struct dirent *f; + char oldest[FMT_PTIME]; + int j = 0; + + while (fchdir(ld->fddir) == -1) + pause2cannot("change directory, want remove old logfile", + ld->name); + oldest[0] = 'A'; + oldest[1] = oldest[27] = '\0'; + while (!(d = opendir("."))) + pause2cannot("open directory, want remove old logfile", + ld->name); + errno = 0; + while ((f = readdir(d))) + if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) { + ++j; + if (strcmp(f->d_name, oldest) < 0) + memcpy(oldest, f->d_name, 27); + } + if (errno) warn2("cannot read directory, want remove old logfile", + ld->name); + closedir(d); + errno = ENOSPC; + if (j > ld->nmin) { + if (*oldest == '@') { + bb_error_msg(WARNING"out of disk space, delete: %s/%s", + ld->name, oldest); + errno = 0; + if (unlink(oldest) == -1) { + warn2("cannot unlink oldest logfile", ld->name); + errno = ENOSPC; + } + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + } + } + } + if (errno) pause2cannot("write to current", ld->name); + } + + ld->size += i; + if (ld->sizemax) + if (s[i-1] == '\n') + if (ld->size >= (ld->sizemax - linemax)) + rotate(ld); + return i; +} + +static void logdir_close(struct logdir *ld) +{ + if (ld->fddir == -1) + return; + if (verbose) + bb_error_msg(INFO"close: %s", ld->name); + close(ld->fddir); + ld->fddir = -1; + if (ld->fdcur == -1) + return; /* impossible */ + while (fsync(ld->fdcur) == -1) + pause2cannot("fsync current logfile", ld->name); + while (fchmod(ld->fdcur, 0744) == -1) + pause2cannot("set mode of current", ld->name); + close(ld->fdcur); + ld->fdcur = -1; + if (ld->fdlock == -1) + return; /* impossible */ + close(ld->fdlock); + ld->fdlock = -1; + free(ld->processor); + ld->processor = NULL; +} + +static unsigned logdir_open(struct logdir *ld, const char *fn) +{ + char buf[128]; + struct taia now; + char *new, *s, *np; + int i; + struct stat st; + + ld->fddir = open(fn, O_RDONLY|O_NDELAY); + if (ld->fddir == -1) { + warn2("cannot open log directory", (char*)fn); + return 0; + } + coe(ld->fddir); + if (fchdir(ld->fddir) == -1) { + logdir_close(ld); + warn2("cannot change directory", (char*)fn); + return 0; + } + ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if ((ld->fdlock == -1) + || (lock_exnb(ld->fdlock) == -1) + ) { + logdir_close(ld); + warn2("cannot lock directory", (char*)fn); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 0; + } + coe(ld->fdlock); + + ld->size = 0; + ld->sizemax = 1000000; + ld->nmax = ld->nmin = 10; + ld->tmax = 0; + ld->name = (char*)fn; + ld->ppid = 0; + ld->match = '+'; + free(ld->inst); ld->inst = NULL; + free(ld->processor); ld->processor = NULL; + + /* read config */ + i = open_read_close("config", buf, sizeof(buf)); + if (i < 0) + warn2("cannot read config", ld->name); + if (i > 0) { + if (verbose) bb_error_msg(INFO"read: %s/config", ld->name); + s = buf; + while (s) { + np = strchr(s, '\n'); + if (np) *np++ = '\0'; + switch (s[0]) { + case '+': + case '-': + case 'e': + case 'E': + while (1) { + int l = asprintf(&new, "%s%s\n", ld->inst?:"", s); + if (l >= 0 && new) break; + pause_nomem(); + } + free(ld->inst); + ld->inst = new; + break; + case 's': { + static const struct suffix_mult km_suffixes[] = { + { "k", 1024 }, + { "m", 1024*1024 }, + { NULL, 0 } + }; + ld->sizemax = xatou_sfx(&s[1], km_suffixes); + break; + } + case 'n': + ld->nmax = xatoi_u(&s[1]); + break; + case 'N': + ld->nmin = xatoi_u(&s[1]); + break; + case 't': { + static const struct suffix_mult mh_suffixes[] = { + { "m", 60 }, + { "h", 60*60 }, + /*{ "d", 24*60*60 },*/ + { NULL, 0 } + }; + ld->tmax = xatou_sfx(&s[1], mh_suffixes); + if (ld->tmax) { + taia_uint(&ld->trotate, ld->tmax); + taia_add(&ld->trotate, &now, &ld->trotate); + if (!tmaxflag || taia_less(&ld->trotate, &trotate)) + trotate = ld->trotate; + tmaxflag = 1; + } + break; + } + case '!': + if (s[1]) { + free(ld->processor); + ld->processor = wstrdup(s); + } + break; + } + s = np; + } + /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */ + s = ld->inst; + while (s) { + np = strchr(s, '\n'); + if (np) *np++ = '\0'; + s = np; + } + } + + /* open current */ + i = stat("current", &st); + if (i != -1) { + if (st.st_size && ! (st.st_mode & S_IXUSR)) { + ld->fnsave[25] = '.'; + ld->fnsave[26] = 'u'; + ld->fnsave[27] = '\0'; + do { + taia_now(&now); + fmt_taia(ld->fnsave, &now); + errno = 0; + } while ((stat(ld->fnsave, &st) != -1) || (errno != ENOENT)); + while (rename("current", ld->fnsave) == -1) + pause2cannot("rename current", ld->name); + rmoldest(ld); + i = -1; + } else { + /* Be paranoid: st.st_size can be not just bigger, but WIDER! */ + /* (bug in original svlogd. remove this comment when fixed there) */ + ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size; + } + } else { + if (errno != ENOENT) { + logdir_close(ld); + warn2("cannot stat current", ld->name); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 0; + } + } + while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1) + pause2cannot("open current", ld->name); + coe(ld->fdcur); + while (fchmod(ld->fdcur, 0644) == -1) + pause2cannot("set mode of current", ld->name); + + if (verbose) { + if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name); + else bb_error_msg(INFO"new: %s/current", ld->name); + } + + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 1; +} + +static void logdirs_reopen(void) +{ + struct taia now; + int l; + int ok = 0; + + tmaxflag = 0; + taia_now(&now); + for (l = 0; l < dirn; ++l) { + logdir_close(&dir[l]); + if (logdir_open(&dir[l], fndir[l])) ok = 1; + } + if (!ok) fatalx("no functional log directories"); +} + +/* Used for reading stdin */ +static int buffer_pread(int fd, char *s, unsigned len) +{ + struct taia now; + int i; + + if (rotateasap) { + for (i = 0; i < dirn; ++i) + rotate(dir+i); + rotateasap = 0; + } + if (exitasap) { + if (linecomplete) + return 0; + len = 1; + } + if (reopenasap) { + logdirs_reopen(); + reopenasap = 0; + } + taia_now(&now); + taia_uint(&trotate, 2744); + taia_add(&trotate, &now, &trotate); + for (i = 0; i < dirn; ++i) + if (dir[i].tmax) { + if (taia_less(&dir[i].trotate, &now)) + rotate(dir+i); + if (taia_less(&dir[i].trotate, &trotate)) + trotate = dir[i].trotate; + } + + while (1) { + /* Comment? */ + sig_unblock(sig_term); + sig_unblock(sig_child); + sig_unblock(sig_alarm); + sig_unblock(sig_hangup); + iopause(&in, 1, &trotate, &now); + sig_block(sig_term); + sig_block(sig_child); + sig_block(sig_alarm); + sig_block(sig_hangup); + i = safe_read(fd, s, len); + if (i >= 0) break; + if (errno != EAGAIN) { + warn("cannot read standard input"); + break; + } + /* else: EAGAIN - normal, repeat silently */ + } + + if (i > 0) { + int cnt; + linecomplete = (s[i-1] == '\n'); + if (!repl) return i; + + cnt = i; + while (--cnt >= 0) { + char ch = *s; + if (ch != '\n') { + if (ch < 32 || ch > 126) + *s = repl; + else { + int j; + for (j = 0; replace[j]; ++j) { + if (ch == replace[j]) { + *s = repl; + break; + } + } + } + } + s++; + } + } + return i; +} + + +static void sig_term_handler(int sig_no) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "term"); + exitasap = 1; +} + +static void sig_child_handler(int sig_no) +{ + int pid, l; + + if (verbose) + bb_error_msg(INFO"sig%s received", "child"); + while ((pid = wait_nohang(&wstat)) > 0) + for (l = 0; l < dirn; ++l) + if (dir[l].ppid == pid) { + dir[l].ppid = 0; + processorstop(&dir[l]); + break; + } +} + +static void sig_alarm_handler(int sig_no) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "alarm"); + rotateasap = 1; +} + +static void sig_hangup_handler(int sig_no) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "hangup"); + reopenasap = 1; +} + +static void logmatch(struct logdir *ld) +{ + char *s; + + ld->match = '+'; + ld->matcherr = 'E'; + s = ld->inst; + while (s && s[0]) { + switch (s[0]) { + case '+': + case '-': + if (pmatch(s+1, line, linelen)) + ld->match = s[0]; + break; + case 'e': + case 'E': + if (pmatch(s+1, line, linelen)) + ld->matcherr = s[0]; + break; + } + s += strlen(s) + 1; + } +} + +int svlogd_main(int argc, char **argv) +{ + struct taia now; + char *r,*l,*b; + ssize_t stdin_cnt = 0; + int i; + unsigned opt; + unsigned timestamp = 0; + + opt_complementary = "tt:vv"; + opt = getopt32(argc, argv, "r:R:l:b:tv", + &r, &replace, &l, &b, ×tamp, &verbose); + if (opt & 1) { // -r + repl = r[0]; + if (!repl || r[1]) usage(); + } + if (opt & 2) if (!repl) repl = '_'; // -R + if (opt & 4) { // -l + linemax = xatou_range(l, 0, 1000); + if (linemax == 0) linemax = 1000; + if (linemax < 256) linemax = 256; + } + if (opt & 8) { // -b + buflen = xatoi_u(b); + if (buflen == 0) buflen = 1024; + } + //if (opt & 0x10) timestamp++; // -t + //if (opt & 0x20) verbose++; // -v + if (timestamp > 2) timestamp = 2; + argv += optind; + argc -= optind; + + dirn = argc; + if (dirn <= 0) usage(); + if (buflen <= linemax) usage(); + fdwdir = xopen(".", O_RDONLY|O_NDELAY); + coe(fdwdir); + dir = xmalloc(dirn * sizeof(struct logdir)); + for (i = 0; i < dirn; ++i) { + dir[i].fddir = -1; + dir[i].fdcur = -1; + dir[i].btmp = xmalloc(buflen); + dir[i].ppid = 0; + } + line = xmalloc(linemax + (timestamp ? 26 : 0)); + fndir = argv; + in.fd = 0; + in.events = IOPAUSE_READ; + ndelay_on(in.fd); + + sig_block(sig_term); + sig_block(sig_child); + sig_block(sig_alarm); + sig_block(sig_hangup); + sig_catch(sig_term, sig_term_handler); + sig_catch(sig_child, sig_child_handler); + sig_catch(sig_alarm, sig_alarm_handler); + sig_catch(sig_hangup, sig_hangup_handler); + + logdirs_reopen(); + + /* Each iteration processes one line */ + while (1) { + int printlen; + char *lineptr = line; + char *np; + char ch; + + /* Prepare timestamp if needed */ + if (timestamp) { + char stamp[FMT_PTIME]; + taia_now(&now); + switch (timestamp) { + case 1: + fmt_taia(stamp, &now); + break; + default: /* case 2: */ + fmt_ptime(stamp, &now); + break; + } + memcpy(line, stamp, 25); + line[25] = ' '; + lineptr += 26; + } + + /* lineptr[0..linemax-1] - buffer for stdin */ + /* (possibly has some unprocessed data from prev loop) */ + + /* Refill the buffer if needed */ + np = memchr(lineptr, '\n', stdin_cnt); + i = linemax - stdin_cnt; /* avail. bytes at tail */ + if (i >= 128 && !exitasap && !np) { + int sz = buffer_pread(0, lineptr + stdin_cnt, i); + if (sz <= 0) /* EOF or error on stdin */ + exitasap = 1; + else { + stdin_cnt += sz; + np = memchr(lineptr, '\n', stdin_cnt); + } + } + if (stdin_cnt <= 0 && exitasap) + break; + + /* Search for '\n' (in fact, np already holds the result) */ + linelen = stdin_cnt; + if (np) linelen = np - lineptr + 1; + /* linelen == no of chars incl. '\n' (or == stdin_cnt) */ + ch = lineptr[linelen-1]; + + printlen = linelen + (timestamp ? 26 : 0); + /* write out line[0..printlen-1] to each log destination */ + for (i = 0; i < dirn; ++i) { + struct logdir *ld = &dir[i]; + if (ld->fddir == -1) continue; + if (ld->inst) + logmatch(ld); + if (ld->matcherr == 'e') + full_write(2, line, printlen); + if (ld->match != '+') continue; + buffer_pwrite(i, line, printlen); + } + + /* If we didn't see '\n' (long input line), */ + /* read/write repeatedly until we see it */ + while (ch != '\n') { + /* lineptr is emptied now, safe to use as buffer */ + stdin_cnt = exitasap ? -1 : buffer_pread(0, lineptr, linemax); + if (stdin_cnt <= 0) { /* EOF or error on stdin */ + lineptr[0] = ch = '\n'; + linelen = 1; + exitasap = 1; + stdin_cnt = 1; + } else { + linelen = stdin_cnt; + np = memchr(lineptr, '\n', stdin_cnt); + if (np) linelen = np - lineptr + 1; + ch = lineptr[linelen-1]; + } + /* linelen == no of chars incl. '\n' (or == stdin_cnt) */ + for (i = 0; i < dirn; ++i) { + if (dir[i].fddir == -1) continue; + if (dir[i].matcherr == 'e') + full_write(2, lineptr, linelen); + if (dir[i].match != '+') continue; + buffer_pwrite(i, lineptr, linelen); + } + } + + /* Move unprocessed data to the front of line */ + stdin_cnt -= linelen; + if (stdin_cnt > 0) /* TODO: slow if buffer is big */ + memmove(lineptr, &lineptr[linelen], stdin_cnt); + } + + for (i = 0; i < dirn; ++i) { + if (dir[i].ppid) + while (!processorstop(&dir[i])) + /* repeat */; + logdir_close(&dir[i]); + } + _exit(0); +} diff --git a/scripts/Kbuild b/scripts/Kbuild new file mode 100644 index 000000000..83b423253 --- /dev/null +++ b/scripts/Kbuild @@ -0,0 +1,7 @@ +### +# scripts contains sources for various helper programs used throughout +# the kernel for the build process. +# --------------------------------------------------------------------------- + +# Let clean descend into subdirs +subdir- += basic kconfig diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include new file mode 100644 index 000000000..b0d067be7 --- /dev/null +++ b/scripts/Kbuild.include @@ -0,0 +1,148 @@ +#### +# kbuild: Generic definitions + +# Convinient variables +comma := , +squote := ' +empty := +space := $(empty) $(empty) + +### +# The temporary file to save gcc -MD generated dependencies must not +# contain a comma +depfile = $(subst $(comma),_,$(@D)/.$(@F).d) + +### +# Escape single quote for use in echo statements +escsq = $(subst $(squote),'\$(squote)',$1) + +### +# filechk is used to check if the content of a generated file is updated. +# Sample usage: +# define filechk_sample +# echo $KERNELRELEASE +# endef +# version.h : Makefile +# $(call filechk,sample) +# The rule defined shall write to stdout the content of the new file. +# The existing file will be compared with the new one. +# - If no file exist it is created +# - If the content differ the new file is used +# - If they are equal no change, and no timestamp update +# - stdin is piped in from the first prerequisite ($<) so one has +# to specify a valid file as first prerequisite (often the kbuild file) +define filechk + $(Q)set -e; \ + echo ' CHK $@'; \ + mkdir -p $(dir $@); \ + $(filechk_$(1)) < $< > $@.tmp; \ + if [ -r $@ ] && cmp -s $@ $@.tmp; then \ + rm -f $@.tmp; \ + else \ + echo ' UPD $@'; \ + mv -f $@.tmp $@; \ + fi +endef + +###### +# gcc support functions +# See documentation in Documentation/kbuild/makefiles.txt + +# as-option +# Usage: cflags-y += $(call as-option, -Wa$(comma)-isa=foo,) + +as-option = $(shell if $(CC) $(CFLAGS) $(1) -Wa,-Z -c -o /dev/null \ + -xassembler /dev/null > /dev/null 2>&1; then echo "$(1)"; \ + else echo "$(2)"; fi ;) + +# cc-option +# Usage: cflags-y += $(call cc-option, -march=winchip-c6, -march=i586) + +cc-option = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \ + > /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;) + +# cc-option-yn +# Usage: flag := $(call cc-option-yn, -march=winchip-c6) +cc-option-yn = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \ + > /dev/null 2>&1; then echo "y"; else echo "n"; fi;) + +# cc-option-align +# Prefix align with either -falign or -malign +cc-option-align = $(subst -functions=0,,\ + $(call cc-option,-falign-functions=0,-malign-functions=0)) + +# cc-version +# Usage gcc-ver := $(call cc-version, $(CC)) +cc-version = $(shell $(CONFIG_SHELL) $(srctree)/scripts/gcc-version.sh \ + $(if $(1), $(1), $(CC))) + +# cc-ifversion +# Usage: EXTRA_CFLAGS += $(call cc-ifversion, -lt, 0402, -O1) +cc-ifversion = $(shell if [ $(call cc-version, $(CC)) $(1) $(2) ]; then \ + echo $(3); fi;) + +### +# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj= +# Usage: +# $(Q)$(MAKE) $(build)=dir +build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj + +# Prefix -I with $(srctree) if it is not an absolute path +addtree = $(if $(filter-out -I/%,$(1)),$(patsubst -I%,-I$(srctree)/%,$(1))) $(1) +# Find all -I options and call addtree +flags = $(foreach o,$($(1)),$(if $(filter -I%,$(o)),$(call addtree,$(o)),$(o))) + +# If quiet is set, only print short version of command +cmd = @$(echo-cmd) $(cmd_$(1)) + +# Add $(obj)/ for paths that is not absolute +objectify = $(foreach o,$(1),$(if $(filter /%,$(o)),$(o),$(obj)/$(o))) + +### +# if_changed - execute command if any prerequisite is newer than +# target, or command line has changed +# if_changed_dep - as if_changed, but uses fixdep to reveal dependencies +# including used config symbols +# if_changed_rule - as if_changed but execute rule instead +# See Documentation/kbuild/makefiles.txt for more info + +ifneq ($(KBUILD_NOCMDDEP),1) +# Check if both arguments has same arguments. Result in empty string if equal +# User may override this check using make KBUILD_NOCMDDEP=1 +arg-check = $(strip $(filter-out $(1), $(2)) $(filter-out $(2), $(1)) ) +endif + +# echo command. Short version is $(quiet) equals quiet, otherwise full command +echo-cmd = $(if $($(quiet)cmd_$(1)), \ + echo ' $(call escsq,$($(quiet)cmd_$(1)))';) + +make-cmd = $(subst \#,\\\#,$(subst $$,$$$$,$(call escsq,$(cmd_$(1))))) + +# function to only execute the passed command if necessary +# >'< substitution is for echo to work, >$< substitution to preserve $ when reloading .cmd file +# note: when using inline perl scripts [perl -e '...$$t=1;...'] in $(cmd_xxx) double $$ your perl vars +# +if_changed = $(if $(strip $(filter-out $(PHONY),$?) \ + $(call arg-check, $(cmd_$(1)), $(cmd_$@)) ), \ + @set -e; \ + $(echo-cmd) $(cmd_$(1)); \ + echo 'cmd_$@ := $(make-cmd)' > $(@D)/.$(@F).cmd) + +# execute the command and also postprocess generated .d dependencies +# file +if_changed_dep = $(if $(strip $(filter-out $(PHONY),$?) \ + $(filter-out FORCE $(wildcard $^),$^) \ + $(call arg-check, $(cmd_$(1)), $(cmd_$@)) ), \ + @set -e; \ + $(echo-cmd) $(cmd_$(1)); \ + scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(@D)/.$(@F).tmp; \ + rm -f $(depfile); \ + mv -f $(@D)/.$(@F).tmp $(@D)/.$(@F).cmd) + +# Usage: $(call if_changed_rule,foo) +# will check if $(cmd_foo) changed, or any of the prequisites changed, +# and if so will execute $(rule_foo) +if_changed_rule = $(if $(strip $(filter-out $(PHONY),$?) \ + $(call arg-check, $(cmd_$(1)), $(cmd_$@)) ),\ + @set -e; \ + $(rule_$(1))) diff --git a/scripts/Makefile.build b/scripts/Makefile.build new file mode 100644 index 000000000..e48e60da3 --- /dev/null +++ b/scripts/Makefile.build @@ -0,0 +1,338 @@ +# ========================================================================== +# Building +# ========================================================================== + +src := $(obj) + +PHONY := __build +__build: + +# Read .config if it exist, otherwise ignore +-include .config + +include scripts/Kbuild.include + +# The filename Kbuild has precedence over Makefile +kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) +include $(if $(wildcard $(kbuild-dir)/Kbuild), $(kbuild-dir)/Kbuild, $(kbuild-dir)/Makefile) + +include scripts/Makefile.lib + +ifdef host-progs +ifneq ($(hostprogs-y),$(host-progs)) +$(warning kbuild: $(obj)/Makefile - Usage of host-progs is deprecated. Please replace with hostprogs-y!) +hostprogs-y += $(host-progs) +endif +endif + +# Do not include host rules unles needed +ifneq ($(hostprogs-y)$(hostprogs-m),) +include scripts/Makefile.host +endif + +ifneq ($(KBUILD_SRC),) +# Create output directory if not already present +_dummy := $(shell [ -d $(obj) ] || mkdir -p $(obj)) + +# Create directories for object files if directory does not exist +# Needed when obj-y := dir/file.o syntax is used +_dummy := $(foreach d,$(obj-dirs), $(shell [ -d $(d) ] || mkdir -p $(d))) +endif + + +ifdef EXTRA_TARGETS +$(warning kbuild: $(obj)/Makefile - Usage of EXTRA_TARGETS is obsolete in 2.6. Please fix!) +endif + +ifdef build-targets +$(warning kbuild: $(obj)/Makefile - Usage of build-targets is obsolete in 2.6. Please fix!) +endif + +ifdef export-objs +$(warning kbuild: $(obj)/Makefile - Usage of export-objs is obsolete in 2.6. Please fix!) +endif + +ifdef O_TARGET +$(warning kbuild: $(obj)/Makefile - Usage of O_TARGET := $(O_TARGET) is obsolete in 2.6. Please fix!) +endif + +ifdef L_TARGET +$(error kbuild: $(obj)/Makefile - Use of L_TARGET is replaced by lib-y in 2.6. Please fix!) +endif + +ifdef list-multi +$(warning kbuild: $(obj)/Makefile - list-multi := $(list-multi) is obsolete in 2.6. Please fix!) +endif + +ifndef obj +$(warning kbuild: Makefile.build is included improperly) +endif + +# =========================================================================== + +ifneq ($(strip $(lib-y) $(lib-m) $(lib-n) $(lib-)),) +lib-target := $(obj)/lib.a +endif + +ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(lib-target)),) +builtin-target := $(obj)/built-in.o +endif + +# We keep a list of all modules in $(MODVERDIR) + +__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ + $(if $(KBUILD_MODULES),$(obj-m)) \ + $(subdir-ym) $(always) + @: + +# Linus' kernel sanity checking tool +ifneq ($(KBUILD_CHECKSRC),0) + ifeq ($(KBUILD_CHECKSRC),2) + quiet_cmd_force_checksrc = CHECK $< + cmd_force_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< ; + else + quiet_cmd_checksrc = CHECK $< + cmd_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< ; + endif +endif + + +# Compile C sources (.c) +# --------------------------------------------------------------------------- + +# Default is built-in, unless we know otherwise +modkern_cflags := $(CFLAGS_KERNEL) +quiet_modtag := $(empty) $(empty) + +$(real-objs-m) : modkern_cflags := $(CFLAGS_MODULE) +$(real-objs-m:.o=.i) : modkern_cflags := $(CFLAGS_MODULE) +$(real-objs-m:.o=.s) : modkern_cflags := $(CFLAGS_MODULE) +$(real-objs-m:.o=.lst): modkern_cflags := $(CFLAGS_MODULE) + +$(real-objs-m) : quiet_modtag := [M] +$(real-objs-m:.o=.i) : quiet_modtag := [M] +$(real-objs-m:.o=.s) : quiet_modtag := [M] +$(real-objs-m:.o=.lst): quiet_modtag := [M] + +$(obj-m) : quiet_modtag := [M] + +# Default for not multi-part modules +modname = $(*F) + +$(multi-objs-m) : modname = $(modname-multi) +$(multi-objs-m:.o=.i) : modname = $(modname-multi) +$(multi-objs-m:.o=.s) : modname = $(modname-multi) +$(multi-objs-m:.o=.lst) : modname = $(modname-multi) +$(multi-objs-y) : modname = $(modname-multi) +$(multi-objs-y:.o=.i) : modname = $(modname-multi) +$(multi-objs-y:.o=.s) : modname = $(modname-multi) +$(multi-objs-y:.o=.lst) : modname = $(modname-multi) + +quiet_cmd_cc_s_c = CC $(quiet_modtag) $@ +cmd_cc_s_c = $(CC) $(c_flags) -fverbose-asm -S -o $@ $< + +%.s: %.c FORCE + $(call if_changed_dep,cc_s_c) + +quiet_cmd_cc_i_c = CPP $(quiet_modtag) $@ +cmd_cc_i_c = $(CPP) $(c_flags) -o $@ $< + +%.i: %.c FORCE + $(call if_changed_dep,cc_i_c) + +# C (.c) files +# The C file is compiled and updated dependency information is generated. +# (See cmd_cc_o_c + relevant part of rule_cc_o_c) + +quiet_cmd_cc_o_c = CC $(quiet_modtag) $@ + +ifndef CONFIG_MODVERSIONS +cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< + +else +# When module versioning is enabled the following steps are executed: +# o compile a .tmp_.o from .c +# o if .tmp_.o doesn't contain a __ksymtab version, i.e. does +# not export symbols, we just rename .tmp_.o to .o and +# are done. +# o otherwise, we calculate symbol versions using the good old +# genksyms on the preprocessed source and postprocess them in a way +# that they are usable as a linker script +# o generate .o from .tmp_.o using the linker to +# replace the unresolved symbols __crc_exported_symbol with +# the actual value of the checksum generated by genksyms + +cmd_cc_o_c = $(CC) $(c_flags) -c -o $(@D)/.tmp_$(@F) $< +cmd_modversions = \ + if $(OBJDUMP) -h $(@D)/.tmp_$(@F) | grep -q __ksymtab; then \ + $(CPP) -D__GENKSYMS__ $(c_flags) $< \ + | $(GENKSYMS) -a $(ARCH) \ + > $(@D)/.tmp_$(@F:.o=.ver); \ + \ + $(LD) $(LDFLAGS) -r -o $@ $(@D)/.tmp_$(@F) \ + -T $(@D)/.tmp_$(@F:.o=.ver); \ + rm -f $(@D)/.tmp_$(@F) $(@D)/.tmp_$(@F:.o=.ver); \ + else \ + mv -f $(@D)/.tmp_$(@F) $@; \ + fi; +endif + +define rule_cc_o_c + $(call echo-cmd,checksrc) $(cmd_checksrc) \ + $(call echo-cmd,cc_o_c) $(cmd_cc_o_c); \ + $(cmd_modversions) \ + scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' > $(@D)/.$(@F).tmp; \ + rm -f $(depfile); \ + mv -f $(@D)/.$(@F).tmp $(@D)/.$(@F).cmd +endef + +# Built-in and composite module parts + +%.o: %.c FORCE + $(call cmd,force_checksrc) + $(call if_changed_rule,cc_o_c) + +# Single-part modules are special since we need to mark them in $(MODVERDIR) + +$(single-used-m): %.o: %.c FORCE + $(call cmd,force_checksrc) + $(call if_changed_rule,cc_o_c) + @{ echo $(@:.o=.ko); echo $@; } > $(MODVERDIR)/$(@F:.o=.mod) + +quiet_cmd_cc_lst_c = MKLST $@ + cmd_cc_lst_c = $(CC) $(c_flags) -g -c -o $*.o $< && \ + $(CONFIG_SHELL) $(srctree)/scripts/makelst $*.o \ + System.map $(OBJDUMP) > $@ + +%.lst: %.c FORCE + $(call if_changed_dep,cc_lst_c) + +# Compile assembler sources (.S) +# --------------------------------------------------------------------------- + +modkern_aflags := $(AFLAGS_KERNEL) + +$(real-objs-m) : modkern_aflags := $(AFLAGS_MODULE) +$(real-objs-m:.o=.s): modkern_aflags := $(AFLAGS_MODULE) + +quiet_cmd_as_s_S = CPP $(quiet_modtag) $@ +cmd_as_s_S = $(CPP) $(a_flags) -o $@ $< + +%.s: %.S FORCE + $(call if_changed_dep,as_s_S) + +quiet_cmd_as_o_S = AS $(quiet_modtag) $@ +cmd_as_o_S = $(CC) $(a_flags) -c -o $@ $< + +%.o: %.S FORCE + $(call if_changed_dep,as_o_S) + +targets += $(real-objs-y) $(real-objs-m) $(lib-y) +targets += $(extra-y) $(MAKECMDGOALS) $(always) + +# Linker scripts preprocessor (.lds.S -> .lds) +# --------------------------------------------------------------------------- +quiet_cmd_cpp_lds_S = LDS $@ + cmd_cpp_lds_S = $(CPP) $(cpp_flags) -D__ASSEMBLY__ -o $@ $< + +%.lds: %.lds.S FORCE + $(call if_changed_dep,cpp_lds_S) + +# Build the compiled-in targets +# --------------------------------------------------------------------------- + +# To build objects in subdirs, we need to descend into the directories +$(sort $(subdir-obj-y)): $(subdir-ym) ; + +# +# Rule to compile a set of .o files into one .o file +# +ifdef builtin-target +quiet_cmd_link_o_target = LD $@ +# If the list of objects to link is empty, just create an empty built-in.o +cmd_link_o_target = $(if $(strip $(obj-y)),\ + $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^),\ + rm -f $@; $(AR) rcs $@) + +$(builtin-target): $(obj-y) FORCE + $(call if_changed,link_o_target) + +targets += $(builtin-target) +endif # builtin-target + +# +# Rule to compile a set of .o files into one .a file +# +ifdef lib-target +quiet_cmd_link_l_target = AR $@ +cmd_link_l_target = rm -f $@; $(AR) $(EXTRA_ARFLAGS) rcs $@ $(lib-y) + +$(lib-target): $(lib-y) FORCE + $(call if_changed,link_l_target) + +targets += $(lib-target) +endif + +# +# Rule to link composite objects +# +# Composite objects are specified in kbuild makefile as follows: +# -objs := +# or +# -y := +link_multi_deps = \ +$(filter $(addprefix $(obj)/, \ +$($(subst $(obj)/,,$(@:.o=-objs))) \ +$($(subst $(obj)/,,$(@:.o=-y)))), $^) + +quiet_cmd_link_multi-y = LD $@ +cmd_link_multi-y = $(LD) $(ld_flags) -r -o $@ $(link_multi_deps) + +quiet_cmd_link_multi-m = LD [M] $@ +cmd_link_multi-m = $(LD) $(ld_flags) $(LDFLAGS_MODULE) -o $@ $(link_multi_deps) + +# We would rather have a list of rules like +# foo.o: $(foo-objs) +# but that's not so easy, so we rather make all composite objects depend +# on the set of all their parts +$(multi-used-y) : %.o: $(multi-objs-y) FORCE + $(call if_changed,link_multi-y) + +$(multi-used-m) : %.o: $(multi-objs-m) FORCE + $(call if_changed,link_multi-m) + @{ echo $(@:.o=.ko); echo $(link_multi_deps); } > $(MODVERDIR)/$(@F:.o=.mod) + +targets += $(multi-used-y) $(multi-used-m) + + +# Descending +# --------------------------------------------------------------------------- + +PHONY += $(subdir-ym) +$(subdir-ym): + $(Q)$(MAKE) $(build)=$@ + +# Add FORCE to the prequisites of a target to force it to be always rebuilt. +# --------------------------------------------------------------------------- + +PHONY += FORCE + +FORCE: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +targets := $(wildcard $(sort $(targets))) +cmd_files := $(wildcard $(foreach f,$(targets),$(dir $(f)).$(notdir $(f)).cmd)) + +ifneq ($(cmd_files),) + include $(cmd_files) +endif + + +# Declare the contents of the .PHONY variable as phony. We keep that +# information in a variable se we can use it in if_changed and friends. + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.clean b/scripts/Makefile.clean new file mode 100644 index 000000000..cff33498f --- /dev/null +++ b/scripts/Makefile.clean @@ -0,0 +1,102 @@ +# ========================================================================== +# Cleaning up +# ========================================================================== + +src := $(obj) + +PHONY := __clean +__clean: + +# Shorthand for $(Q)$(MAKE) scripts/Makefile.clean obj=dir +# Usage: +# $(Q)$(MAKE) $(clean)=dir +clean := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.clean obj + +# The filename Kbuild has precedence over Makefile +kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) +include $(if $(wildcard $(kbuild-dir)/Kbuild), $(kbuild-dir)/Kbuild, $(kbuild-dir)/Makefile) + +# Figure out what we need to build from the various variables +# ========================================================================== + +__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) +subdir-y += $(__subdir-y) +__subdir-m := $(patsubst %/,%,$(filter %/, $(obj-m))) +subdir-m += $(__subdir-m) +__subdir-n := $(patsubst %/,%,$(filter %/, $(obj-n))) +subdir-n += $(__subdir-n) +__subdir- := $(patsubst %/,%,$(filter %/, $(obj-))) +subdir- += $(__subdir-) + +# Subdirectories we need to descend into + +subdir-ym := $(sort $(subdir-y) $(subdir-m)) +subdir-ymn := $(sort $(subdir-ym) $(subdir-n) $(subdir-)) + +# Add subdir path + +subdir-ymn := $(addprefix $(obj)/,$(subdir-ymn)) + +# build a list of files to remove, usually releative to the current +# directory + +__clean-files := $(extra-y) $(EXTRA_TARGETS) $(always) \ + $(targets) $(clean-files) \ + $(host-progs) \ + $(hostprogs-y) $(hostprogs-m) $(hostprogs-) + +# as clean-files is given relative to the current directory, this adds +# a $(obj) prefix, except for absolute paths + +__clean-files := $(wildcard \ + $(addprefix $(obj)/, $(filter-out /%, $(__clean-files))) \ + $(filter /%, $(__clean-files))) + +# as clean-dirs is given relative to the current directory, this adds +# a $(obj) prefix, except for absolute paths + +__clean-dirs := $(wildcard \ + $(addprefix $(obj)/, $(filter-out /%, $(clean-dirs))) \ + $(filter /%, $(clean-dirs))) + +# ========================================================================== + +quiet_cmd_clean = CLEAN $(obj) + cmd_clean = rm -f $(__clean-files) +quiet_cmd_cleandir = CLEAN $(__clean-dirs) + cmd_cleandir = rm -rf $(__clean-dirs) + + +__clean: $(subdir-ymn) +ifneq ($(strip $(__clean-files)),) + +$(call cmd,clean) +endif +ifneq ($(strip $(__clean-dirs)),) + +$(call cmd,cleandir) +endif +ifneq ($(strip $(clean-rule)),) + +$(clean-rule) +endif + @: + + +# =========================================================================== +# Generic stuff +# =========================================================================== + +# Descending +# --------------------------------------------------------------------------- + +PHONY += $(subdir-ymn) +$(subdir-ymn): + $(Q)$(MAKE) $(clean)=$@ + +# If quiet is set, only print short version of command + +cmd = @$(if $($(quiet)cmd_$(1)),echo ' $($(quiet)cmd_$(1))' &&) $(cmd_$(1)) + + +# Declare the contents of the .PHONY variable as phony. We keep that +# information in a variable se we can use it in if_changed and friends. + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.host b/scripts/Makefile.host new file mode 100644 index 000000000..2d519704b --- /dev/null +++ b/scripts/Makefile.host @@ -0,0 +1,156 @@ +# ========================================================================== +# Building binaries on the host system +# Binaries are used during the compilation of the kernel, for example +# to preprocess a data file. +# +# Both C and C++ is supported, but preferred language is C for such utilities. +# +# Samle syntax (see Documentation/kbuild/makefile.txt for reference) +# hostprogs-y := bin2hex +# Will compile bin2hex.c and create an executable named bin2hex +# +# hostprogs-y := lxdialog +# lxdialog-objs := checklist.o lxdialog.o +# Will compile lxdialog.c and checklist.c, and then link the executable +# lxdialog, based on checklist.o and lxdialog.o +# +# hostprogs-y := qconf +# qconf-cxxobjs := qconf.o +# qconf-objs := menu.o +# Will compile qconf as a C++ program, and menu as a C program. +# They are linked as C++ code to the executable qconf + +# hostprogs-y := conf +# conf-objs := conf.o libkconfig.so +# libkconfig-objs := expr.o type.o +# Will create a shared library named libkconfig.so that consist of +# expr.o and type.o (they are both compiled as C code and the object file +# are made as position independent code). +# conf.c is compiled as a c program, and conf.o is linked together with +# libkconfig.so as the executable conf. +# Note: Shared libraries consisting of C++ files are not supported + +__hostprogs := $(sort $(hostprogs-y)$(hostprogs-m)) + +# hostprogs-y := tools/build may have been specified. Retreive directory +obj-dirs += $(foreach f,$(__hostprogs), $(if $(dir $(f)),$(dir $(f)))) +obj-dirs := $(strip $(sort $(filter-out ./,$(obj-dirs)))) + + +# C code +# Executables compiled from a single .c file +host-csingle := $(foreach m,$(__hostprogs),$(if $($(m)-objs),,$(m))) + +# C executables linked based on several .o files +host-cmulti := $(foreach m,$(__hostprogs),\ + $(if $($(m)-cxxobjs),,$(if $($(m)-objs),$(m)))) + +# Object (.o) files compiled from .c files +host-cobjs := $(sort $(foreach m,$(__hostprogs),$($(m)-objs))) + +# C++ code +# C++ executables compiled from at least on .cc file +# and zero or more .c files +host-cxxmulti := $(foreach m,$(__hostprogs),$(if $($(m)-cxxobjs),$(m))) + +# C++ Object (.o) files compiled from .cc files +host-cxxobjs := $(sort $(foreach m,$(host-cxxmulti),$($(m)-cxxobjs))) + +# Shared libaries (only .c supported) +# Shared libraries (.so) - all .so files referenced in "xxx-objs" +host-cshlib := $(sort $(filter %.so, $(host-cobjs))) +# Remove .so files from "xxx-objs" +host-cobjs := $(filter-out %.so,$(host-cobjs)) + +#Object (.o) files used by the shared libaries +host-cshobjs := $(sort $(foreach m,$(host-cshlib),$($(m:.so=-objs)))) + +__hostprogs := $(addprefix $(obj)/,$(__hostprogs)) +host-csingle := $(addprefix $(obj)/,$(host-csingle)) +host-cmulti := $(addprefix $(obj)/,$(host-cmulti)) +host-cobjs := $(addprefix $(obj)/,$(host-cobjs)) +host-cxxmulti := $(addprefix $(obj)/,$(host-cxxmulti)) +host-cxxobjs := $(addprefix $(obj)/,$(host-cxxobjs)) +host-cshlib := $(addprefix $(obj)/,$(host-cshlib)) +host-cshobjs := $(addprefix $(obj)/,$(host-cshobjs)) +obj-dirs := $(addprefix $(obj)/,$(obj-dirs)) + +##### +# Handle options to gcc. Support building with separate output directory + +_hostc_flags = $(HOSTCFLAGS) $(HOST_EXTRACFLAGS) $(HOSTCFLAGS_$(*F).o) +_hostcxx_flags = $(HOSTCXXFLAGS) $(HOST_EXTRACXXFLAGS) $(HOSTCXXFLAGS_$(*F).o) + +ifeq ($(KBUILD_SRC),) +__hostc_flags = $(_hostc_flags) +__hostcxx_flags = $(_hostcxx_flags) +else +__hostc_flags = -I$(obj) $(call flags,_hostc_flags) +__hostcxx_flags = -I$(obj) $(call flags,_hostcxx_flags) +endif + +hostc_flags = -Wp,-MD,$(depfile) $(__hostc_flags) +hostcxx_flags = -Wp,-MD,$(depfile) $(__hostcxx_flags) + +##### +# Compile programs on the host + +# Create executable from a single .c file +# host-csingle -> Executable +quiet_cmd_host-csingle = HOSTCC $@ + cmd_host-csingle = $(HOSTCC) $(hostc_flags) -o $@ $< \ + $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F)) +$(host-csingle): %: %.c FORCE + $(call if_changed_dep,host-csingle) + +# Link an executable based on list of .o files, all plain c +# host-cmulti -> executable +quiet_cmd_host-cmulti = HOSTLD $@ + cmd_host-cmulti = $(HOSTCC) $(HOSTLDFLAGS) -o $@ \ + $(addprefix $(obj)/,$($(@F)-objs)) \ + $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F)) +$(host-cmulti): %: $(host-cobjs) $(host-cshlib) FORCE + $(call if_changed,host-cmulti) + +# Create .o file from a single .c file +# host-cobjs -> .o +quiet_cmd_host-cobjs = HOSTCC $@ + cmd_host-cobjs = $(HOSTCC) $(hostc_flags) -c -o $@ $< +$(host-cobjs): %.o: %.c FORCE + $(call if_changed_dep,host-cobjs) + +# Link an executable based on list of .o files, a mixture of .c and .cc +# host-cxxmulti -> executable +quiet_cmd_host-cxxmulti = HOSTLD $@ + cmd_host-cxxmulti = $(HOSTCXX) $(HOSTLDFLAGS) -o $@ \ + $(foreach o,objs cxxobjs,\ + $(addprefix $(obj)/,$($(@F)-$(o)))) \ + $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F)) +$(host-cxxmulti): %: $(host-cobjs) $(host-cxxobjs) $(host-cshlib) FORCE + $(call if_changed,host-cxxmulti) + +# Create .o file from a single .cc (C++) file +quiet_cmd_host-cxxobjs = HOSTCXX $@ + cmd_host-cxxobjs = $(HOSTCXX) $(hostcxx_flags) -c -o $@ $< +$(host-cxxobjs): %.o: %.cc FORCE + $(call if_changed_dep,host-cxxobjs) + +# Compile .c file, create position independent .o file +# host-cshobjs -> .o +quiet_cmd_host-cshobjs = HOSTCC -fPIC $@ + cmd_host-cshobjs = $(HOSTCC) $(hostc_flags) -fPIC -c -o $@ $< +$(host-cshobjs): %.o: %.c FORCE + $(call if_changed_dep,host-cshobjs) + +# Link a shared library, based on position independent .o files +# *.o -> .so shared library (host-cshlib) +quiet_cmd_host-cshlib = HOSTLLD -shared $@ + cmd_host-cshlib = $(HOSTCC) $(HOSTLDFLAGS) -shared -o $@ \ + $(addprefix $(obj)/,$($(@F:.so=-objs))) \ + $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F)) +$(host-cshlib): %: $(host-cshobjs) FORCE + $(call if_changed,host-cshlib) + +targets += $(host-csingle) $(host-cmulti) $(host-cobjs)\ + $(host-cxxmulti) $(host-cxxobjs) $(host-cshlib) $(host-cshobjs) + diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib new file mode 100644 index 000000000..2cb4935e8 --- /dev/null +++ b/scripts/Makefile.lib @@ -0,0 +1,165 @@ +# Backward compatibility - to be removed... +extra-y += $(EXTRA_TARGETS) +# Figure out what we need to build from the various variables +# =========================================================================== + +# When an object is listed to be built compiled-in and modular, +# only build the compiled-in version + +obj-m := $(filter-out $(obj-y),$(obj-m)) + +# Libraries are always collected in one lib file. +# Filter out objects already built-in + +lib-y := $(filter-out $(obj-y), $(sort $(lib-y) $(lib-m))) + + +# Handle objects in subdirs +# --------------------------------------------------------------------------- +# o if we encounter foo/ in $(obj-y), replace it by foo/built-in.o +# and add the directory to the list of dirs to descend into: $(subdir-y) +# o if we encounter foo/ in $(obj-m), remove it from $(obj-m) +# and add the directory to the list of dirs to descend into: $(subdir-m) + +__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) +subdir-y += $(__subdir-y) +__subdir-m := $(patsubst %/,%,$(filter %/, $(obj-m))) +subdir-m += $(__subdir-m) +obj-y := $(patsubst %/, %/built-in.o, $(obj-y)) +obj-m := $(filter-out %/, $(obj-m)) + +# Subdirectories we need to descend into + +subdir-ym := $(sort $(subdir-y) $(subdir-m)) + +# if $(foo-objs) exists, foo.o is a composite object +multi-used-y := $(sort $(foreach m,$(obj-y), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))), $(m)))) +multi-used-m := $(sort $(foreach m,$(obj-m), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))), $(m)))) +multi-used := $(multi-used-y) $(multi-used-m) +single-used-m := $(sort $(filter-out $(multi-used-m),$(obj-m))) + +# Build list of the parts of our composite objects, our composite +# objects depend on those (obviously) +multi-objs-y := $(foreach m, $(multi-used-y), $($(m:.o=-objs)) $($(m:.o=-y))) +multi-objs-m := $(foreach m, $(multi-used-m), $($(m:.o=-objs)) $($(m:.o=-y))) +multi-objs := $(multi-objs-y) $(multi-objs-m) + +# $(subdir-obj-y) is the list of objects in $(obj-y) which do not live +# in the local directory +subdir-obj-y := $(foreach o,$(obj-y),$(if $(filter-out $(o),$(notdir $(o))),$(o))) + +# $(obj-dirs) is a list of directories that contain object files +obj-dirs := $(dir $(multi-objs) $(subdir-obj-y)) + +# Replace multi-part objects by their individual parts, look at local dir only +real-objs-y := $(foreach m, $(filter-out $(subdir-obj-y), $(obj-y)), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))),$($(m:.o=-objs)) $($(m:.o=-y)),$(m))) $(extra-y) +real-objs-m := $(foreach m, $(obj-m), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))),$($(m:.o=-objs)) $($(m:.o=-y)),$(m))) + +# Add subdir path + +extra-y := $(addprefix $(obj)/,$(extra-y)) +always := $(addprefix $(obj)/,$(always)) +targets := $(addprefix $(obj)/,$(targets)) +obj-y := $(addprefix $(obj)/,$(obj-y)) +obj-m := $(addprefix $(obj)/,$(obj-m)) +lib-y := $(addprefix $(obj)/,$(lib-y)) +subdir-obj-y := $(addprefix $(obj)/,$(subdir-obj-y)) +real-objs-y := $(addprefix $(obj)/,$(real-objs-y)) +real-objs-m := $(addprefix $(obj)/,$(real-objs-m)) +single-used-m := $(addprefix $(obj)/,$(single-used-m)) +multi-used-y := $(addprefix $(obj)/,$(multi-used-y)) +multi-used-m := $(addprefix $(obj)/,$(multi-used-m)) +multi-objs-y := $(addprefix $(obj)/,$(multi-objs-y)) +multi-objs-m := $(addprefix $(obj)/,$(multi-objs-m)) +subdir-ym := $(addprefix $(obj)/,$(subdir-ym)) +obj-dirs := $(addprefix $(obj)/,$(obj-dirs)) + +# These flags are needed for modversions and compiling, so we define them here +# already +# $(modname_flags) #defines KBUILD_MODNAME as the name of the module it will +# end up in (or would, if it gets compiled in) +# Note: It's possible that one object gets potentially linked into more +# than one module. In that case KBUILD_MODNAME will be set to foo_bar, +# where foo and bar are the name of the modules. +name-fix = $(subst $(comma),_,$(subst -,_,$1)) +basename_flags = -D"KBUILD_BASENAME=KBUILD_STR($(call name-fix,$(*F)))" +modname_flags = $(if $(filter 1,$(words $(modname))),\ + -D"KBUILD_MODNAME=KBUILD_STR($(call name-fix,$(modname)))") + +_c_flags = $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(*F).o) +_a_flags = $(AFLAGS) $(EXTRA_AFLAGS) $(AFLAGS_$(*F).o) +_cpp_flags = $(CPPFLAGS) $(EXTRA_CPPFLAGS) $(CPPFLAGS_$(@F)) + +# If building the kernel in a separate objtree expand all occurrences +# of -Idir to -I$(srctree)/dir except for absolute paths (starting with '/'). + +ifeq ($(KBUILD_SRC),) +__c_flags = $(_c_flags) +__a_flags = $(_a_flags) +__cpp_flags = $(_cpp_flags) +else + +# -I$(obj) locates generated .h files +# $(call addtree,-I$(obj)) locates .h files in srctree, from generated .c files +# and locates generated .h files +# FIXME: Replace both with specific CFLAGS* statements in the makefiles +__c_flags = $(call addtree,-I$(obj)) $(call flags,_c_flags) +__a_flags = $(call flags,_a_flags) +__cpp_flags = $(call flags,_cpp_flags) +endif + +c_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(CPPFLAGS) \ + $(__c_flags) $(modkern_cflags) \ + -D"KBUILD_STR(s)=\#s" $(basename_flags) $(modname_flags) + +a_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(CPPFLAGS) \ + $(__a_flags) $(modkern_aflags) + +cpp_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(__cpp_flags) + +ld_flags = $(LDFLAGS) $(EXTRA_LDFLAGS) + +# Finds the multi-part object the current object will be linked into +modname-multi = $(sort $(foreach m,$(multi-used),\ + $(if $(filter $(subst $(obj)/,,$*.o), $($(m:.o=-objs)) $($(m:.o=-y))),$(m:.o=)))) + +# Shipped files +# =========================================================================== + +quiet_cmd_shipped = SHIPPED $@ +cmd_shipped = cat $< > $@ + +$(obj)/%:: $(src)/%_shipped + $(call cmd,shipped) + +# Commands useful for building a boot image +# =========================================================================== +# +# Use as following: +# +# target: source(s) FORCE +# $(if_changed,ld/objcopy/gzip) +# +# and add target to EXTRA_TARGETS so that we know we have to +# read in the saved command line + +# Linking +# --------------------------------------------------------------------------- + +quiet_cmd_ld = LD $@ +cmd_ld = $(LD) $(LDFLAGS) $(EXTRA_LDFLAGS) $(LDFLAGS_$(@F)) \ + $(filter-out FORCE,$^) -o $@ + +# Objcopy +# --------------------------------------------------------------------------- + +quiet_cmd_objcopy = OBJCOPY $@ +cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@ + +# Gzip +# --------------------------------------------------------------------------- + +quiet_cmd_gzip = GZIP $@ +cmd_gzip = gzip -f -9 < $< > $@ + + diff --git a/scripts/basic/Makefile b/scripts/basic/Makefile new file mode 100644 index 000000000..119f079cf --- /dev/null +++ b/scripts/basic/Makefile @@ -0,0 +1,18 @@ +### +# Makefile.basic list the most basic programs used during the build process. +# The programs listed herein is what is needed to do the basic stuff, +# such as splitting .config and fix dependency file. +# This initial step is needed to avoid files to be recompiled +# when busybox configuration changes (which is what happens when +# .config is included by main Makefile. +# --------------------------------------------------------------------------- +# fixdep: Used to generate dependency information during build process +# split-include: Divide all config symbols up in a number of files in +# include/config/... +# docproc: Used in Documentation/docbook + +hostprogs-y := fixdep split-include docproc +always := $(hostprogs-y) + +# fixdep is needed to compile other host programs +$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep diff --git a/scripts/basic/docproc.c b/scripts/basic/docproc.c new file mode 100644 index 000000000..cb02baa63 --- /dev/null +++ b/scripts/basic/docproc.c @@ -0,0 +1,398 @@ +/* + * docproc is a simple preprocessor for the template files + * used as placeholders for the kernel internal documentation. + * docproc is used for documentation-frontend and + * dependency-generator. + * The two usages have in common that they require + * some knowledge of the .tmpl syntax, therefore they + * are kept together. + * + * documentation-frontend + * Scans the template file and call kernel-doc for + * all occurrences of ![EIF]file + * Beforehand each referenced file are scanned for + * any exported sympols "EXPORT_SYMBOL()" statements. + * This is used to create proper -function and + * -nofunction arguments in calls to kernel-doc. + * Usage: docproc doc file.tmpl + * + * dependency-generator: + * Scans the template file and list all files + * referenced in a format recognized by make. + * Usage: docproc depend file.tmpl + * Writes dependency information to stdout + * in the following format: + * file.tmpl src.c src2.c + * The filenames are obtained from the following constructs: + * !Efilename + * !Ifilename + * !Dfilename + * !Ffilename + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* exitstatus is used to keep track of any failing calls to kernel-doc, + * but execution continues. */ +int exitstatus = 0; + +typedef void DFL(char *); +DFL *defaultline; + +typedef void FILEONLY(char * file); +FILEONLY *internalfunctions; +FILEONLY *externalfunctions; +FILEONLY *symbolsonly; + +typedef void FILELINE(char * file, char * line); +FILELINE * singlefunctions; +FILELINE * entity_system; + +#define MAXLINESZ 2048 +#define MAXFILES 250 +#define KERNELDOCPATH "scripts/" +#define KERNELDOC "kernel-doc" +#define DOCBOOK "-docbook" +#define FUNCTION "-function" +#define NOFUNCTION "-nofunction" + +void usage (void) +{ + fprintf(stderr, "Usage: docproc {doc|depend} file\n"); + fprintf(stderr, "Input is read from file.tmpl. Output is sent to stdout\n"); + fprintf(stderr, "doc: frontend when generating kernel documentation\n"); + fprintf(stderr, "depend: generate list of files referenced within file\n"); +} + +/* + * Execute kernel-doc with parameters givin in svec + */ +void exec_kernel_doc(char **svec) +{ + pid_t pid; + int ret; + char real_filename[PATH_MAX + 1]; + /* Make sure output generated so far are flushed */ + fflush(stdout); + switch(pid=fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + memset(real_filename, 0, sizeof(real_filename)); + strncat(real_filename, getenv("SRCTREE"), PATH_MAX); + strncat(real_filename, KERNELDOCPATH KERNELDOC, + PATH_MAX - strlen(real_filename)); + execvp(real_filename, svec); + fprintf(stderr, "exec "); + perror(real_filename); + exit(1); + default: + waitpid(pid, &ret ,0); + } + if (WIFEXITED(ret)) + exitstatus |= WEXITSTATUS(ret); + else + exitstatus = 0xff; +} + +/* Types used to create list of all exported symbols in a number of files */ +struct symbols +{ + char *name; +}; + +struct symfile +{ + char *filename; + struct symbols *symbollist; + int symbolcnt; +}; + +struct symfile symfilelist[MAXFILES]; +int symfilecnt = 0; + +void add_new_symbol(struct symfile *sym, char * symname) +{ + sym->symbollist = + realloc(sym->symbollist, (sym->symbolcnt + 1) * sizeof(char *)); + sym->symbollist[sym->symbolcnt++].name = strdup(symname); +} + +/* Add a filename to the list */ +struct symfile * add_new_file(char * filename) +{ + symfilelist[symfilecnt++].filename = strdup(filename); + return &symfilelist[symfilecnt - 1]; +} +/* Check if file already are present in the list */ +struct symfile * filename_exist(char * filename) +{ + int i; + for (i=0; i < symfilecnt; i++) + if (strcmp(symfilelist[i].filename, filename) == 0) + return &symfilelist[i]; + return NULL; +} + +/* + * List all files referenced within the template file. + * Files are separated by tabs. + */ +void adddep(char * file) { printf("\t%s", file); } +void adddep2(char * file, char * line) { line = line; adddep(file); } +void noaction(char * line) { line = line; } +void noaction2(char * file, char * line) { file = file; line = line; } + +/* Echo the line without further action */ +void printline(char * line) { printf("%s", line); } + +/* + * Find all symbols exported with EXPORT_SYMBOL and EXPORT_SYMBOL_GPL + * in filename. + * All symbols located are stored in symfilelist. + */ +void find_export_symbols(char * filename) +{ + FILE * fp; + struct symfile *sym; + char line[MAXLINESZ]; + if (filename_exist(filename) == NULL) { + char real_filename[PATH_MAX + 1]; + memset(real_filename, 0, sizeof(real_filename)); + strncat(real_filename, getenv("SRCTREE"), PATH_MAX); + strncat(real_filename, filename, + PATH_MAX - strlen(real_filename)); + sym = add_new_file(filename); + fp = fopen(real_filename, "r"); + if (fp == NULL) + { + fprintf(stderr, "docproc: "); + perror(real_filename); + } + while(fgets(line, MAXLINESZ, fp)) { + char *p; + char *e; + if (((p = strstr(line, "EXPORT_SYMBOL_GPL")) != 0) || + ((p = strstr(line, "EXPORT_SYMBOL")) != 0)) { + /* Skip EXPORT_SYMBOL{_GPL} */ + while (isalnum(*p) || *p == '_') + p++; + /* Remove paranteses and additional ws */ + while (isspace(*p)) + p++; + if (*p != '(') + continue; /* Syntax error? */ + else + p++; + while (isspace(*p)) + p++; + e = p; + while (isalnum(*e) || *e == '_') + e++; + *e = '\0'; + add_new_symbol(sym, p); + } + } + fclose(fp); + } +} + +/* + * Document all external or internal functions in a file. + * Call kernel-doc with following parameters: + * kernel-doc -docbook -nofunction function_name1 filename + * function names are obtained from all the the src files + * by find_export_symbols. + * intfunc uses -nofunction + * extfunc uses -function + */ +void docfunctions(char * filename, char * type) +{ + int i,j; + int symcnt = 0; + int idx = 0; + char **vec; + + for (i=0; i <= symfilecnt; i++) + symcnt += symfilelist[i].symbolcnt; + vec = malloc((2 + 2 * symcnt + 2) * sizeof(char*)); + if (vec == NULL) { + perror("docproc: "); + exit(1); + } + vec[idx++] = KERNELDOC; + vec[idx++] = DOCBOOK; + for (i=0; i < symfilecnt; i++) { + struct symfile * sym = &symfilelist[i]; + for (j=0; j < sym->symbolcnt; j++) { + vec[idx++] = type; + vec[idx++] = sym->symbollist[j].name; + } + } + vec[idx++] = filename; + vec[idx] = NULL; + printf("\n", filename); + exec_kernel_doc(vec); + fflush(stdout); + free(vec); +} +void intfunc(char * filename) { docfunctions(filename, NOFUNCTION); } +void extfunc(char * filename) { docfunctions(filename, FUNCTION); } + +/* + * Document spåecific function(s) in a file. + * Call kernel-doc with the following parameters: + * kernel-doc -docbook -function function1 [-function function2] + */ +void singfunc(char * filename, char * line) +{ + char *vec[200]; /* Enough for specific functions */ + int i, idx = 0; + int startofsym = 1; + vec[idx++] = KERNELDOC; + vec[idx++] = DOCBOOK; + + /* Split line up in individual parameters preceeded by FUNCTION */ + for (i=0; line[i]; i++) { + if (isspace(line[i])) { + line[i] = '\0'; + startofsym = 1; + continue; + } + if (startofsym) { + startofsym = 0; + vec[idx++] = FUNCTION; + vec[idx++] = &line[i]; + } + } + vec[idx++] = filename; + vec[idx] = NULL; + exec_kernel_doc(vec); +} + +/* + * Parse file, calling action specific functions for: + * 1) Lines containing !E + * 2) Lines containing !I + * 3) Lines containing !D + * 4) Lines containing !F + * 5) Default lines - lines not matching the above + */ +void parse_file(FILE *infile) +{ + char line[MAXLINESZ]; + char * s; + while(fgets(line, MAXLINESZ, infile)) { + if (line[0] == '!') { + s = line + 2; + switch (line[1]) { + case 'E': + while (*s && !isspace(*s)) s++; + *s = '\0'; + externalfunctions(line+2); + break; + case 'I': + while (*s && !isspace(*s)) s++; + *s = '\0'; + internalfunctions(line+2); + break; + case 'D': + while (*s && !isspace(*s)) s++; + *s = '\0'; + symbolsonly(line+2); + break; + case 'F': + /* filename */ + while (*s && !isspace(*s)) s++; + *s++ = '\0'; + /* function names */ + while (isspace(*s)) + s++; + singlefunctions(line +2, s); + break; + default: + defaultline(line); + } + } + else { + defaultline(line); + } + } + fflush(stdout); +} + + +int main(int argc, char *argv[]) +{ + FILE * infile; + if (argc != 3) { + usage(); + exit(1); + } + /* Open file, exit on error */ + infile = fopen(argv[2], "r"); + if (infile == NULL) { + fprintf(stderr, "docproc: "); + perror(argv[2]); + exit(2); + } + + if (strcmp("doc", argv[1]) == 0) + { + /* Need to do this in two passes. + * First pass is used to collect all symbols exported + * in the various files. + * Second pass generate the documentation. + * This is required because function are declared + * and exported in different files :-(( + */ + /* Collect symbols */ + defaultline = noaction; + internalfunctions = find_export_symbols; + externalfunctions = find_export_symbols; + symbolsonly = find_export_symbols; + singlefunctions = noaction2; + parse_file(infile); + + /* Rewind to start from beginning of file again */ + fseek(infile, 0, SEEK_SET); + defaultline = printline; + internalfunctions = intfunc; + externalfunctions = extfunc; + symbolsonly = printline; + singlefunctions = singfunc; + + parse_file(infile); + } + else if (strcmp("depend", argv[1]) == 0) + { + /* Create first part of dependency chain + * file.tmpl */ + printf("%s\t", argv[2]); + defaultline = noaction; + internalfunctions = adddep; + externalfunctions = adddep; + symbolsonly = adddep; + singlefunctions = adddep2; + parse_file(infile); + printf("\n"); + } + else + { + fprintf(stderr, "Unknown option: %s\n", argv[1]); + exit(1); + } + fclose(infile); + fflush(stdout); + return exitstatus; +} + diff --git a/scripts/basic/fixdep.c b/scripts/basic/fixdep.c new file mode 100644 index 000000000..df3446e35 --- /dev/null +++ b/scripts/basic/fixdep.c @@ -0,0 +1,399 @@ +/* + * "Optimize" a list of dependencies as spit out by gcc -MD + * for the kernel build + * =========================================================================== + * + * Author Kai Germaschewski + * Copyright 2002 by Kai Germaschewski + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * + * Introduction: + * + * gcc produces a very nice and correct list of dependencies which + * tells make when to remake a file. + * + * To use this list as-is however has the drawback that virtually + * every file in the kernel includes which then again + * includes + * + * If the user re-runs make *config, linux/autoconf.h will be + * regenerated. make notices that and will rebuild every file which + * includes autoconf.h, i.e. basically all files. This is extremely + * annoying if the user just changed CONFIG_HIS_DRIVER from n to m. + * + * So we play the same trick that "mkdep" played before. We replace + * the dependency on linux/autoconf.h by a dependency on every config + * option which is mentioned in any of the listed prequisites. + * + * To be exact, split-include populates a tree in include/config/, + * e.g. include/config/his/driver.h, which contains the #define/#undef + * for the CONFIG_HIS_DRIVER option. + * + * So if the user changes his CONFIG_HIS_DRIVER option, only the objects + * which depend on "include/linux/config/his/driver.h" will be rebuilt, + * so most likely only his driver ;-) + * + * The idea above dates, by the way, back to Michael E Chastain, AFAIK. + * + * So to get dependencies right, there are two issues: + * o if any of the files the compiler read changed, we need to rebuild + * o if the command line given to the compile the file changed, we + * better rebuild as well. + * + * The former is handled by using the -MD output, the later by saving + * the command line used to compile the old object and comparing it + * to the one we would now use. + * + * Again, also this idea is pretty old and has been discussed on + * kbuild-devel a long time ago. I don't have a sensibly working + * internet connection right now, so I rather don't mention names + * without double checking. + * + * This code here has been based partially based on mkdep.c, which + * says the following about its history: + * + * Copyright abandoned, Michael Chastain, . + * This is a C version of syncdep.pl by Werner Almesberger. + * + * + * It is invoked as + * + * fixdep + * + * and will read the dependency file + * + * The transformed dependency snipped is written to stdout. + * + * It first generates a line + * + * cmd_ = + * + * and then basically copies the ..d file to stdout, in the + * process filtering out the dependency on linux/autoconf.h and adding + * dependencies on include/config/my/option.h for every + * CONFIG_MY_OPTION encountered in any of the prequisites. + * + * It will also filter out all the dependencies on *.ver. We need + * to make sure that the generated version checksum are globally up + * to date before even starting the recursive build, so it's too late + * at this point anyway. + * + * The algorithm to grep for "CONFIG_..." is bit unusual, but should + * be fast ;-) We don't even try to really parse the header files, but + * merely grep, i.e. if CONFIG_FOO is mentioned in a comment, it will + * be picked up as well. It's not a problem with respect to + * correctness, since that can only give too many dependencies, thus + * we cannot miss a rebuild. Since people tend to not mention totally + * unrelated CONFIG_ options all over the place, it's not an + * efficiency problem either. + * + * (Note: it'd be easy to port over the complete mkdep state machine, + * but I don't think the added complexity is worth it) + */ +/* + * Note 2: if somebody writes HELLO_CONFIG_BOOM in a file, it will depend onto + * CONFIG_BOOM. This could seem a bug (not too hard to fix), but please do not + * fix it! Some UserModeLinux files (look at arch/um/) call CONFIG_BOOM as + * UML_CONFIG_BOOM, to avoid conflicts with /usr/include/linux/autoconf.h, + * through arch/um/include/uml-config.h; this fixdep "bug" makes sure that + * those files will have correct dependencies. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* bbox: not needed +#define INT_CONF ntohl(0x434f4e46) +#define INT_ONFI ntohl(0x4f4e4649) +#define INT_NFIG ntohl(0x4e464947) +#define INT_FIG_ ntohl(0x4649475f) +*/ + +char *target; +char *depfile; +char *cmdline; + +void usage(void) + +{ + fprintf(stderr, "Usage: fixdep \n"); + exit(1); +} + +/* + * Print out the commandline prefixed with cmd_ := + */ +void print_cmdline(void) +{ + printf("cmd_%s := %s\n\n", target, cmdline); +} + +char * str_config = NULL; +int size_config = 0; +int len_config = 0; + +/* + * Grow the configuration string to a desired length. + * Usually the first growth is plenty. + */ +void grow_config(int len) +{ + while (len_config + len > size_config) { + if (size_config == 0) + size_config = 2048; + str_config = realloc(str_config, size_config *= 2); + if (str_config == NULL) + { perror("fixdep:malloc"); exit(1); } + } +} + + + +/* + * Lookup a value in the configuration string. + */ +int is_defined_config(const char * name, int len) +{ + const char * pconfig; + const char * plast = str_config + len_config - len; + for ( pconfig = str_config + 1; pconfig < plast; pconfig++ ) { + if (pconfig[ -1] == '\n' + && pconfig[len] == '\n' + && !memcmp(pconfig, name, len)) + return 1; + } + return 0; +} + +/* + * Add a new value to the configuration string. + */ +void define_config(const char * name, int len) +{ + grow_config(len + 1); + + memcpy(str_config+len_config, name, len); + len_config += len; + str_config[len_config++] = '\n'; +} + +/* + * Clear the set of configuration strings. + */ +void clear_config(void) +{ + len_config = 0; + define_config("", 0); +} + +/* + * Record the use of a CONFIG_* word. + */ +void use_config(char *m, int slen) +{ + char s[PATH_MAX]; + char *p; + + if (is_defined_config(m, slen)) + return; + + define_config(m, slen); + + memcpy(s, m, slen); s[slen] = 0; + + for (p = s; p < s + slen; p++) { + if (*p == '_') + *p = '/'; + else + *p = tolower((int)*p); + } + printf(" $(wildcard include/config/%s.h) \\\n", s); +} + +void parse_config_file(char *map, size_t len) +{ + /* modified for bbox */ + char *end = map + len; + char *p = map; + char *q; + int off; + + for (; p < end; p++) { + if (!memcmp(p, "CONFIG_", 7)) goto conf7; + if (!memcmp(p, "ENABLE_", 7)) goto conf7; + if (!memcmp(p, "USE_", 4)) goto conf4; + if (!memcmp(p, "SKIP_", 5)) goto conf5; + continue; + conf4: off = 4; goto conf; + conf5: off = 5; goto conf; + conf7: off = 7; + conf: + if (p > map + len - off) + continue; + for (q = p + off; q < map + len; q++) { + if (!(isalnum(*q) || *q == '_')) + goto found; + } + continue; + + found: + use_config(p+off, q-p-off); + } +} + +/* test is s ends in sub */ +int strrcmp(char *s, char *sub) +{ + int slen = strlen(s); + int sublen = strlen(sub); + + if (sublen > slen) + return 1; + + return memcmp(s + slen - sublen, sub, sublen); +} + +void do_config_file(char *filename) +{ + struct stat st; + int fd; + void *map; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "fixdep: "); + perror(filename); + exit(2); + } + fstat(fd, &st); + if (st.st_size == 0) { + close(fd); + return; + } + map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if ((long) map == -1) { + perror("fixdep: mmap"); + close(fd); + return; + } + + parse_config_file(map, st.st_size); + + munmap(map, st.st_size); + + close(fd); +} + +void parse_dep_file(void *map, size_t len) +{ + char *m = map; + char *end = m + len; + char *p; + char s[PATH_MAX]; + + p = strchr(m, ':'); + if (!p) { + fprintf(stderr, "fixdep: parse error\n"); + exit(1); + } + memcpy(s, m, p-m); s[p-m] = 0; + printf("deps_%s := \\\n", target); + m = p+1; + + clear_config(); + + while (m < end) { + while (m < end && (*m == ' ' || *m == '\\' || *m == '\n')) + m++; + p = m; + while (p < end && *p != ' ') p++; + if (p == end) { + do p--; while (!isalnum(*p)); + p++; + } + memcpy(s, m, p-m); s[p-m] = 0; + if (strrcmp(s, "include/autoconf.h") && + strrcmp(s, "arch/um/include/uml-config.h") && + strrcmp(s, ".ver")) { + printf(" %s \\\n", s); + do_config_file(s); + } + m = p + 1; + } + printf("\n%s: $(deps_%s)\n\n", target, target); + printf("$(deps_%s):\n", target); +} + +void print_deps(void) +{ + struct stat st; + int fd; + void *map; + + fd = open(depfile, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "fixdep: "); + perror(depfile); + exit(2); + } + fstat(fd, &st); + if (st.st_size == 0) { + fprintf(stderr,"fixdep: %s is empty\n",depfile); + close(fd); + return; + } + map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if ((long) map == -1) { + perror("fixdep: mmap"); + close(fd); + return; + } + + parse_dep_file(map, st.st_size); + + munmap(map, st.st_size); + + close(fd); +} + +void traps(void) +{ +/* bbox: not needed + static char test[] __attribute__((aligned(sizeof(int)))) = "CONF"; + + if (*(int *)test != INT_CONF) { + fprintf(stderr, "fixdep: sizeof(int) != 4 or wrong endianess? %#x\n", + *(int *)test); + exit(2); + } +*/ +} + +int main(int argc, char *argv[]) +{ + traps(); + + if (argc != 4) + usage(); + + depfile = argv[1]; + target = argv[2]; + cmdline = argv[3]; + + print_cmdline(); + print_deps(); + + return 0; +} diff --git a/scripts/basic/split-include.c b/scripts/basic/split-include.c new file mode 100644 index 000000000..459c45276 --- /dev/null +++ b/scripts/basic/split-include.c @@ -0,0 +1,226 @@ +/* + * split-include.c + * + * Copyright abandoned, Michael Chastain, . + * This is a C version of syncdep.pl by Werner Almesberger. + * + * This program takes autoconf.h as input and outputs a directory full + * of one-line include files, merging onto the old values. + * + * Think of the configuration options as key-value pairs. Then there + * are five cases: + * + * key old value new value action + * + * KEY-1 VALUE-1 VALUE-1 leave file alone + * KEY-2 VALUE-2A VALUE-2B write VALUE-2B into file + * KEY-3 - VALUE-3 write VALUE-3 into file + * KEY-4 VALUE-4 - write an empty file + * KEY-5 (empty) - leave old empty file alone + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define ERROR_EXIT(strExit) \ + { \ + const int errnoSave = errno; \ + fprintf(stderr, "%s: ", str_my_name); \ + errno = errnoSave; \ + perror((strExit)); \ + exit(1); \ + } + + + +int main(int argc, const char * argv []) +{ + const char * str_my_name; + const char * str_file_autoconf; + const char * str_dir_config; + + FILE * fp_config; + FILE * fp_target; + FILE * fp_find; + + int buffer_size; + + char * line; + char * old_line; + char * list_target; + char * ptarget; + + struct stat stat_buf; + + /* Check arg count. */ + if (argc != 3) + { + fprintf(stderr, "%s: wrong number of arguments.\n", argv[0]); + exit(1); + } + + str_my_name = argv[0]; + str_file_autoconf = argv[1]; + str_dir_config = argv[2]; + + /* Find a buffer size. */ + if (stat(str_file_autoconf, &stat_buf) != 0) + ERROR_EXIT(str_file_autoconf); + buffer_size = 2 * stat_buf.st_size + 4096; + + /* Allocate buffers. */ + if ( (line = malloc(buffer_size)) == NULL + || (old_line = malloc(buffer_size)) == NULL + || (list_target = malloc(buffer_size)) == NULL ) + ERROR_EXIT(str_file_autoconf); + + /* Open autoconfig file. */ + if ((fp_config = fopen(str_file_autoconf, "r")) == NULL) + ERROR_EXIT(str_file_autoconf); + + /* Make output directory if needed. */ + if (stat(str_dir_config, &stat_buf) != 0) + { + if (mkdir(str_dir_config, 0755) != 0) + ERROR_EXIT(str_dir_config); + } + + /* Change to output directory. */ + if (chdir(str_dir_config) != 0) + ERROR_EXIT(str_dir_config); + + /* Put initial separator into target list. */ + ptarget = list_target; + *ptarget++ = '\n'; + + /* Read config lines. */ + while (fgets(line, buffer_size, fp_config)) + { + const char * str_config; + int is_same; + int itarget; + + if (line[0] != '#') + continue; + if ((str_config = strstr(line, "CONFIG_")) == NULL) + continue; + + /* Make the output file name. */ + str_config += sizeof("CONFIG_") - 1; + for (itarget = 0; !isspace(str_config[itarget]); itarget++) + { + int c = (unsigned char) str_config[itarget]; + if (isupper(c)) c = tolower(c); + if (c == '_') c = '/'; + ptarget[itarget] = c; + } + ptarget[itarget++] = '.'; + ptarget[itarget++] = 'h'; + ptarget[itarget++] = '\0'; + + /* Check for existing file. */ + is_same = 0; + if ((fp_target = fopen(ptarget, "r")) != NULL) + { + fgets(old_line, buffer_size, fp_target); + if (fclose(fp_target) != 0) + ERROR_EXIT(ptarget); + if (!strcmp(line, old_line)) + is_same = 1; + } + + if (!is_same) + { + /* Auto-create directories. */ + int islash; + for (islash = 0; islash < itarget; islash++) + { + if (ptarget[islash] == '/') + { + ptarget[islash] = '\0'; + if (stat(ptarget, &stat_buf) != 0 + && mkdir(ptarget, 0755) != 0) + ERROR_EXIT( ptarget ); + ptarget[islash] = '/'; + } + } + + /* Write the file. */ + if ((fp_target = fopen(ptarget, "w" )) == NULL) + ERROR_EXIT(ptarget); + fputs(line, fp_target); + if (ferror(fp_target) || fclose(fp_target) != 0) + ERROR_EXIT(ptarget); + } + + /* Update target list */ + ptarget += itarget; + *(ptarget-1) = '\n'; + } + + /* + * Close autoconfig file. + * Terminate the target list. + */ + if (fclose(fp_config) != 0) + ERROR_EXIT(str_file_autoconf); + *ptarget = '\0'; + + /* + * Fix up existing files which have no new value. + * This is Case 4 and Case 5. + * + * I re-read the tree and filter it against list_target. + * This is crude. But it avoids data copies. Also, list_target + * is compact and contiguous, so it easily fits into cache. + * + * Notice that list_target contains strings separated by \n, + * with a \n before the first string and after the last. + * fgets gives the incoming names a terminating \n. + * So by having an initial \n, strstr will find exact matches. + */ + + fp_find = popen("find * -type f -name \"*.h\" -print", "r"); + if (fp_find == 0) + ERROR_EXIT( "find" ); + + line[0] = '\n'; + while (fgets(line+1, buffer_size, fp_find)) + { + if (strstr(list_target, line) == NULL) + { + /* + * This is an old file with no CONFIG_* flag in autoconf.h. + */ + + /* First strip the \n. */ + line[strlen(line)-1] = '\0'; + + /* Grab size. */ + if (stat(line+1, &stat_buf) != 0) + ERROR_EXIT(line); + + /* If file is not empty, make it empty and give it a fresh date. */ + if (stat_buf.st_size != 0) + { + if ((fp_target = fopen(line+1, "w")) == NULL) + ERROR_EXIT(line); + if (fclose(fp_target) != 0) + ERROR_EXIT(line); + } + } + } + + if (pclose(fp_find) != 0) + ERROR_EXIT("find"); + + return 0; +} diff --git a/scripts/bloat-o-meter b/scripts/bloat-o-meter new file mode 100755 index 000000000..67b3d290d --- /dev/null +++ b/scripts/bloat-o-meter @@ -0,0 +1,65 @@ +#!/usr/bin/python +# +# Copyright 2004 Matt Mackall +# +# inspired by perl Bloat-O-Meter (c) 1997 by Andi Kleen +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import sys, os, re + +if len(sys.argv) != 3: + sys.stderr.write("usage: %s file1 file2\n" % sys.argv[0]) + sys.exit(-1) + +def getsizes(file): + sym = {} + for l in os.popen("nm --size-sort " + file).readlines(): + size, type, name = l[:-1].split() + if type in "tTdDbBrR": + if "." in name: name = "static." + name.split(".")[0] + sym[name] = sym.get(name, 0) + int(size, 16) + for l in os.popen("readelf -S " + file).readlines(): + x = l.split() + if len(x)<6 or x[1] != ".rodata": continue + sym[".rodata"] = int(x[5], 16) + return sym + +old = getsizes(sys.argv[1]) +new = getsizes(sys.argv[2]) +grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0 +delta, common = [], {} + +for a in old: + if a in new: + common[a] = 1 + +for name in old: + if name not in common: + remove += 1 + down += old[name] + delta.append((-old[name], name)) + +for name in new: + if name not in common: + add += 1 + up += new[name] + delta.append((new[name], name)) + +for name in common: + d = new.get(name, 0) - old.get(name, 0) + if d>0: grow, up = grow+1, up+d + if d<0: shrink, down = shrink+1, down-d + delta.append((d, name)) + +delta.sort() +delta.reverse() + +print "%-48s %7s %7s %+7s" % ("function", "old", "new", "delta") +for d, n in delta: + if d: print "%-48s %7s %7s %+7d" % (n, old.get(n,"-"), new.get(n,"-"), d) +print "-"*78 +total="(add/remove: %s/%s grow/shrink: %s/%s up/down: %s/%s)%%sTotal: %s bytes"\ + % (add, remove, grow, shrink, up, -down, up-down) +print total % (" "*(80-len(total))) diff --git a/scripts/checkhelp.awk b/scripts/checkhelp.awk new file mode 100755 index 000000000..4bb4996dc --- /dev/null +++ b/scripts/checkhelp.awk @@ -0,0 +1,40 @@ +#!/usr/bin/awk -f +# AWK script to check for missing help entries for config options +# +# Copyright (C) 2006 Bernhard Fischer +# +# This file is distributed under the terms and conditions of the +# MIT/X public licenses. See http://opensource.org/licenses/mit-license.html +# and notice http://www.gnu.org/licenses/license-list.html#X11License + + +/^choice/ { is_choice = 1; } +/^endchoice/ { is_choice = 0; } +/^config/ { + pos++; + conf[pos] = $2; + file[pos] = FILENAME; + if (is_choice) { + help[pos] = 1; # do not warn about 'choice' config entries. + } else { + help[pos] = 0; + } +} +/^[ \t]*help[ \t]*$/ { + help[pos] = 1; +} +/^[ \t]*bool[ \t]*$/ { + help[pos] = 1; # ignore options which are not selectable +} +BEGIN { + pos = -1; + is_choice = 0; +} +END { + for (i = 0; i <= pos; i++) { +# printf("%s: help for #%i '%s' == %i\n", file[i], i, conf[i], help[i]); + if (help[i] == 0) { + printf("%s: No helptext for '%s'\n", file[i], conf[i]); + } + } +} diff --git a/scripts/defconfig b/scripts/defconfig new file mode 100644 index 000000000..102c21a5f --- /dev/null +++ b/scripts/defconfig @@ -0,0 +1,665 @@ +# +# +CONFIG_HAVE_DOT_CONFIG=y + +# +# Busybox Settings +# + +# +# General Configuration +# +# CONFIG_NITPICK is not set +# CONFIG_DESKTOP is not set +# CONFIG_FEATURE_BUFFERS_USE_MALLOC is not set +# CONFIG_FEATURE_BUFFERS_GO_ON_STACK is not set +# CONFIG_FEATURE_BUFFERS_GO_IN_BSS is not set +CONFIG_SHOW_USAGE=y +CONFIG_FEATURE_VERBOSE_USAGE=y +CONFIG_FEATURE_COMPRESS_USAGE=y +CONFIG_FEATURE_INSTALLER=y +CONFIG_LOCALE_SUPPORT=y +CONFIG_GETOPT_LONG=y +CONFIG_FEATURE_DEVPTS=y +# CONFIG_FEATURE_CLEAN_UP is not set +CONFIG_FEATURE_SUID=y +CONFIG_FEATURE_SYSLOG=y +CONFIG_FEATURE_SUID_CONFIG=y +CONFIG_FEATURE_SUID_CONFIG_QUIET=y +# CONFIG_SELINUX is not set +CONFIG_BUSYBOX_EXEC_PATH="/proc/self/exe" + +# +# Build Options +# +# CONFIG_STATIC is not set +# CONFIG_BUILD_LIBBUSYBOX is not set +# CONFIG_FEATURE_FULL_LIBBUSYBOX is not set +# CONFIG_FEATURE_SHARED_BUSYBOX is not set +CONFIG_LFS=y +# CONFIG_BUILD_AT_ONCE is not set + +# +# Debugging Options +# +# CONFIG_DEBUG is not set +# CONFIG_DEBUG_PESSIMIZE is not set +# CONFIG_NO_DEBUG_LIB is not set +# CONFIG_DMALLOC is not set +# CONFIG_EFENCE is not set +CONFIG_DEBUG_YANK_SUSv2=y + +# +# Installation Options +# +# CONFIG_INSTALL_NO_USR is not set +CONFIG_INSTALL_APPLET_SYMLINKS=y +# CONFIG_INSTALL_APPLET_HARDLINKS is not set +# CONFIG_INSTALL_APPLET_DONT is not set +CONFIG_PREFIX="./_install" + +# +# Busybox Library Tuning +# +CONFIG_PASSWORD_MINLEN=6 +CONFIG_MD5_SIZE_VS_SPEED=2 + +# +# Applets +# + +# +# Archival Utilities +# +CONFIG_AR=y +CONFIG_FEATURE_AR_LONG_FILENAMES=y +CONFIG_BUNZIP2=y +CONFIG_CPIO=y +# CONFIG_DPKG is not set +# CONFIG_DPKG_DEB is not set +# CONFIG_FEATURE_DPKG_DEB_EXTRACT_ONLY is not set +CONFIG_GUNZIP=y +CONFIG_FEATURE_GUNZIP_UNCOMPRESS=y +CONFIG_GZIP=y +CONFIG_RPM2CPIO=y +CONFIG_RPM=y +CONFIG_TAR=y +CONFIG_FEATURE_TAR_CREATE=y +CONFIG_FEATURE_TAR_BZIP2=y +CONFIG_FEATURE_TAR_LZMA=y +CONFIG_FEATURE_TAR_FROM=y +CONFIG_FEATURE_TAR_GZIP=y +CONFIG_FEATURE_TAR_COMPRESS=y +CONFIG_FEATURE_TAR_OLDGNU_COMPATIBILITY=y +CONFIG_FEATURE_TAR_GNU_EXTENSIONS=y +CONFIG_FEATURE_TAR_LONG_OPTIONS=y +CONFIG_UNCOMPRESS=y +CONFIG_UNLZMA=y +CONFIG_FEATURE_LZMA_FAST=y +CONFIG_UNZIP=y + +# +# Common options for cpio and tar +# +CONFIG_FEATURE_UNARCHIVE_TAPE=y +# CONFIG_FEATURE_DEB_TAR_GZ is not set +# CONFIG_FEATURE_DEB_TAR_BZ2 is not set +# CONFIG_FEATURE_DEB_TAR_LZMA is not set + +# +# Coreutils +# +CONFIG_BASENAME=y +CONFIG_CAL=y +CONFIG_CAT=y +CONFIG_CATV=y +CONFIG_CHGRP=y +CONFIG_CHMOD=y +CONFIG_CHOWN=y +CONFIG_CHROOT=y +CONFIG_CKSUM=y +CONFIG_CMP=y +CONFIG_COMM=y +CONFIG_CP=y +CONFIG_CUT=y +CONFIG_DATE=y +CONFIG_FEATURE_DATE_ISOFMT=y +CONFIG_DD=y +CONFIG_FEATURE_DD_SIGNAL_HANDLING=y +CONFIG_FEATURE_DD_IBS_OBS=y +CONFIG_DF=y +CONFIG_DIFF=y +CONFIG_FEATURE_DIFF_BINARY=y +CONFIG_FEATURE_DIFF_DIR=y +CONFIG_FEATURE_DIFF_MINIMAL=y +CONFIG_DIRNAME=y +CONFIG_DOS2UNIX=y +CONFIG_UNIX2DOS=y +CONFIG_DU=y +CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K=y +CONFIG_ECHO=y +CONFIG_FEATURE_FANCY_ECHO=y +CONFIG_ENV=y +CONFIG_FEATURE_ENV_LONG_OPTIONS=y +CONFIG_EXPR=y +CONFIG_EXPR_MATH_SUPPORT_64=y +CONFIG_FALSE=y +CONFIG_FOLD=y +CONFIG_HEAD=y +CONFIG_FEATURE_FANCY_HEAD=y +CONFIG_HOSTID=y +CONFIG_ID=y +CONFIG_INSTALL=y +CONFIG_FEATURE_INSTALL_LONG_OPTIONS=y +CONFIG_LENGTH=y +CONFIG_LN=y +CONFIG_LOGNAME=y +CONFIG_LS=y +CONFIG_FEATURE_LS_FILETYPES=y +CONFIG_FEATURE_LS_FOLLOWLINKS=y +CONFIG_FEATURE_LS_RECURSIVE=y +CONFIG_FEATURE_LS_SORTFILES=y +CONFIG_FEATURE_LS_TIMESTAMPS=y +CONFIG_FEATURE_LS_USERNAME=y +CONFIG_FEATURE_LS_COLOR=y +CONFIG_FEATURE_LS_COLOR_IS_DEFAULT=y +CONFIG_MD5SUM=y +CONFIG_MKDIR=y +CONFIG_FEATURE_MKDIR_LONG_OPTIONS=y +CONFIG_MKFIFO=y +CONFIG_MKNOD=y +CONFIG_MV=y +CONFIG_FEATURE_MV_LONG_OPTIONS=y +CONFIG_NICE=y +CONFIG_NOHUP=y +CONFIG_OD=y +CONFIG_PRINTENV=y +CONFIG_PRINTF=y +CONFIG_PWD=y +CONFIG_REALPATH=y +CONFIG_RM=y +CONFIG_RMDIR=y +CONFIG_SEQ=y +CONFIG_SHA1SUM=y +CONFIG_SLEEP=y +CONFIG_FEATURE_FANCY_SLEEP=y +CONFIG_SORT=y +CONFIG_FEATURE_SORT_BIG=y +CONFIG_STAT=y +CONFIG_FEATURE_STAT_FORMAT=y +CONFIG_STTY=y +CONFIG_SUM=y +CONFIG_SYNC=y +CONFIG_TAIL=y +CONFIG_FEATURE_FANCY_TAIL=y +CONFIG_TEE=y +CONFIG_FEATURE_TEE_USE_BLOCK_IO=y +CONFIG_TEST=y +CONFIG_FEATURE_TEST_64=y +CONFIG_TOUCH=y +CONFIG_TR=y +CONFIG_FEATURE_TR_CLASSES=y +CONFIG_FEATURE_TR_EQUIV=y +CONFIG_TRUE=y +CONFIG_TTY=y +CONFIG_UNAME=y +CONFIG_UNIQ=y +CONFIG_USLEEP=y +CONFIG_UUDECODE=y +CONFIG_UUENCODE=y +CONFIG_WATCH=y +CONFIG_WC=y +CONFIG_FEATURE_WC_LARGE=y +CONFIG_WHO=y +CONFIG_WHOAMI=y +CONFIG_YES=y + +# +# Common options for cp and mv +# +CONFIG_FEATURE_PRESERVE_HARDLINKS=y + +# +# Common options for ls, more and telnet +# +CONFIG_FEATURE_AUTOWIDTH=y + +# +# Common options for df, du, ls +# +CONFIG_FEATURE_HUMAN_READABLE=y + +# +# Common options for md5sum, sha1sum +# +CONFIG_FEATURE_MD5_SHA1_SUM_CHECK=y + +# +# Console Utilities +# +CONFIG_CHVT=y +CONFIG_CLEAR=y +CONFIG_DEALLOCVT=y +CONFIG_DUMPKMAP=y +CONFIG_LOADFONT=y +CONFIG_LOADKMAP=y +CONFIG_OPENVT=y +CONFIG_RESET=y +CONFIG_RESIZE=y +CONFIG_FEATURE_RESIZE_PRINT=y +CONFIG_SETCONSOLE=y +# CONFIG_FEATURE_SETCONSOLE_LONG_OPTIONS is not set +CONFIG_SETKEYCODES=y +CONFIG_SETLOGCONS=y + +# +# Debian Utilities +# +CONFIG_MKTEMP=y +CONFIG_PIPE_PROGRESS=y +CONFIG_READLINK=y +CONFIG_FEATURE_READLINK_FOLLOW=y +CONFIG_RUN_PARTS=y +CONFIG_FEATURE_RUN_PARTS_LONG_OPTIONS=y +CONFIG_START_STOP_DAEMON=y +CONFIG_FEATURE_START_STOP_DAEMON_FANCY=y +CONFIG_FEATURE_START_STOP_DAEMON_LONG_OPTIONS=y +CONFIG_WHICH=y + +# +# Editors +# +CONFIG_AWK=y +CONFIG_FEATURE_AWK_MATH=y +CONFIG_ED=y +CONFIG_PATCH=y +CONFIG_SED=y +CONFIG_VI=y +CONFIG_FEATURE_VI_COLON=y +CONFIG_FEATURE_VI_YANKMARK=y +CONFIG_FEATURE_VI_SEARCH=y +CONFIG_FEATURE_VI_USE_SIGNALS=y +CONFIG_FEATURE_VI_DOT_CMD=y +CONFIG_FEATURE_VI_READONLY=y +CONFIG_FEATURE_VI_SETOPTS=y +CONFIG_FEATURE_VI_SET=y +CONFIG_FEATURE_VI_WIN_RESIZE=y +CONFIG_FEATURE_VI_OPTIMIZE_CURSOR=y + +# +# Finding Utilities +# +CONFIG_FIND=y +CONFIG_FEATURE_FIND_PRINT0=y +CONFIG_FEATURE_FIND_MTIME=y +CONFIG_FEATURE_FIND_MMIN=y +CONFIG_FEATURE_FIND_PERM=y +CONFIG_FEATURE_FIND_TYPE=y +CONFIG_FEATURE_FIND_XDEV=y +CONFIG_FEATURE_FIND_NEWER=y +CONFIG_FEATURE_FIND_INUM=y +CONFIG_FEATURE_FIND_EXEC=y +CONFIG_GREP=y +CONFIG_FEATURE_GREP_EGREP_ALIAS=y +CONFIG_FEATURE_GREP_FGREP_ALIAS=y +CONFIG_FEATURE_GREP_CONTEXT=y +CONFIG_XARGS=y +CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION=y +CONFIG_FEATURE_XARGS_SUPPORT_QUOTES=y +CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT=y +CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM=y + +# +# Init Utilities +# +CONFIG_INIT=y +# CONFIG_DEBUG_INIT is not set +CONFIG_FEATURE_USE_INITTAB=y +CONFIG_FEATURE_INIT_SCTTY=y +CONFIG_FEATURE_EXTRA_QUIET=y +CONFIG_FEATURE_INIT_COREDUMPS=y +CONFIG_FEATURE_INITRD=y +CONFIG_HALT=y +CONFIG_MESG=y + +# +# Login/Password Management Utilities +# +CONFIG_FEATURE_SHADOWPASSWDS=y +CONFIG_USE_BB_SHADOW=y +CONFIG_USE_BB_PWD_GRP=y +CONFIG_ADDGROUP=y +CONFIG_DELGROUP=y +CONFIG_ADDUSER=y +CONFIG_DELUSER=y +CONFIG_GETTY=y +CONFIG_FEATURE_UTMP=y +CONFIG_FEATURE_WTMP=y +CONFIG_LOGIN=y +CONFIG_LOGIN_SCRIPTS=y +CONFIG_FEATURE_SECURETTY=y +CONFIG_PASSWD=y +CONFIG_SU=y +CONFIG_SU_SYSLOG=y +CONFIG_SULOGIN=y +CONFIG_VLOCK=y + +# +# Linux Ext2 FS Progs +# +CONFIG_CHATTR=y +# CONFIG_E2FSCK is not set +CONFIG_FSCK=y +CONFIG_LSATTR=y +# CONFIG_MKE2FS is not set +# CONFIG_TUNE2FS is not set +# CONFIG_E2LABEL is not set +# CONFIG_FINDFS is not set + +# +# Linux Module Utilities +# +CONFIG_INSMOD=y +CONFIG_FEATURE_INSMOD_VERSION_CHECKING=y +CONFIG_FEATURE_INSMOD_KSYMOOPS_SYMBOLS=y +CONFIG_FEATURE_INSMOD_LOADINKMEM=y +CONFIG_FEATURE_INSMOD_LOAD_MAP=y +CONFIG_FEATURE_INSMOD_LOAD_MAP_FULL=y +CONFIG_RMMOD=y +CONFIG_LSMOD=y +CONFIG_FEATURE_LSMOD_PRETTY_2_6_OUTPUT=y +CONFIG_MODPROBE=y +CONFIG_FEATURE_MODPROBE_MULTIPLE_OPTIONS=y +CONFIG_FEATURE_MODPROBE_FANCY_ALIAS=y + +# +# Options common to multiple modutils +# +CONFIG_FEATURE_CHECK_TAINTED_MODULE=y +CONFIG_FEATURE_2_4_MODULES=y +CONFIG_FEATURE_2_6_MODULES=y +# CONFIG_FEATURE_QUERY_MODULE_INTERFACE is not set + +# +# Linux System Utilities +# +CONFIG_DMESG=y +CONFIG_FEATURE_DMESG_PRETTY=y +CONFIG_FBSET=y +CONFIG_FEATURE_FBSET_FANCY=y +CONFIG_FEATURE_FBSET_READMODE=y +CONFIG_FDFLUSH=y +CONFIG_FDFORMAT=y +CONFIG_FDISK=y +CONFIG_FDISK_SUPPORT_LARGE_DISKS=y +CONFIG_FEATURE_FDISK_WRITABLE=y +# CONFIG_FEATURE_AIX_LABEL is not set +# CONFIG_FEATURE_SGI_LABEL is not set +# CONFIG_FEATURE_SUN_LABEL is not set +# CONFIG_FEATURE_OSF_LABEL is not set +CONFIG_FEATURE_FDISK_ADVANCED=y +CONFIG_FREERAMDISK=y +CONFIG_FSCK_MINIX=y +CONFIG_MKFS_MINIX=y + +# +# Minix filesystem support +# +CONFIG_FEATURE_MINIX2=y +CONFIG_GETOPT=y +CONFIG_HEXDUMP=y +CONFIG_HWCLOCK=y +CONFIG_FEATURE_HWCLOCK_LONG_OPTIONS=y +CONFIG_FEATURE_HWCLOCK_ADJTIME_FHS=y +CONFIG_IPCRM=y +CONFIG_IPCS=y +CONFIG_LOSETUP=y +CONFIG_MDEV=y +CONFIG_FEATURE_MDEV_CONF=y +CONFIG_FEATURE_MDEV_EXEC=y +CONFIG_MKSWAP=y +CONFIG_FEATURE_MKSWAP_V0=y +CONFIG_MORE=y +CONFIG_FEATURE_USE_TERMIOS=y +CONFIG_MOUNT=y +CONFIG_FEATURE_MOUNT_NFS=y +CONFIG_FEATURE_MOUNT_CIFS=y +CONFIG_FEATURE_MOUNT_FLAGS=y +CONFIG_FEATURE_MOUNT_FSTAB=y +CONFIG_PIVOT_ROOT=y +CONFIG_RDATE=y +CONFIG_READPROFILE=y +CONFIG_SETARCH=y +CONFIG_SWAPONOFF=y +CONFIG_SWITCH_ROOT=y +CONFIG_UMOUNT=y +CONFIG_FEATURE_UMOUNT_ALL=y + +# +# Common options for mount/umount +# +CONFIG_FEATURE_MOUNT_LOOP=y +# CONFIG_FEATURE_MTAB_SUPPORT is not set + +# +# Miscellaneous Utilities +# +CONFIG_ADJTIMEX=y +CONFIG_BBCONFIG=y +CONFIG_CROND=y +# CONFIG_DEBUG_CROND_OPTION is not set +CONFIG_FEATURE_CROND_CALL_SENDMAIL=y +CONFIG_CRONTAB=y +CONFIG_DC=y +# CONFIG_DEVFSD is not set +# CONFIG_DEVFSD_MODLOAD is not set +# CONFIG_DEVFSD_FG_NP is not set +# CONFIG_DEVFSD_VERBOSE is not set +# CONFIG_FEATURE_DEVFS is not set +CONFIG_EJECT=y +CONFIG_LAST=y +CONFIG_LESS=y +CONFIG_FEATURE_LESS_BRACKETS=y +CONFIG_FEATURE_LESS_FLAGS=y +CONFIG_FEATURE_LESS_FLAGCS=y +CONFIG_FEATURE_LESS_MARKS=y +CONFIG_FEATURE_LESS_REGEXP=y +CONFIG_HDPARM=y +CONFIG_FEATURE_HDPARM_GET_IDENTITY=y +CONFIG_FEATURE_HDPARM_HDIO_SCAN_HWIF=y +CONFIG_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF=y +CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET=y +CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF=y +CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA=y +CONFIG_MAKEDEVS=y +# CONFIG_FEATURE_MAKEDEVS_LEAF is not set +CONFIG_FEATURE_MAKEDEVS_TABLE=y +CONFIG_MOUNTPOINT=y +CONFIG_MT=y +CONFIG_NMETER=y +CONFIG_RAIDAUTORUN=y +CONFIG_READAHEAD=y +CONFIG_RUNLEVEL=y +CONFIG_RX=y +CONFIG_STRINGS=y +CONFIG_SETSID=y +CONFIG_TASKSET=y +CONFIG_TIME=y +CONFIG_WATCHDOG=y + +# +# Networking Utilities +# +CONFIG_FEATURE_IPV6=y +CONFIG_ARPING=y +CONFIG_DNSD=y +CONFIG_ETHER_WAKE=y +CONFIG_FAKEIDENTD=y +CONFIG_FTPGET=y +CONFIG_FTPPUT=y +CONFIG_FEATURE_FTPGETPUT_LONG_OPTIONS=y +CONFIG_HOSTNAME=y +CONFIG_HTTPD=y +# CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP is not set +# CONFIG_FEATURE_HTTPD_SETUID is not set +CONFIG_FEATURE_HTTPD_BASIC_AUTH=y +CONFIG_FEATURE_HTTPD_AUTH_MD5=y +CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES=y +CONFIG_FEATURE_HTTPD_CGI=y +CONFIG_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR=y +CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV=y +CONFIG_FEATURE_HTTPD_ENCODE_URL_STR=y +CONFIG_IFCONFIG=y +CONFIG_FEATURE_IFCONFIG_STATUS=y +CONFIG_FEATURE_IFCONFIG_SLIP=y +CONFIG_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ=y +CONFIG_FEATURE_IFCONFIG_HW=y +CONFIG_FEATURE_IFCONFIG_BROADCAST_PLUS=y +CONFIG_IFUPDOWN=y +CONFIG_FEATURE_IFUPDOWN_IP=y +CONFIG_FEATURE_IFUPDOWN_IP_BUILTIN=y +# CONFIG_FEATURE_IFUPDOWN_IFCONFIG_BUILTIN is not set +CONFIG_FEATURE_IFUPDOWN_IPV4=y +CONFIG_FEATURE_IFUPDOWN_IPV6=y +CONFIG_FEATURE_IFUPDOWN_IPX=y +CONFIG_FEATURE_IFUPDOWN_MAPPING=y +CONFIG_INETD=y +CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO=y +CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD=y +CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME=y +CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME=y +CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN=y +CONFIG_FEATURE_INETD_RPC=y +CONFIG_IP=y +CONFIG_FEATURE_IP_ADDRESS=y +CONFIG_FEATURE_IP_LINK=y +CONFIG_FEATURE_IP_ROUTE=y +CONFIG_FEATURE_IP_TUNNEL=y +CONFIG_FEATURE_IP_RULE=y +CONFIG_FEATURE_IP_SHORT_FORMS=y +CONFIG_IPADDR=y +CONFIG_IPLINK=y +CONFIG_IPROUTE=y +CONFIG_IPTUNNEL=y +CONFIG_IPRULE=y +CONFIG_IPCALC=y +CONFIG_FEATURE_IPCALC_FANCY=y +CONFIG_FEATURE_IPCALC_LONG_OPTIONS=y +CONFIG_NAMEIF=y +CONFIG_NC=y +CONFIG_NC_SERVER=y +CONFIG_NC_EXTRA=y +CONFIG_NETSTAT=y +CONFIG_NSLOOKUP=y +CONFIG_PING=y +CONFIG_FEATURE_FANCY_PING=y +CONFIG_PING6=y +CONFIG_FEATURE_FANCY_PING6=y +CONFIG_ROUTE=y +CONFIG_TELNET=y +CONFIG_FEATURE_TELNET_TTYPE=y +CONFIG_FEATURE_TELNET_AUTOLOGIN=y +CONFIG_TELNETD=y +CONFIG_TFTP=y +CONFIG_FEATURE_TFTP_GET=y +CONFIG_FEATURE_TFTP_PUT=y +CONFIG_FEATURE_TFTP_BLOCKSIZE=y +# CONFIG_DEBUG_TFTP is not set +CONFIG_TRACEROUTE=y +# CONFIG_FEATURE_TRACEROUTE_VERBOSE is not set +# CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE is not set +# CONFIG_FEATURE_TRACEROUTE_USE_ICMP is not set +CONFIG_APP_UDHCPD=y +CONFIG_APP_DHCPRELAY=y +CONFIG_APP_DUMPLEASES=y +CONFIG_APP_UDHCPC=y +CONFIG_FEATURE_UDHCP_SYSLOG=y +# CONFIG_FEATURE_UDHCP_DEBUG is not set +CONFIG_VCONFIG=y +CONFIG_WGET=y +CONFIG_FEATURE_WGET_STATUSBAR=y +CONFIG_FEATURE_WGET_AUTHENTICATION=y +CONFIG_FEATURE_WGET_IP6_LITERAL=y +CONFIG_FEATURE_WGET_LONG_OPTIONS=y +CONFIG_ZCIP=y + +# +# Process Utilities +# +CONFIG_FREE=y +CONFIG_FUSER=y +CONFIG_KILL=y +CONFIG_KILLALL=y +CONFIG_KILLALL5=y +CONFIG_PIDOF=y +CONFIG_FEATURE_PIDOF_SINGLE=y +CONFIG_FEATURE_PIDOF_OMIT=y +CONFIG_PS=y +CONFIG_FEATURE_PS_WIDE=y +CONFIG_RENICE=y +CONFIG_BB_SYSCTL=y +CONFIG_TOP=y +CONFIG_FEATURE_TOP_CPU_USAGE_PERCENTAGE=y +CONFIG_UPTIME=y + +# +# Shells +# +# CONFIG_FEATURE_SH_IS_ASH is not set +# CONFIG_FEATURE_SH_IS_HUSH is not set +# CONFIG_FEATURE_SH_IS_LASH is not set +# CONFIG_FEATURE_SH_IS_MSH is not set +CONFIG_FEATURE_SH_IS_NONE=y +# CONFIG_ASH is not set +# CONFIG_ASH_JOB_CONTROL is not set +# CONFIG_ASH_READ_NCHARS is not set +# CONFIG_ASH_READ_TIMEOUT is not set +# CONFIG_ASH_ALIAS is not set +# CONFIG_ASH_MATH_SUPPORT is not set +# CONFIG_ASH_MATH_SUPPORT_64 is not set +# CONFIG_ASH_GETOPTS is not set +# CONFIG_ASH_BUILTIN_ECHO is not set +# CONFIG_ASH_BUILTIN_TEST is not set +# CONFIG_ASH_CMDCMD is not set +# CONFIG_ASH_MAIL is not set +# CONFIG_ASH_OPTIMIZE_FOR_SIZE is not set +# CONFIG_ASH_RANDOM_SUPPORT is not set +# CONFIG_ASH_EXPAND_PRMT is not set +# CONFIG_HUSH is not set +# CONFIG_LASH is not set +# CONFIG_MSH is not set +# CONFIG_FEATURE_SH_EXTRA_QUIET is not set +# CONFIG_FEATURE_SH_STANDALONE_SHELL is not set +# CONFIG_FEATURE_COMMAND_EDITING is not set +# CONFIG_FEATURE_COMMAND_EDITING_VI is not set +CONFIG_FEATURE_COMMAND_HISTORY= +# CONFIG_FEATURE_COMMAND_SAVEHISTORY is not set +# CONFIG_FEATURE_COMMAND_TAB_COMPLETION is not set +# CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION is not set +# CONFIG_FEATURE_SH_FANCY_PROMPT is not set + +# +# System Logging Utilities +# +CONFIG_SYSLOGD=y +CONFIG_FEATURE_ROTATE_LOGFILE=y +CONFIG_FEATURE_REMOTE_LOG=y +CONFIG_FEATURE_IPC_SYSLOG=y +CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE=16 +CONFIG_LOGREAD=y +CONFIG_FEATURE_LOGREAD_REDUCED_LOCKING=y +CONFIG_KLOGD=y +CONFIG_LOGGER=y + +# +# Runit Utilities +# +# CONFIG_RUNSV is not set +# CONFIG_RUNSVDIR is not set +# CONFIG_SV is not set +# CONFIG_SVLOGD is not set +CONFIG_CHPST=y +CONFIG_SETUIDGID=y +CONFIG_ENVUIDGID=y +CONFIG_ENVDIR=y +CONFIG_SOFTLIMIT=y diff --git a/scripts/individual b/scripts/individual new file mode 100755 index 000000000..a09a5dc7a --- /dev/null +++ b/scripts/individual @@ -0,0 +1,129 @@ +#!/bin/sh + +# Compile individual versions of each busybox applet. + +if [ $# -eq 0 ] +then + +# Clear out the build directory. (Make clean should do this instead of here.) + +rm -rf build +mkdir build + +# Make our prerequisites. + +make busybox.links include/bb_config.h $(pwd)/{libbb/libbb.a,archival/libunarchive/libunarchive.a,coreutils/libcoreutils/libcoreutils.a,networking/libiproute/libiproute.a} + +else +# Could very well be that we want to build an individual applet but have no +# 'build' dir yet.. + +test -d ./build || mkdir build + +fi + +# About 3/5 of the applets build from one .c file (with the same name as the +# corresponding applet), and all it needs to link against. However, to build +# them all we need more than that. + +# Figure out which applets need extra libraries added to their command line. + +function substithing() +{ + if [ "${1/ $3 //}" != "$1" ] + then + echo $2 + fi +} + +function extra_libraries() +{ + # gzip needs gunzip.c (when gunzip is enabled, anyway). + substithing " gzip " "archival/gunzip.c archival/uncompress.c" "$1" + + # init needs init_shared.c + substithing " init " "init/init_shared.c" "$1" + + # ifconfig needs interface.c + substithing " ifconfig " "networking/interface.c" "$1" + + # Applets that need libunarchive.a + substithing " ar bunzip2 unlzma cpio dpkg gunzip rpm2cpio rpm tar uncompress unzip dpkg_deb gzip " "archival/libunarchive/libunarchive.a" "$1" + + # Applets that need libcoreutils.a + substithing " cp mv " "coreutils/libcoreutils/libcoreutils.a" "$1" + + # Applets that need libiproute.a + substithing " ip " "networking/libiproute/libiproute.a" "$1" + + # What needs -libm? + substithing " awk dc " "-lm" "$1" + + # What needs -lcrypt? + substithing " httpd vlock " "-lcrypt" "$1" +} + +# Query applets.h to figure out which applets need special treatment + +strange_names=`sed -rn -e 's/\#.*//' -e 's/.*APPLET_NOUSAGE\(([^,]*),([^,]*),.*/\1 \2/p' -e 's/.*APPLET_ODDNAME\(([^,]*),([^,]*),.*, *([^)]*).*/\1 \2@\3/p' include/applets.h` + +function bonkname() +{ + while [ $# -gt 0 ] + do + if [ "$APPLET" == "$1" ] + then + APPFILT="${2/@*/}" + if [ "${APPFILT}" == "$2" ] + then + HELPNAME='"nousage\n"' # These should be _fixed_. + else + HELPNAME="${2/*@/}"_full_usage + fi + break + fi + shift 2 + done +#echo APPLET=${APPLET} APPFILT=${APPFILT} HELPNAME=${HELPNAME} 2=${2} +} + +# Iterate through every name in busybox.links + +function buildit () +{ + export APPLET="$1" + export APPFILT=${APPLET} + export HELPNAME=${APPLET}_full_usage + + bonkname $strange_names + + j=`find archival console-tools coreutils debianutils editors findutils init loginutils miscutils modutils networking procps shell sysklogd util-linux -name "${APPFILT}.c"` + if [ -z "$j" ] + then + echo no file for $APPLET + else + echo "Building $APPLET" + gcc -Os -o build/$APPLET applets/individual.c $j \ + `extra_libraries $APPFILT` libbb/libbb.a -Iinclude \ + -DBUILD_INDIVIDUAL \ + '-Drun_applet_by_name(...)' '-Dfind_applet_by_name(...)=0' \ + -DAPPLET_main=${APPFILT}_main -DAPPLET_full_usage=${HELPNAME} + if [ $? -ne 0 ]; + then + echo "Failed $APPLET" + fi + fi +} + +if [ $# -eq 0 ] +then + for APPLET in `sed 's .*/ ' busybox.links` + do + buildit "$APPLET" + done +else + buildit "$1" +fi + + +strip build/* diff --git a/scripts/kconfig/Makefile b/scripts/kconfig/Makefile new file mode 100644 index 000000000..a28414de8 --- /dev/null +++ b/scripts/kconfig/Makefile @@ -0,0 +1,254 @@ +# =========================================================================== +# Kernel configuration targets +# These targets are used from top-level makefile + +PHONY += oldconfig xconfig gconfig menuconfig config silentoldconfig update-po-config + +xconfig: $(obj)/qconf + $< Config.in + +gconfig: $(obj)/gconf + $< Config.in + +menuconfig: $(obj)/mconf + $(Q)$(MAKE) $(build)=scripts/kconfig/lxdialog + $< Config.in + +config: $(obj)/conf + $< Config.in + +oldconfig: $(obj)/conf + $< -o Config.in + +silentoldconfig: $(obj)/conf + $< -s Config.in + +update-po-config: $(obj)/kxgettext + xgettext --default-domain=linux \ + --add-comments --keyword=_ --keyword=N_ \ + --files-from=scripts/kconfig/POTFILES.in \ + --output scripts/kconfig/config.pot + $(Q)ln -fs Kconfig_i386 arch/um/Kconfig_arch + $(Q)for i in `ls arch/`; \ + do \ + scripts/kconfig/kxgettext arch/$$i/Kconfig \ + | msguniq -o scripts/kconfig/linux_$${i}.pot; \ + done + $(Q)msgcat scripts/kconfig/config.pot \ + `find scripts/kconfig/ -type f -name linux_*.pot` \ + --output scripts/kconfig/linux_raw.pot + $(Q)msguniq --sort-by-file scripts/kconfig/linux_raw.pot \ + --output scripts/kconfig/linux.pot + $(Q)rm -f arch/um/Kconfig_arch + $(Q)rm -f scripts/kconfig/linux_*.pot scripts/kconfig/config.pot + +PHONY += randconfig allyesconfig allnoconfig allmodconfig defconfig + +randconfig: $(obj)/conf + $< -r Config.in + +allyesconfig: $(obj)/conf + $< -y Config.in + +allnoconfig: $(obj)/conf + $< -n Config.in + +allmodconfig: $(obj)/conf + $< -m Config.in + +defconfig: $(obj)/conf +ifeq ($(KBUILD_DEFCONFIG),) + $< -d Config.in +else + @echo *** Default configuration is based on '$(KBUILD_DEFCONFIG)' + $(Q)$< -D $(KBUILD_DEFCONFIG) Config.in +endif + +%_defconfig: $(obj)/conf + $(Q)$< -D $@ Config.in + +# Help text used by make help +help: + @echo ' config - Update current config utilising a line-oriented program' + @echo ' menuconfig - Update current config utilising a menu based program' + @echo ' xconfig - Update current config utilising a QT based front-end' + @echo ' gconfig - Update current config utilising a GTK based front-end' + @echo ' oldconfig - Update current config utilising a provided .config as base' + @echo ' randconfig - New config with random answer to all options' + @echo ' defconfig - New config with default answer to all options' + @echo ' allmodconfig - New config selecting modules when possible' + @echo ' allyesconfig - New config where all options are accepted with yes' + @echo ' allnoconfig - New config where all options are answered with no' + +# =========================================================================== +# Shared Makefile for the various kconfig executables: +# conf: Used for defconfig, oldconfig and related targets +# mconf: Used for the mconfig target. +# Utilizes the lxdialog package +# qconf: Used for the xconfig target +# Based on QT which needs to be installed to compile it +# gconf: Used for the gconfig target +# Based on GTK which needs to be installed to compile it +# object files used by all kconfig flavours + +hostprogs-y := conf mconf qconf gconf kxgettext +conf-objs := conf.o zconf.tab.o +mconf-objs := mconf.o zconf.tab.o +kxgettext-objs := kxgettext.o zconf.tab.o + +ifeq ($(MAKECMDGOALS),xconfig) + qconf-target := 1 +endif +ifeq ($(MAKECMDGOALS),gconfig) + gconf-target := 1 +endif + + +ifeq ($(qconf-target),1) +qconf-cxxobjs := qconf.o +qconf-objs := kconfig_load.o zconf.tab.o +endif + +ifeq ($(gconf-target),1) +gconf-objs := gconf.o kconfig_load.o zconf.tab.o +endif + +clean-files := lkc_defs.h qconf.moc .tmp_qtcheck \ + .tmp_gtkcheck zconf.tab.c lex.zconf.c zconf.hash.c +subdir- += lxdialog + +# Needed for systems without gettext +KBUILD_HAVE_NLS := $(shell \ + if echo "\#include " | $(HOSTCC) $(HOSTCFLAGS) -E - > /dev/null 2>&1 ; \ + then echo yes ; \ + else echo no ; fi) +ifeq ($(KBUILD_HAVE_NLS),no) +HOSTCFLAGS += -DKBUILD_NO_NLS +endif + +# generated files seem to need this to find local include files +HOSTCFLAGS_lex.zconf.o := -I$(src) +HOSTCFLAGS_zconf.tab.o := -I$(src) + +HOSTLOADLIBES_qconf = $(KC_QT_LIBS) -ldl +HOSTCXXFLAGS_qconf.o = $(KC_QT_CFLAGS) -D LKC_DIRECT_LINK + +HOSTLOADLIBES_gconf = `pkg-config --libs gtk+-2.0 gmodule-2.0 libglade-2.0` +HOSTCFLAGS_gconf.o = `pkg-config --cflags gtk+-2.0 gmodule-2.0 libglade-2.0` \ + -D LKC_DIRECT_LINK + +$(obj)/qconf.o: $(obj)/.tmp_qtcheck + +ifeq ($(qconf-target),1) +$(obj)/.tmp_qtcheck: $(src)/Makefile +-include $(obj)/.tmp_qtcheck + +# QT needs some extra effort... +$(obj)/.tmp_qtcheck: + @set -e; echo " CHECK qt"; dir=""; pkg=""; \ + pkg-config --exists qt 2> /dev/null && pkg=qt; \ + pkg-config --exists qt-mt 2> /dev/null && pkg=qt-mt; \ + if [ -n "$$pkg" ]; then \ + cflags="\$$(shell pkg-config $$pkg --cflags)"; \ + libs="\$$(shell pkg-config $$pkg --libs)"; \ + moc="\$$(shell pkg-config $$pkg --variable=prefix)/bin/moc"; \ + dir="$$(pkg-config $$pkg --variable=prefix)"; \ + else \ + for d in $$QTDIR /usr/share/qt* /usr/lib/qt*; do \ + if [ -f $$d/include/qconfig.h ]; then dir=$$d; break; fi; \ + done; \ + if [ -z "$$dir" ]; then \ + echo "*"; \ + echo "* Unable to find the QT installation. Please make sure that"; \ + echo "* the QT development package is correctly installed and"; \ + echo "* either install pkg-config or set the QTDIR environment"; \ + echo "* variable to the correct location."; \ + echo "*"; \ + false; \ + fi; \ + libpath=$$dir/lib; lib=qt; osdir=""; \ + $(HOSTCXX) -print-multi-os-directory > /dev/null 2>&1 && \ + osdir=x$$($(HOSTCXX) -print-multi-os-directory); \ + test -d $$libpath/$$osdir && libpath=$$libpath/$$osdir; \ + test -f $$libpath/libqt-mt.so && lib=qt-mt; \ + cflags="-I$$dir/include"; \ + libs="-L$$libpath -Wl,-rpath,$$libpath -l$$lib"; \ + moc="$$dir/bin/moc"; \ + fi; \ + if [ ! -x $$dir/bin/moc -a -x /usr/bin/moc ]; then \ + echo "*"; \ + echo "* Unable to find $$dir/bin/moc, using /usr/bin/moc instead."; \ + echo "*"; \ + moc="/usr/bin/moc"; \ + fi; \ + echo "KC_QT_CFLAGS=$$cflags" > $@; \ + echo "KC_QT_LIBS=$$libs" >> $@; \ + echo "KC_QT_MOC=$$moc" >> $@ +endif + +$(obj)/gconf.o: $(obj)/.tmp_gtkcheck + +ifeq ($(gconf-target),1) +-include $(obj)/.tmp_gtkcheck + +# GTK needs some extra effort, too... +$(obj)/.tmp_gtkcheck: + @if `pkg-config --exists gtk+-2.0 gmodule-2.0 libglade-2.0`; then \ + if `pkg-config --atleast-version=2.0.0 gtk+-2.0`; then \ + touch $@; \ + else \ + echo "*"; \ + echo "* GTK+ is present but version >= 2.0.0 is required."; \ + echo "*"; \ + false; \ + fi \ + else \ + echo "*"; \ + echo "* Unable to find the GTK+ installation. Please make sure that"; \ + echo "* the GTK+ 2.0 development package is correctly installed..."; \ + echo "* You need gtk+-2.0, glib-2.0 and libglade-2.0."; \ + echo "*"; \ + false; \ + fi +endif + +$(obj)/zconf.tab.o: $(obj)/lex.zconf.c $(obj)/zconf.hash.c + +$(obj)/kconfig_load.o: $(obj)/lkc_defs.h + +$(obj)/qconf.o: $(obj)/qconf.moc $(obj)/lkc_defs.h + +$(obj)/gconf.o: $(obj)/lkc_defs.h + +$(obj)/%.moc: $(src)/%.h + $(KC_QT_MOC) -i $< -o $@ + +$(obj)/lkc_defs.h: $(src)/lkc_proto.h + sed < $< > $@ 's/P(\([^,]*\),.*/#define \1 (\*\1_p)/' + + +### +# The following requires flex/bison/gperf +# By default we use the _shipped versions, uncomment the following line if +# you are modifying the flex/bison src. +# LKC_GENPARSER := 1 + +ifdef LKC_GENPARSER + +$(obj)/zconf.tab.c: $(src)/zconf.y +$(obj)/lex.zconf.c: $(src)/zconf.l +$(obj)/zconf.hash.c: $(src)/zconf.gperf + +%.tab.c: %.y + bison -l -b $* -p $(notdir $*) $< + cp $@ $@_shipped + +lex.%.c: %.l + flex -L -P$(notdir $*) -o$@ $< + cp $@ $@_shipped + +%.hash.c: %.gperf + gperf < $< > $@ + cp $@ $@_shipped + +endif diff --git a/scripts/kconfig/POTFILES.in b/scripts/kconfig/POTFILES.in new file mode 100644 index 000000000..cc94e46a7 --- /dev/null +++ b/scripts/kconfig/POTFILES.in @@ -0,0 +1,5 @@ +scripts/kconfig/mconf.c +scripts/kconfig/conf.c +scripts/kconfig/confdata.c +scripts/kconfig/gconf.c +scripts/kconfig/qconf.cc diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c new file mode 100644 index 000000000..a95ba93e2 --- /dev/null +++ b/scripts/kconfig/conf.c @@ -0,0 +1,612 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define LKC_DIRECT_LINK +#include "lkc.h" + +static void conf(struct menu *menu); +static void check_conf(struct menu *menu); + +enum { + ask_all, + ask_new, + ask_silent, + set_default, + set_yes, + set_mod, + set_no, + set_random +} input_mode = ask_all; +char *defconfig_file; + +static int indent = 1; +static int valid_stdin = 1; +static int conf_cnt; +static char line[128]; +static struct menu *rootEntry; + +static char nohelp_text[] = N_("Sorry, no help available for this option yet.\n"); + +static void strip(char *str) +{ + char *p = str; + int l; + + while ((isspace(*p))) + p++; + l = strlen(p); + if (p != str) + memmove(str, p, l + 1); + if (!l) + return; + p = str + l - 1; + while ((isspace(*p))) + *p-- = 0; +} + +static void check_stdin(void) +{ + if (!valid_stdin && input_mode == ask_silent) { + printf(_("aborted!\n\n")); + printf(_("Console input/output is redirected. ")); + printf(_("Run 'make oldconfig' to update configuration.\n\n")); + exit(1); + } +} + +static void conf_askvalue(struct symbol *sym, const char *def) +{ + enum symbol_type type = sym_get_type(sym); + tristate val; + + if (!sym_has_value(sym)) + printf("(NEW) "); + + line[0] = '\n'; + line[1] = 0; + + if (!sym_is_changable(sym)) { + printf("%s\n", def); + line[0] = '\n'; + line[1] = 0; + return; + } + + switch (input_mode) { + case set_no: + case set_mod: + case set_yes: + case set_random: + if (sym_has_value(sym)) { + printf("%s\n", def); + return; + } + break; + case ask_new: + case ask_silent: + if (sym_has_value(sym)) { + printf("%s\n", def); + return; + } + check_stdin(); + case ask_all: + fflush(stdout); + fgets(line, 128, stdin); + return; + case set_default: + printf("%s\n", def); + return; + default: + break; + } + + switch (type) { + case S_INT: + case S_HEX: + case S_STRING: + printf("%s\n", def); + return; + default: + ; + } + switch (input_mode) { + case set_yes: + if (sym_tristate_within_range(sym, yes)) { + line[0] = 'y'; + line[1] = '\n'; + line[2] = 0; + break; + } + case set_mod: + if (type == S_TRISTATE) { + if (sym_tristate_within_range(sym, mod)) { + line[0] = 'm'; + line[1] = '\n'; + line[2] = 0; + break; + } + } else { + if (sym_tristate_within_range(sym, yes)) { + line[0] = 'y'; + line[1] = '\n'; + line[2] = 0; + break; + } + } + case set_no: + if (sym_tristate_within_range(sym, no)) { + line[0] = 'n'; + line[1] = '\n'; + line[2] = 0; + break; + } + case set_random: + do { + val = (tristate)(random() % 3); + } while (!sym_tristate_within_range(sym, val)); + switch (val) { + case no: line[0] = 'n'; break; + case mod: line[0] = 'm'; break; + case yes: line[0] = 'y'; break; + } + line[1] = '\n'; + line[2] = 0; + break; + default: + break; + } + printf("%s", line); +} + +int conf_string(struct menu *menu) +{ + struct symbol *sym = menu->sym; + const char *def, *help; + + while (1) { + printf("%*s%s ", indent - 1, "", menu->prompt->text); + printf("(%s) ", sym->name); + def = sym_get_string_value(sym); + if (sym_get_string_value(sym)) + printf("[%s] ", def); + conf_askvalue(sym, def); + switch (line[0]) { + case '\n': + break; + case '?': + /* print help */ + if (line[1] == '\n') { + help = nohelp_text; + if (menu->sym->help) + help = menu->sym->help; + printf("\n%s\n", menu->sym->help); + def = NULL; + break; + } + default: + line[strlen(line)-1] = 0; + def = line; + } + if (def && sym_set_string_value(sym, def)) + return 0; + } +} + +static int conf_sym(struct menu *menu) +{ + struct symbol *sym = menu->sym; + int type; + tristate oldval, newval; + const char *help; + + while (1) { + printf("%*s%s ", indent - 1, "", menu->prompt->text); + if (sym->name) + printf("(%s) ", sym->name); + type = sym_get_type(sym); + putchar('['); + oldval = sym_get_tristate_value(sym); + switch (oldval) { + case no: + putchar('N'); + break; + case mod: + putchar('M'); + break; + case yes: + putchar('Y'); + break; + } + if (oldval != no && sym_tristate_within_range(sym, no)) + printf("/n"); + if (oldval != mod && sym_tristate_within_range(sym, mod)) + printf("/m"); + if (oldval != yes && sym_tristate_within_range(sym, yes)) + printf("/y"); + if (sym->help) + printf("/?"); + printf("] "); + conf_askvalue(sym, sym_get_string_value(sym)); + strip(line); + + switch (line[0]) { + case 'n': + case 'N': + newval = no; + if (!line[1] || !strcmp(&line[1], "o")) + break; + continue; + case 'm': + case 'M': + newval = mod; + if (!line[1]) + break; + continue; + case 'y': + case 'Y': + newval = yes; + if (!line[1] || !strcmp(&line[1], "es")) + break; + continue; + case 0: + newval = oldval; + break; + case '?': + goto help; + default: + continue; + } + if (sym_set_tristate_value(sym, newval)) + return 0; +help: + help = nohelp_text; + if (sym->help) + help = sym->help; + printf("\n%s\n", help); + } +} + +static int conf_choice(struct menu *menu) +{ + struct symbol *sym, *def_sym; + struct menu *child; + int type; + bool is_new; + + sym = menu->sym; + type = sym_get_type(sym); + is_new = !sym_has_value(sym); + if (sym_is_changable(sym)) { + conf_sym(menu); + sym_calc_value(sym); + switch (sym_get_tristate_value(sym)) { + case no: + return 1; + case mod: + return 0; + case yes: + break; + } + } else { + switch (sym_get_tristate_value(sym)) { + case no: + return 1; + case mod: + printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu)); + return 0; + case yes: + break; + } + } + + while (1) { + int cnt, def; + + printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu)); + def_sym = sym_get_choice_value(sym); + cnt = def = 0; + line[0] = 0; + for (child = menu->list; child; child = child->next) { + if (!menu_is_visible(child)) + continue; + if (!child->sym) { + printf("%*c %s\n", indent, '*', menu_get_prompt(child)); + continue; + } + cnt++; + if (child->sym == def_sym) { + def = cnt; + printf("%*c", indent, '>'); + } else + printf("%*c", indent, ' '); + printf(" %d. %s", cnt, menu_get_prompt(child)); + if (child->sym->name) + printf(" (%s)", child->sym->name); + if (!sym_has_value(child->sym)) + printf(" (NEW)"); + printf("\n"); + } + printf("%*schoice", indent - 1, ""); + if (cnt == 1) { + printf("[1]: 1\n"); + goto conf_childs; + } + printf("[1-%d", cnt); + if (sym->help) + printf("?"); + printf("]: "); + switch (input_mode) { + case ask_new: + case ask_silent: + if (!is_new) { + cnt = def; + printf("%d\n", cnt); + break; + } + check_stdin(); + case ask_all: + fflush(stdout); + fgets(line, 128, stdin); + strip(line); + if (line[0] == '?') { + printf("\n%s\n", menu->sym->help ? + menu->sym->help : nohelp_text); + continue; + } + if (!line[0]) + cnt = def; + else if (isdigit(line[0])) + cnt = atoi(line); + else + continue; + break; + case set_random: + def = (random() % cnt) + 1; + case set_default: + case set_yes: + case set_mod: + case set_no: + cnt = def; + printf("%d\n", cnt); + break; + } + + conf_childs: + for (child = menu->list; child; child = child->next) { + if (!child->sym || !menu_is_visible(child)) + continue; + if (!--cnt) + break; + } + if (!child) + continue; + if (line[strlen(line) - 1] == '?') { + printf("\n%s\n", child->sym->help ? + child->sym->help : nohelp_text); + continue; + } + sym_set_choice_value(sym, child->sym); + if (child->list) { + indent += 2; + conf(child->list); + indent -= 2; + } + return 1; + } +} + +static void conf(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + + if (!menu_is_visible(menu)) + return; + + sym = menu->sym; + prop = menu->prompt; + if (prop) { + const char *prompt; + + switch (prop->type) { + case P_MENU: + if (input_mode == ask_silent && rootEntry != menu) { + check_conf(menu); + return; + } + case P_COMMENT: + prompt = menu_get_prompt(menu); + if (prompt) + printf("%*c\n%*c %s\n%*c\n", + indent, '*', + indent, '*', prompt, + indent, '*'); + default: + ; + } + } + + if (!sym) + goto conf_childs; + + if (sym_is_choice(sym)) { + conf_choice(menu); + if (sym->curr.tri != mod) + return; + goto conf_childs; + } + + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + conf_string(menu); + break; + default: + conf_sym(menu); + break; + } + +conf_childs: + if (sym) + indent += 2; + for (child = menu->list; child; child = child->next) + conf(child); + if (sym) + indent -= 2; +} + +static void check_conf(struct menu *menu) +{ + struct symbol *sym; + struct menu *child; + + if (!menu_is_visible(menu)) + return; + + sym = menu->sym; + if (sym && !sym_has_value(sym)) { + if (sym_is_changable(sym) || + (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)) { + if (!conf_cnt++) + printf(_("*\n* Restart config...\n*\n")); + rootEntry = menu_get_parent_menu(menu); + conf(rootEntry); + } + } + + for (child = menu->list; child; child = child->next) + check_conf(child); +} + +int main(int ac, char **av) +{ + int i = 1; + const char *name; + struct stat tmpstat; + + if (ac > i && av[i][0] == '-') { + switch (av[i++][1]) { + case 'o': + input_mode = ask_new; + break; + case 's': + input_mode = ask_silent; + valid_stdin = isatty(0) && isatty(1) && isatty(2); + break; + case 'd': + input_mode = set_default; + break; + case 'D': + input_mode = set_default; + defconfig_file = av[i++]; + if (!defconfig_file) { + printf(_("%s: No default config file specified\n"), + av[0]); + exit(1); + } + break; + case 'n': + input_mode = set_no; + break; + case 'm': + input_mode = set_mod; + break; + case 'y': + input_mode = set_yes; + break; + case 'r': + input_mode = set_random; + srandom(time(NULL)); + break; + case 'h': + case '?': + fprintf(stderr, "See README for usage info\n"); + exit(0); + } + } + name = av[i]; + if (!name) { + printf(_("%s: Kconfig file missing\n"), av[0]); + } + conf_parse(name); + //zconfdump(stdout); + switch (input_mode) { + case set_default: + if (!defconfig_file) + defconfig_file = conf_get_default_confname(); + if (conf_read(defconfig_file)) { + printf("***\n" + "*** Can't find default configuration \"%s\"!\n" + "***\n", defconfig_file); + exit(1); + } + break; + case ask_silent: + if (stat(".config", &tmpstat)) { + printf(_("***\n" + "*** You have not yet configured your kernel!\n" + "***\n" + "*** Please run some configurator (e.g. \"make oldconfig\" or\n" + "*** \"make menuconfig\" or \"make xconfig\").\n" + "***\n")); + exit(1); + } + case ask_all: + case ask_new: + conf_read(NULL); + break; + case set_no: + case set_mod: + case set_yes: + case set_random: + name = getenv("KCONFIG_ALLCONFIG"); + if (name && !stat(name, &tmpstat)) { + conf_read_simple(name); + break; + } + switch (input_mode) { + case set_no: name = "allno.config"; break; + case set_mod: name = "allmod.config"; break; + case set_yes: name = "allyes.config"; break; + case set_random: name = "allrandom.config"; break; + default: break; + } + if (!stat(name, &tmpstat)) + conf_read_simple(name); + else if (!stat("all.config", &tmpstat)) + conf_read_simple("all.config"); + break; + default: + break; + } + + if (input_mode != ask_silent) { + rootEntry = &rootmenu; + conf(&rootmenu); + if (input_mode == ask_all) { + input_mode = ask_silent; + valid_stdin = 1; + } + } + do { + conf_cnt = 0; + check_conf(&rootmenu); + } while (conf_cnt); + if (conf_write(NULL)) { + fprintf(stderr, _("\n*** Error during writing of the busybox configuration.\n\n")); + return 1; + } + return 0; +} diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c new file mode 100644 index 000000000..d38bbba93 --- /dev/null +++ b/scripts/kconfig/confdata.c @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define LKC_DIRECT_LINK +#include "lkc.h" + +static void conf_warning(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +static const char *conf_filename; +static int conf_lineno, conf_warnings, conf_unsaved; + +const char conf_def_filename[] = ".config"; + +const char conf_defname[] = "scripts/defconfig"; + +const char *conf_confnames[] = { + ".config", + "/lib/modules/$UNAME_RELEASE/.config", + "/etc/kernel-config", + "/boot/config-$UNAME_RELEASE", + conf_defname, + NULL, +}; + +static void conf_warning(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", conf_filename, conf_lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + conf_warnings++; +} + +static char *conf_expand_value(const char *in) +{ + struct symbol *sym; + const char *src; + static char res_value[SYMBOL_MAXLENGTH]; + char *dst, name[SYMBOL_MAXLENGTH]; + + res_value[0] = 0; + dst = name; + while ((src = strchr(in, '$'))) { + strncat(res_value, in, src - in); + src++; + dst = name; + while (isalnum(*src) || *src == '_') + *dst++ = *src++; + *dst = 0; + sym = sym_lookup(name, 0); + sym_calc_value(sym); + strcat(res_value, sym_get_string_value(sym)); + in = src; + } + strcat(res_value, in); + + return res_value; +} + +char *conf_get_default_confname(void) +{ + struct stat buf; + static char fullname[PATH_MAX+1]; + char *env, *name; + + name = conf_expand_value(conf_defname); + env = getenv(SRCTREE); + if (env) { + sprintf(fullname, "%s/%s", env, name); + if (!stat(fullname, &buf)) + return fullname; + } + return name; +} + +int conf_read_simple(const char *name) +{ + FILE *in = NULL; + char line[1024]; + char *p, *p2; + struct symbol *sym; + int i; + + if (name) { + in = zconf_fopen(name); + } else { + const char **names = conf_confnames; + while ((name = *names++)) { + name = conf_expand_value(name); + in = zconf_fopen(name); + if (in) { + printf(_("#\n" + "# using defaults found in %s\n" + "#\n"), name); + break; + } + } + } + if (!in) + return 1; + + conf_filename = name; + conf_lineno = 0; + conf_warnings = 0; + conf_unsaved = 0; + + for_all_symbols(i, sym) { + sym->flags |= SYMBOL_NEW | SYMBOL_CHANGED; + if (sym_is_choice(sym)) + sym->flags &= ~SYMBOL_NEW; + sym->flags &= ~SYMBOL_VALID; + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + if (sym->user.val) + free(sym->user.val); + default: + sym->user.val = NULL; + sym->user.tri = no; + } + } + + while (fgets(line, sizeof(line), in)) { + conf_lineno++; + sym = NULL; + switch (line[0]) { + case '#': + if (memcmp(line + 2, "CONFIG_", 7)) + continue; + p = strchr(line + 9, ' '); + if (!p) + continue; + *p++ = 0; + if (strncmp(p, "is not set", 10)) + continue; + sym = sym_find(line + 9); + if (!sym) { + conf_warning("trying to assign nonexistent symbol %s", line + 9); + break; + } else if (!(sym->flags & SYMBOL_NEW)) { + conf_warning("trying to reassign symbol %s", sym->name); + break; + } + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + sym->user.tri = no; + sym->flags &= ~SYMBOL_NEW; + break; + default: + ; + } + break; + case 'C': + if (memcmp(line, "CONFIG_", 7)) { + conf_warning("unexpected data"); + continue; + } + p = strchr(line + 7, '='); + if (!p) + continue; + *p++ = 0; + p2 = strchr(p, '\n'); + if (p2) + *p2 = 0; + sym = sym_find(line + 7); + if (!sym) { + conf_warning("trying to assign nonexistent symbol %s", line + 7); + break; + } else if (!(sym->flags & SYMBOL_NEW)) { + conf_warning("trying to reassign symbol %s", sym->name); + break; + } + switch (sym->type) { + case S_TRISTATE: + if (p[0] == 'm') { + sym->user.tri = mod; + sym->flags &= ~SYMBOL_NEW; + break; + } + case S_BOOLEAN: + if (p[0] == 'y') { + sym->user.tri = yes; + sym->flags &= ~SYMBOL_NEW; + break; + } + if (p[0] == 'n') { + sym->user.tri = no; + sym->flags &= ~SYMBOL_NEW; + break; + } + conf_warning("symbol value '%s' invalid for %s", p, sym->name); + break; + case S_STRING: + if (*p++ != '"') + break; + for (p2 = p; (p2 = strpbrk(p2, "\"\\")); p2++) { + if (*p2 == '"') { + *p2 = 0; + break; + } + memmove(p2, p2 + 1, strlen(p2)); + } + if (!p2) { + conf_warning("invalid string found"); + continue; + } + case S_INT: + case S_HEX: + if (sym_string_valid(sym, p)) { + sym->user.val = strdup(p); + sym->flags &= ~SYMBOL_NEW; + } else { + conf_warning("symbol value '%s' invalid for %s", p, sym->name); + continue; + } + break; + default: + ; + } + break; + case '\n': + break; + default: + conf_warning("unexpected data"); + continue; + } + if (sym && sym_is_choice_value(sym)) { + struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym)); + switch (sym->user.tri) { + case no: + break; + case mod: + if (cs->user.tri == yes) { + conf_warning("%s creates inconsistent choice state", sym->name); + cs->flags |= SYMBOL_NEW; + } + break; + case yes: + if (cs->user.tri != no) { + conf_warning("%s creates inconsistent choice state", sym->name); + cs->flags |= SYMBOL_NEW; + } else + cs->user.val = sym; + break; + } + cs->user.tri = E_OR(cs->user.tri, sym->user.tri); + } + } + fclose(in); + + if (modules_sym) + sym_calc_value(modules_sym); + return 0; +} + +int conf_read(const char *name) +{ + struct symbol *sym; + struct property *prop; + struct expr *e; + int i; + + if (conf_read_simple(name)) + return 1; + + for_all_symbols(i, sym) { + sym_calc_value(sym); + if (sym_is_choice(sym) || (sym->flags & SYMBOL_AUTO)) + goto sym_ok; + if (sym_has_value(sym) && (sym->flags & SYMBOL_WRITE)) { + /* check that calculated value agrees with saved value */ + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym->user.tri != sym_get_tristate_value(sym)) + break; + if (!sym_is_choice(sym)) + goto sym_ok; + default: + if (!strcmp(sym->curr.val, sym->user.val)) + goto sym_ok; + break; + } + } else if (!sym_has_value(sym) && !(sym->flags & SYMBOL_WRITE)) + /* no previous value and not saved */ + goto sym_ok; + conf_unsaved++; + /* maybe print value in verbose mode... */ + sym_ok: + if (sym_has_value(sym) && !sym_is_choice_value(sym)) { + if (sym->visible == no) + sym->flags |= SYMBOL_NEW; + switch (sym->type) { + case S_STRING: + case S_INT: + case S_HEX: + if (!sym_string_within_range(sym, sym->user.val)) { + sym->flags |= SYMBOL_NEW; + sym->flags &= ~SYMBOL_VALID; + } + default: + break; + } + } + if (!sym_is_choice(sym)) + continue; + prop = sym_get_choice_prop(sym); + for (e = prop->expr; e; e = e->left.expr) + if (e->right.sym->visible != no) + sym->flags |= e->right.sym->flags & SYMBOL_NEW; + } + + sym_change_count = conf_warnings || conf_unsaved; + + return 0; +} + +int conf_write(const char *name) +{ + FILE *out, *out_h; + struct symbol *sym; + struct menu *menu; + const char *basename; + char dirname[128], tmpname[128], newname[128]; + int type, l; + const char *str; + time_t now; + int use_timestamp = 1; + char *env; + + dirname[0] = 0; + if (name && name[0]) { + struct stat st; + char *slash; + + if (!stat(name, &st) && S_ISDIR(st.st_mode)) { + strcpy(dirname, name); + strcat(dirname, "/"); + basename = conf_def_filename; + } else if ((slash = strrchr(name, '/'))) { + int size = slash - name + 1; + memcpy(dirname, name, size); + dirname[size] = 0; + if (slash[1]) + basename = slash + 1; + else + basename = conf_def_filename; + } else + basename = name; + } else + basename = conf_def_filename; + + sprintf(newname, "%s.tmpconfig.%d", dirname, (int)getpid()); + out = fopen(newname, "w"); + if (!out) + return 1; + out_h = NULL; + if (!name) { + out_h = fopen(".tmpconfig.h", "w"); + if (!out_h) + return 1; + file_write_dep(NULL); + } + sym = sym_lookup("KERNELVERSION", 0); + sym_calc_value(sym); + time(&now); + env = getenv("KCONFIG_NOTIMESTAMP"); + if (env && *env) + use_timestamp = 0; + + fprintf(out, _("#\n" + "# Automatically generated make config: don't edit\n" + "# Linux kernel version: %s\n" + "%s%s" + "#\n"), + sym_get_string_value(sym), + use_timestamp ? "# " : "", + use_timestamp ? ctime(&now) : ""); + if (out_h) { + char buf[sizeof("#define AUTOCONF_TIMESTAMP " + "\"YYYY-MM-DD HH:MM:SS some_timezone\"\n")]; + buf[0] = '\0'; + if (use_timestamp) + strftime(buf, sizeof(buf), "#define AUTOCONF_TIMESTAMP " + "\"%Y-%m-%d %H:%M:%S %Z\"\n", localtime(&now)); + fprintf(out_h, "/*\n" + " * Automatically generated C config: don't edit\n" + " * Linux kernel version: %s\n" + " */\n" + "%s" + "#define AUTOCONF_INCLUDED\n", + sym_get_string_value(sym), + buf); + } + if (!sym_change_count) + sym_clear_all_valid(); + + menu = rootmenu.list; + while (menu) { + sym = menu->sym; + if (!sym) { + if (!menu_is_visible(menu)) + goto next; + str = menu_get_prompt(menu); + fprintf(out, "\n" + "#\n" + "# %s\n" + "#\n", str); + if (out_h) + fprintf(out_h, "\n" + "/*\n" + " * %s\n" + " */\n", str); + } else if (!(sym->flags & SYMBOL_CHOICE)) { + sym_calc_value(sym); +/* bbox: we want to all syms + if (!(sym->flags & SYMBOL_WRITE)) + goto next; +*/ + sym->flags &= ~SYMBOL_WRITE; + type = sym->type; + if (type == S_TRISTATE) { + sym_calc_value(modules_sym); + if (modules_sym->curr.tri == no) + type = S_BOOLEAN; + } + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + switch (sym_get_tristate_value(sym)) { + case no: + fprintf(out, "# CONFIG_%s is not set\n", sym->name); + if (out_h) { + fprintf(out_h, "#undef CONFIG_%s\n", sym->name); + /* bbox */ + fprintf(out_h, "#define ENABLE_%s 0\n", sym->name); + fprintf(out_h, "#define USE_%s(...)\n", sym->name); + fprintf(out_h, "#define SKIP_%s(...) __VA_ARGS__\n", sym->name); + } + break; + case mod: + fprintf(out, "CONFIG_%s=m\n", sym->name); + if (out_h) + fprintf(out_h, "#define CONFIG_%s_MODULE 1\n", sym->name); + break; + case yes: + fprintf(out, "CONFIG_%s=y\n", sym->name); + if (out_h) { + fprintf(out_h, "#define CONFIG_%s 1\n", sym->name); + /* bbox */ + fprintf(out_h, "#define ENABLE_%s 1\n", sym->name); + fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name); + fprintf(out_h, "#define SKIP_%s(...)\n", sym->name); + } + break; + } + break; + case S_STRING: + // fix me + str = sym_get_string_value(sym); + fprintf(out, "CONFIG_%s=\"", sym->name); + if (out_h) + fprintf(out_h, "#define CONFIG_%s \"", sym->name); + do { + l = strcspn(str, "\"\\"); + if (l) { + fwrite(str, l, 1, out); + if (out_h) + fwrite(str, l, 1, out_h); + } + str += l; + while (*str == '\\' || *str == '"') { + fprintf(out, "\\%c", *str); + if (out_h) + fprintf(out_h, "\\%c", *str); + str++; + } + } while (*str); + fputs("\"\n", out); + if (out_h) { + fputs("\"\n", out_h); + /* bbox */ + fprintf(out_h, "#define ENABLE_%s 1\n", sym->name); + fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name); + fprintf(out_h, "#define SKIP_%s(...)\n", sym->name); + } + break; + case S_HEX: + str = sym_get_string_value(sym); + if (str[0] != '0' || (str[1] != 'x' && str[1] != 'X')) { + fprintf(out, "CONFIG_%s=%s\n", sym->name, str); + if (out_h) { + fprintf(out_h, "#define CONFIG_%s 0x%s\n", sym->name, str); + /* bbox */ + fprintf(out_h, "#define ENABLE_%s 1\n", sym->name); + fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name); + fprintf(out_h, "#define SKIP_%s(...)\n", sym->name); + } + break; + } + case S_INT: + str = sym_get_string_value(sym); + fprintf(out, "CONFIG_%s=%s\n", sym->name, str); + if (out_h) { + fprintf(out_h, "#define CONFIG_%s %s\n", sym->name, str); + /* bbox */ + fprintf(out_h, "#define ENABLE_%s 1\n", sym->name); + fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name); + fprintf(out_h, "#define SKIP_%s(...)\n", sym->name); + } + break; + } + } + + next: + if (menu->list) { + menu = menu->list; + continue; + } + if (menu->next) + menu = menu->next; + else while ((menu = menu->parent)) { + if (menu->next) { + menu = menu->next; + break; + } + } + } + fclose(out); + if (out_h) { + fclose(out_h); + rename(".tmpconfig.h", "include/autoconf.h"); + } + if (!name || basename != conf_def_filename) { + if (!name) + name = conf_def_filename; + sprintf(tmpname, "%s.old", name); + rename(name, tmpname); + } + sprintf(tmpname, "%s%s", dirname, basename); + if (rename(newname, tmpname)) + return 1; + + sym_change_count = 0; + + return 0; +} diff --git a/scripts/kconfig/expr.c b/scripts/kconfig/expr.c new file mode 100644 index 000000000..30e4f9d69 --- /dev/null +++ b/scripts/kconfig/expr.c @@ -0,0 +1,1099 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include + +#define LKC_DIRECT_LINK +#include "lkc.h" + +#define DEBUG_EXPR 0 + +struct expr *expr_alloc_symbol(struct symbol *sym) +{ + struct expr *e = malloc(sizeof(*e)); + memset(e, 0, sizeof(*e)); + e->type = E_SYMBOL; + e->left.sym = sym; + return e; +} + +struct expr *expr_alloc_one(enum expr_type type, struct expr *ce) +{ + struct expr *e = malloc(sizeof(*e)); + memset(e, 0, sizeof(*e)); + e->type = type; + e->left.expr = ce; + return e; +} + +struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2) +{ + struct expr *e = malloc(sizeof(*e)); + memset(e, 0, sizeof(*e)); + e->type = type; + e->left.expr = e1; + e->right.expr = e2; + return e; +} + +struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2) +{ + struct expr *e = malloc(sizeof(*e)); + memset(e, 0, sizeof(*e)); + e->type = type; + e->left.sym = s1; + e->right.sym = s2; + return e; +} + +struct expr *expr_alloc_and(struct expr *e1, struct expr *e2) +{ + if (!e1) + return e2; + return e2 ? expr_alloc_two(E_AND, e1, e2) : e1; +} + +struct expr *expr_alloc_or(struct expr *e1, struct expr *e2) +{ + if (!e1) + return e2; + return e2 ? expr_alloc_two(E_OR, e1, e2) : e1; +} + +struct expr *expr_copy(struct expr *org) +{ + struct expr *e; + + if (!org) + return NULL; + + e = malloc(sizeof(*org)); + memcpy(e, org, sizeof(*org)); + switch (org->type) { + case E_SYMBOL: + e->left = org->left; + break; + case E_NOT: + e->left.expr = expr_copy(org->left.expr); + break; + case E_EQUAL: + case E_UNEQUAL: + e->left.sym = org->left.sym; + e->right.sym = org->right.sym; + break; + case E_AND: + case E_OR: + case E_CHOICE: + e->left.expr = expr_copy(org->left.expr); + e->right.expr = expr_copy(org->right.expr); + break; + default: + printf("can't copy type %d\n", e->type); + free(e); + e = NULL; + break; + } + + return e; +} + +void expr_free(struct expr *e) +{ + if (!e) + return; + + switch (e->type) { + case E_SYMBOL: + break; + case E_NOT: + expr_free(e->left.expr); + return; + case E_EQUAL: + case E_UNEQUAL: + break; + case E_OR: + case E_AND: + expr_free(e->left.expr); + expr_free(e->right.expr); + break; + default: + printf("how to free type %d?\n", e->type); + break; + } + free(e); +} + +static int trans_count; + +#define e1 (*ep1) +#define e2 (*ep2) + +static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct expr **ep2) +{ + if (e1->type == type) { + __expr_eliminate_eq(type, &e1->left.expr, &e2); + __expr_eliminate_eq(type, &e1->right.expr, &e2); + return; + } + if (e2->type == type) { + __expr_eliminate_eq(type, &e1, &e2->left.expr); + __expr_eliminate_eq(type, &e1, &e2->right.expr); + return; + } + if (e1->type == E_SYMBOL && e2->type == E_SYMBOL && + e1->left.sym == e2->left.sym && (e1->left.sym->flags & (SYMBOL_YES|SYMBOL_NO))) + return; + if (!expr_eq(e1, e2)) + return; + trans_count++; + expr_free(e1); expr_free(e2); + switch (type) { + case E_OR: + e1 = expr_alloc_symbol(&symbol_no); + e2 = expr_alloc_symbol(&symbol_no); + break; + case E_AND: + e1 = expr_alloc_symbol(&symbol_yes); + e2 = expr_alloc_symbol(&symbol_yes); + break; + default: + ; + } +} + +void expr_eliminate_eq(struct expr **ep1, struct expr **ep2) +{ + if (!e1 || !e2) + return; + switch (e1->type) { + case E_OR: + case E_AND: + __expr_eliminate_eq(e1->type, ep1, ep2); + default: + ; + } + if (e1->type != e2->type) switch (e2->type) { + case E_OR: + case E_AND: + __expr_eliminate_eq(e2->type, ep1, ep2); + default: + ; + } + e1 = expr_eliminate_yn(e1); + e2 = expr_eliminate_yn(e2); +} + +#undef e1 +#undef e2 + +int expr_eq(struct expr *e1, struct expr *e2) +{ + int res, old_count; + + if (e1->type != e2->type) + return 0; + switch (e1->type) { + case E_EQUAL: + case E_UNEQUAL: + return e1->left.sym == e2->left.sym && e1->right.sym == e2->right.sym; + case E_SYMBOL: + return e1->left.sym == e2->left.sym; + case E_NOT: + return expr_eq(e1->left.expr, e2->left.expr); + case E_AND: + case E_OR: + e1 = expr_copy(e1); + e2 = expr_copy(e2); + old_count = trans_count; + expr_eliminate_eq(&e1, &e2); + res = (e1->type == E_SYMBOL && e2->type == E_SYMBOL && + e1->left.sym == e2->left.sym); + expr_free(e1); + expr_free(e2); + trans_count = old_count; + return res; + case E_CHOICE: + case E_RANGE: + case E_NONE: + /* panic */; + } + + if (DEBUG_EXPR) { + expr_fprint(e1, stdout); + printf(" = "); + expr_fprint(e2, stdout); + printf(" ?\n"); + } + + return 0; +} + +struct expr *expr_eliminate_yn(struct expr *e) +{ + struct expr *tmp; + + if (e) switch (e->type) { + case E_AND: + e->left.expr = expr_eliminate_yn(e->left.expr); + e->right.expr = expr_eliminate_yn(e->right.expr); + if (e->left.expr->type == E_SYMBOL) { + if (e->left.expr->left.sym == &symbol_no) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + e->right.expr = NULL; + return e; + } else if (e->left.expr->left.sym == &symbol_yes) { + free(e->left.expr); + tmp = e->right.expr; + *e = *(e->right.expr); + free(tmp); + return e; + } + } + if (e->right.expr->type == E_SYMBOL) { + if (e->right.expr->left.sym == &symbol_no) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + e->right.expr = NULL; + return e; + } else if (e->right.expr->left.sym == &symbol_yes) { + free(e->right.expr); + tmp = e->left.expr; + *e = *(e->left.expr); + free(tmp); + return e; + } + } + break; + case E_OR: + e->left.expr = expr_eliminate_yn(e->left.expr); + e->right.expr = expr_eliminate_yn(e->right.expr); + if (e->left.expr->type == E_SYMBOL) { + if (e->left.expr->left.sym == &symbol_no) { + free(e->left.expr); + tmp = e->right.expr; + *e = *(e->right.expr); + free(tmp); + return e; + } else if (e->left.expr->left.sym == &symbol_yes) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + e->right.expr = NULL; + return e; + } + } + if (e->right.expr->type == E_SYMBOL) { + if (e->right.expr->left.sym == &symbol_no) { + free(e->right.expr); + tmp = e->left.expr; + *e = *(e->left.expr); + free(tmp); + return e; + } else if (e->right.expr->left.sym == &symbol_yes) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + e->right.expr = NULL; + return e; + } + } + break; + default: + ; + } + return e; +} + +/* + * bool FOO!=n => FOO + */ +struct expr *expr_trans_bool(struct expr *e) +{ + if (!e) + return NULL; + switch (e->type) { + case E_AND: + case E_OR: + case E_NOT: + e->left.expr = expr_trans_bool(e->left.expr); + e->right.expr = expr_trans_bool(e->right.expr); + break; + case E_UNEQUAL: + // FOO!=n -> FOO + if (e->left.sym->type == S_TRISTATE) { + if (e->right.sym == &symbol_no) { + e->type = E_SYMBOL; + e->right.sym = NULL; + } + } + break; + default: + ; + } + return e; +} + +/* + * e1 || e2 -> ? + */ +struct expr *expr_join_or(struct expr *e1, struct expr *e2) +{ + struct expr *tmp; + struct symbol *sym1, *sym2; + + if (expr_eq(e1, e2)) + return expr_copy(e1); + if (e1->type != E_EQUAL && e1->type != E_UNEQUAL && e1->type != E_SYMBOL && e1->type != E_NOT) + return NULL; + if (e2->type != E_EQUAL && e2->type != E_UNEQUAL && e2->type != E_SYMBOL && e2->type != E_NOT) + return NULL; + if (e1->type == E_NOT) { + tmp = e1->left.expr; + if (tmp->type != E_EQUAL && tmp->type != E_UNEQUAL && tmp->type != E_SYMBOL) + return NULL; + sym1 = tmp->left.sym; + } else + sym1 = e1->left.sym; + if (e2->type == E_NOT) { + if (e2->left.expr->type != E_SYMBOL) + return NULL; + sym2 = e2->left.expr->left.sym; + } else + sym2 = e2->left.sym; + if (sym1 != sym2) + return NULL; + if (sym1->type != S_BOOLEAN && sym1->type != S_TRISTATE) + return NULL; + if (sym1->type == S_TRISTATE) { + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_mod) || + (e1->right.sym == &symbol_mod && e2->right.sym == &symbol_yes))) { + // (a='y') || (a='m') -> (a!='n') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_no); + } + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_yes))) { + // (a='y') || (a='n') -> (a!='m') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_mod); + } + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_mod && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_mod))) { + // (a='m') || (a='n') -> (a!='y') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_yes); + } + } + if (sym1->type == S_BOOLEAN && sym1 == sym2) { + if ((e1->type == E_NOT && e1->left.expr->type == E_SYMBOL && e2->type == E_SYMBOL) || + (e2->type == E_NOT && e2->left.expr->type == E_SYMBOL && e1->type == E_SYMBOL)) + return expr_alloc_symbol(&symbol_yes); + } + + if (DEBUG_EXPR) { + printf("optimize ("); + expr_fprint(e1, stdout); + printf(") || ("); + expr_fprint(e2, stdout); + printf(")?\n"); + } + return NULL; +} + +struct expr *expr_join_and(struct expr *e1, struct expr *e2) +{ + struct expr *tmp; + struct symbol *sym1, *sym2; + + if (expr_eq(e1, e2)) + return expr_copy(e1); + if (e1->type != E_EQUAL && e1->type != E_UNEQUAL && e1->type != E_SYMBOL && e1->type != E_NOT) + return NULL; + if (e2->type != E_EQUAL && e2->type != E_UNEQUAL && e2->type != E_SYMBOL && e2->type != E_NOT) + return NULL; + if (e1->type == E_NOT) { + tmp = e1->left.expr; + if (tmp->type != E_EQUAL && tmp->type != E_UNEQUAL && tmp->type != E_SYMBOL) + return NULL; + sym1 = tmp->left.sym; + } else + sym1 = e1->left.sym; + if (e2->type == E_NOT) { + if (e2->left.expr->type != E_SYMBOL) + return NULL; + sym2 = e2->left.expr->left.sym; + } else + sym2 = e2->left.sym; + if (sym1 != sym2) + return NULL; + if (sym1->type != S_BOOLEAN && sym1->type != S_TRISTATE) + return NULL; + + if ((e1->type == E_SYMBOL && e2->type == E_EQUAL && e2->right.sym == &symbol_yes) || + (e2->type == E_SYMBOL && e1->type == E_EQUAL && e1->right.sym == &symbol_yes)) + // (a) && (a='y') -> (a='y') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if ((e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_no) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_no)) + // (a) && (a!='n') -> (a) + return expr_alloc_symbol(sym1); + + if ((e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_mod) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_mod)) + // (a) && (a!='m') -> (a='y') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if (sym1->type == S_TRISTATE) { + if (e1->type == E_EQUAL && e2->type == E_UNEQUAL) { + // (a='b') && (a!='c') -> 'b'='c' ? 'n' : a='b' + sym2 = e1->right.sym; + if ((e2->right.sym->flags & SYMBOL_CONST) && (sym2->flags & SYMBOL_CONST)) + return sym2 != e2->right.sym ? expr_alloc_comp(E_EQUAL, sym1, sym2) + : expr_alloc_symbol(&symbol_no); + } + if (e1->type == E_UNEQUAL && e2->type == E_EQUAL) { + // (a='b') && (a!='c') -> 'b'='c' ? 'n' : a='b' + sym2 = e2->right.sym; + if ((e1->right.sym->flags & SYMBOL_CONST) && (sym2->flags & SYMBOL_CONST)) + return sym2 != e1->right.sym ? expr_alloc_comp(E_EQUAL, sym1, sym2) + : expr_alloc_symbol(&symbol_no); + } + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_yes))) + // (a!='y') && (a!='n') -> (a='m') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_mod); + + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_mod) || + (e1->right.sym == &symbol_mod && e2->right.sym == &symbol_yes))) + // (a!='y') && (a!='m') -> (a='n') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_no); + + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_mod && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_mod))) + // (a!='m') && (a!='n') -> (a='m') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if ((e1->type == E_SYMBOL && e2->type == E_EQUAL && e2->right.sym == &symbol_mod) || + (e2->type == E_SYMBOL && e1->type == E_EQUAL && e1->right.sym == &symbol_mod) || + (e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_yes) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_yes)) + return NULL; + } + + if (DEBUG_EXPR) { + printf("optimize ("); + expr_fprint(e1, stdout); + printf(") && ("); + expr_fprint(e2, stdout); + printf(")?\n"); + } + return NULL; +} + +static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct expr **ep2) +{ +#define e1 (*ep1) +#define e2 (*ep2) + struct expr *tmp; + + if (e1->type == type) { + expr_eliminate_dups1(type, &e1->left.expr, &e2); + expr_eliminate_dups1(type, &e1->right.expr, &e2); + return; + } + if (e2->type == type) { + expr_eliminate_dups1(type, &e1, &e2->left.expr); + expr_eliminate_dups1(type, &e1, &e2->right.expr); + return; + } + if (e1 == e2) + return; + + switch (e1->type) { + case E_OR: case E_AND: + expr_eliminate_dups1(e1->type, &e1, &e1); + default: + ; + } + + switch (type) { + case E_OR: + tmp = expr_join_or(e1, e2); + if (tmp) { + expr_free(e1); expr_free(e2); + e1 = expr_alloc_symbol(&symbol_no); + e2 = tmp; + trans_count++; + } + break; + case E_AND: + tmp = expr_join_and(e1, e2); + if (tmp) { + expr_free(e1); expr_free(e2); + e1 = expr_alloc_symbol(&symbol_yes); + e2 = tmp; + trans_count++; + } + break; + default: + ; + } +#undef e1 +#undef e2 +} + +static void expr_eliminate_dups2(enum expr_type type, struct expr **ep1, struct expr **ep2) +{ +#define e1 (*ep1) +#define e2 (*ep2) + struct expr *tmp, *tmp1, *tmp2; + + if (e1->type == type) { + expr_eliminate_dups2(type, &e1->left.expr, &e2); + expr_eliminate_dups2(type, &e1->right.expr, &e2); + return; + } + if (e2->type == type) { + expr_eliminate_dups2(type, &e1, &e2->left.expr); + expr_eliminate_dups2(type, &e1, &e2->right.expr); + } + if (e1 == e2) + return; + + switch (e1->type) { + case E_OR: + expr_eliminate_dups2(e1->type, &e1, &e1); + // (FOO || BAR) && (!FOO && !BAR) -> n + tmp1 = expr_transform(expr_alloc_one(E_NOT, expr_copy(e1))); + tmp2 = expr_copy(e2); + tmp = expr_extract_eq_and(&tmp1, &tmp2); + if (expr_is_yes(tmp1)) { + expr_free(e1); + e1 = expr_alloc_symbol(&symbol_no); + trans_count++; + } + expr_free(tmp2); + expr_free(tmp1); + expr_free(tmp); + break; + case E_AND: + expr_eliminate_dups2(e1->type, &e1, &e1); + // (FOO && BAR) || (!FOO || !BAR) -> y + tmp1 = expr_transform(expr_alloc_one(E_NOT, expr_copy(e1))); + tmp2 = expr_copy(e2); + tmp = expr_extract_eq_or(&tmp1, &tmp2); + if (expr_is_no(tmp1)) { + expr_free(e1); + e1 = expr_alloc_symbol(&symbol_yes); + trans_count++; + } + expr_free(tmp2); + expr_free(tmp1); + expr_free(tmp); + break; + default: + ; + } +#undef e1 +#undef e2 +} + +struct expr *expr_eliminate_dups(struct expr *e) +{ + int oldcount; + if (!e) + return e; + + oldcount = trans_count; + while (1) { + trans_count = 0; + switch (e->type) { + case E_OR: case E_AND: + expr_eliminate_dups1(e->type, &e, &e); + expr_eliminate_dups2(e->type, &e, &e); + default: + ; + } + if (!trans_count) + break; + e = expr_eliminate_yn(e); + } + trans_count = oldcount; + return e; +} + +struct expr *expr_transform(struct expr *e) +{ + struct expr *tmp; + + if (!e) + return NULL; + switch (e->type) { + case E_EQUAL: + case E_UNEQUAL: + case E_SYMBOL: + case E_CHOICE: + break; + default: + e->left.expr = expr_transform(e->left.expr); + e->right.expr = expr_transform(e->right.expr); + } + + switch (e->type) { + case E_EQUAL: + if (e->left.sym->type != S_BOOLEAN) + break; + if (e->right.sym == &symbol_no) { + e->type = E_NOT; + e->left.expr = expr_alloc_symbol(e->left.sym); + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_mod) { + printf("boolean symbol %s tested for 'm'? test forced to 'n'\n", e->left.sym->name); + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_yes) { + e->type = E_SYMBOL; + e->right.sym = NULL; + break; + } + break; + case E_UNEQUAL: + if (e->left.sym->type != S_BOOLEAN) + break; + if (e->right.sym == &symbol_no) { + e->type = E_SYMBOL; + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_mod) { + printf("boolean symbol %s tested for 'm'? test forced to 'y'\n", e->left.sym->name); + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_yes) { + e->type = E_NOT; + e->left.expr = expr_alloc_symbol(e->left.sym); + e->right.sym = NULL; + break; + } + break; + case E_NOT: + switch (e->left.expr->type) { + case E_NOT: + // !!a -> a + tmp = e->left.expr->left.expr; + free(e->left.expr); + free(e); + e = tmp; + e = expr_transform(e); + break; + case E_EQUAL: + case E_UNEQUAL: + // !a='x' -> a!='x' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = e->type == E_EQUAL ? E_UNEQUAL : E_EQUAL; + break; + case E_OR: + // !(a || b) -> !a && !b + tmp = e->left.expr; + e->type = E_AND; + e->right.expr = expr_alloc_one(E_NOT, tmp->right.expr); + tmp->type = E_NOT; + tmp->right.expr = NULL; + e = expr_transform(e); + break; + case E_AND: + // !(a && b) -> !a || !b + tmp = e->left.expr; + e->type = E_OR; + e->right.expr = expr_alloc_one(E_NOT, tmp->right.expr); + tmp->type = E_NOT; + tmp->right.expr = NULL; + e = expr_transform(e); + break; + case E_SYMBOL: + if (e->left.expr->left.sym == &symbol_yes) { + // !'y' -> 'n' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + break; + } + if (e->left.expr->left.sym == &symbol_mod) { + // !'m' -> 'm' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = E_SYMBOL; + e->left.sym = &symbol_mod; + break; + } + if (e->left.expr->left.sym == &symbol_no) { + // !'n' -> 'y' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + break; + } + break; + default: + ; + } + break; + default: + ; + } + return e; +} + +int expr_contains_symbol(struct expr *dep, struct symbol *sym) +{ + if (!dep) + return 0; + + switch (dep->type) { + case E_AND: + case E_OR: + return expr_contains_symbol(dep->left.expr, sym) || + expr_contains_symbol(dep->right.expr, sym); + case E_SYMBOL: + return dep->left.sym == sym; + case E_EQUAL: + case E_UNEQUAL: + return dep->left.sym == sym || + dep->right.sym == sym; + case E_NOT: + return expr_contains_symbol(dep->left.expr, sym); + default: + ; + } + return 0; +} + +bool expr_depends_symbol(struct expr *dep, struct symbol *sym) +{ + if (!dep) + return false; + + switch (dep->type) { + case E_AND: + return expr_depends_symbol(dep->left.expr, sym) || + expr_depends_symbol(dep->right.expr, sym); + case E_SYMBOL: + return dep->left.sym == sym; + case E_EQUAL: + if (dep->left.sym == sym) { + if (dep->right.sym == &symbol_yes || dep->right.sym == &symbol_mod) + return true; + } + break; + case E_UNEQUAL: + if (dep->left.sym == sym) { + if (dep->right.sym == &symbol_no) + return true; + } + break; + default: + ; + } + return false; +} + +struct expr *expr_extract_eq_and(struct expr **ep1, struct expr **ep2) +{ + struct expr *tmp = NULL; + expr_extract_eq(E_AND, &tmp, ep1, ep2); + if (tmp) { + *ep1 = expr_eliminate_yn(*ep1); + *ep2 = expr_eliminate_yn(*ep2); + } + return tmp; +} + +struct expr *expr_extract_eq_or(struct expr **ep1, struct expr **ep2) +{ + struct expr *tmp = NULL; + expr_extract_eq(E_OR, &tmp, ep1, ep2); + if (tmp) { + *ep1 = expr_eliminate_yn(*ep1); + *ep2 = expr_eliminate_yn(*ep2); + } + return tmp; +} + +void expr_extract_eq(enum expr_type type, struct expr **ep, struct expr **ep1, struct expr **ep2) +{ +#define e1 (*ep1) +#define e2 (*ep2) + if (e1->type == type) { + expr_extract_eq(type, ep, &e1->left.expr, &e2); + expr_extract_eq(type, ep, &e1->right.expr, &e2); + return; + } + if (e2->type == type) { + expr_extract_eq(type, ep, ep1, &e2->left.expr); + expr_extract_eq(type, ep, ep1, &e2->right.expr); + return; + } + if (expr_eq(e1, e2)) { + *ep = *ep ? expr_alloc_two(type, *ep, e1) : e1; + expr_free(e2); + if (type == E_AND) { + e1 = expr_alloc_symbol(&symbol_yes); + e2 = expr_alloc_symbol(&symbol_yes); + } else if (type == E_OR) { + e1 = expr_alloc_symbol(&symbol_no); + e2 = expr_alloc_symbol(&symbol_no); + } + } +#undef e1 +#undef e2 +} + +struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym) +{ + struct expr *e1, *e2; + + if (!e) { + e = expr_alloc_symbol(sym); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + } + switch (e->type) { + case E_AND: + e1 = expr_trans_compare(e->left.expr, E_EQUAL, sym); + e2 = expr_trans_compare(e->right.expr, E_EQUAL, sym); + if (sym == &symbol_yes) + e = expr_alloc_two(E_AND, e1, e2); + if (sym == &symbol_no) + e = expr_alloc_two(E_OR, e1, e2); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + case E_OR: + e1 = expr_trans_compare(e->left.expr, E_EQUAL, sym); + e2 = expr_trans_compare(e->right.expr, E_EQUAL, sym); + if (sym == &symbol_yes) + e = expr_alloc_two(E_OR, e1, e2); + if (sym == &symbol_no) + e = expr_alloc_two(E_AND, e1, e2); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + case E_NOT: + return expr_trans_compare(e->left.expr, type == E_EQUAL ? E_UNEQUAL : E_EQUAL, sym); + case E_UNEQUAL: + case E_EQUAL: + if (type == E_EQUAL) { + if (sym == &symbol_yes) + return expr_copy(e); + if (sym == &symbol_mod) + return expr_alloc_symbol(&symbol_no); + if (sym == &symbol_no) + return expr_alloc_one(E_NOT, expr_copy(e)); + } else { + if (sym == &symbol_yes) + return expr_alloc_one(E_NOT, expr_copy(e)); + if (sym == &symbol_mod) + return expr_alloc_symbol(&symbol_yes); + if (sym == &symbol_no) + return expr_copy(e); + } + break; + case E_SYMBOL: + return expr_alloc_comp(type, e->left.sym, sym); + case E_CHOICE: + case E_RANGE: + case E_NONE: + /* panic */; + } + return NULL; +} + +tristate expr_calc_value(struct expr *e) +{ + tristate val1, val2; + const char *str1, *str2; + + if (!e) + return yes; + + switch (e->type) { + case E_SYMBOL: + sym_calc_value(e->left.sym); + return e->left.sym->curr.tri; + case E_AND: + val1 = expr_calc_value(e->left.expr); + val2 = expr_calc_value(e->right.expr); + return E_AND(val1, val2); + case E_OR: + val1 = expr_calc_value(e->left.expr); + val2 = expr_calc_value(e->right.expr); + return E_OR(val1, val2); + case E_NOT: + val1 = expr_calc_value(e->left.expr); + return E_NOT(val1); + case E_EQUAL: + sym_calc_value(e->left.sym); + sym_calc_value(e->right.sym); + str1 = sym_get_string_value(e->left.sym); + str2 = sym_get_string_value(e->right.sym); + return !strcmp(str1, str2) ? yes : no; + case E_UNEQUAL: + sym_calc_value(e->left.sym); + sym_calc_value(e->right.sym); + str1 = sym_get_string_value(e->left.sym); + str2 = sym_get_string_value(e->right.sym); + return !strcmp(str1, str2) ? no : yes; + default: + printf("expr_calc_value: %d?\n", e->type); + return no; + } +} + +int expr_compare_type(enum expr_type t1, enum expr_type t2) +{ +#if 0 + return 1; +#else + if (t1 == t2) + return 0; + switch (t1) { + case E_EQUAL: + case E_UNEQUAL: + if (t2 == E_NOT) + return 1; + case E_NOT: + if (t2 == E_AND) + return 1; + case E_AND: + if (t2 == E_OR) + return 1; + case E_OR: + if (t2 == E_CHOICE) + return 1; + case E_CHOICE: + if (t2 == 0) + return 1; + default: + return -1; + } + printf("[%dgt%d?]", t1, t2); + return 0; +#endif +} + +void expr_print(struct expr *e, void (*fn)(void *, const char *), void *data, int prevtoken) +{ + if (!e) { + fn(data, "y"); + return; + } + + if (expr_compare_type(prevtoken, e->type) > 0) + fn(data, "("); + switch (e->type) { + case E_SYMBOL: + if (e->left.sym->name) + fn(data, e->left.sym->name); + else + fn(data, ""); + break; + case E_NOT: + fn(data, "!"); + expr_print(e->left.expr, fn, data, E_NOT); + break; + case E_EQUAL: + fn(data, e->left.sym->name); + fn(data, "="); + fn(data, e->right.sym->name); + break; + case E_UNEQUAL: + fn(data, e->left.sym->name); + fn(data, "!="); + fn(data, e->right.sym->name); + break; + case E_OR: + expr_print(e->left.expr, fn, data, E_OR); + fn(data, " || "); + expr_print(e->right.expr, fn, data, E_OR); + break; + case E_AND: + expr_print(e->left.expr, fn, data, E_AND); + fn(data, " && "); + expr_print(e->right.expr, fn, data, E_AND); + break; + case E_CHOICE: + fn(data, e->right.sym->name); + if (e->left.expr) { + fn(data, " ^ "); + expr_print(e->left.expr, fn, data, E_CHOICE); + } + break; + case E_RANGE: + fn(data, "["); + fn(data, e->left.sym->name); + fn(data, " "); + fn(data, e->right.sym->name); + fn(data, "]"); + break; + default: + { + char buf[32]; + sprintf(buf, "", e->type); + fn(data, buf); + break; + } + } + if (expr_compare_type(prevtoken, e->type) > 0) + fn(data, ")"); +} + +static void expr_print_file_helper(void *data, const char *str) +{ + fwrite(str, strlen(str), 1, data); +} + +void expr_fprint(struct expr *e, FILE *out) +{ + expr_print(e, expr_print_file_helper, out, E_NONE); +} + +static void expr_print_gstr_helper(void *data, const char *str) +{ + str_append((struct gstr*)data, str); +} + +void expr_gstr_print(struct expr *e, struct gstr *gs) +{ + expr_print(e, expr_print_gstr_helper, gs, E_NONE); +} diff --git a/scripts/kconfig/expr.h b/scripts/kconfig/expr.h new file mode 100644 index 000000000..1b36ef18c --- /dev/null +++ b/scripts/kconfig/expr.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef EXPR_H +#define EXPR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#ifndef __cplusplus +#include +#endif + +struct file { + struct file *next; + struct file *parent; + char *name; + int lineno; + int flags; +}; + +#define FILE_BUSY 0x0001 +#define FILE_SCANNED 0x0002 +#define FILE_PRINTED 0x0004 + +typedef enum tristate { + no, mod, yes +} tristate; + +enum expr_type { + E_NONE, E_OR, E_AND, E_NOT, E_EQUAL, E_UNEQUAL, E_CHOICE, E_SYMBOL, E_RANGE +}; + +union expr_data { + struct expr *expr; + struct symbol *sym; +}; + +struct expr { + enum expr_type type; + union expr_data left, right; +}; + +#define E_OR(dep1, dep2) (((dep1)>(dep2))?(dep1):(dep2)) +#define E_AND(dep1, dep2) (((dep1)<(dep2))?(dep1):(dep2)) +#define E_NOT(dep) (2-(dep)) + +struct expr_value { + struct expr *expr; + tristate tri; +}; + +struct symbol_value { + void *val; + tristate tri; +}; + +enum symbol_type { + S_UNKNOWN, S_BOOLEAN, S_TRISTATE, S_INT, S_HEX, S_STRING, S_OTHER +}; + +struct symbol { + struct symbol *next; + char *name; + char *help; + enum symbol_type type; + struct symbol_value curr, user; + tristate visible; + int flags; + struct property *prop; + struct expr *dep, *dep2; + struct expr_value rev_dep; +}; + +#define for_all_symbols(i, sym) for (i = 0; i < 257; i++) for (sym = symbol_hash[i]; sym; sym = sym->next) if (sym->type != S_OTHER) + +#define SYMBOL_YES 0x0001 +#define SYMBOL_MOD 0x0002 +#define SYMBOL_NO 0x0004 +#define SYMBOL_CONST 0x0007 +#define SYMBOL_CHECK 0x0008 +#define SYMBOL_CHOICE 0x0010 +#define SYMBOL_CHOICEVAL 0x0020 +#define SYMBOL_PRINTED 0x0040 +#define SYMBOL_VALID 0x0080 +#define SYMBOL_OPTIONAL 0x0100 +#define SYMBOL_WRITE 0x0200 +#define SYMBOL_CHANGED 0x0400 +#define SYMBOL_NEW 0x0800 +#define SYMBOL_AUTO 0x1000 +#define SYMBOL_CHECKED 0x2000 +#define SYMBOL_WARNED 0x8000 + +#define SYMBOL_MAXLENGTH 256 +#define SYMBOL_HASHSIZE 257 +#define SYMBOL_HASHMASK 0xff + +enum prop_type { + P_UNKNOWN, P_PROMPT, P_COMMENT, P_MENU, P_DEFAULT, P_CHOICE, P_SELECT, P_RANGE +}; + +struct property { + struct property *next; + struct symbol *sym; + enum prop_type type; + const char *text; + struct expr_value visible; + struct expr *expr; + struct menu *menu; + struct file *file; + int lineno; +}; + +#define for_all_properties(sym, st, tok) \ + for (st = sym->prop; st; st = st->next) \ + if (st->type == (tok)) +#define for_all_defaults(sym, st) for_all_properties(sym, st, P_DEFAULT) +#define for_all_choices(sym, st) for_all_properties(sym, st, P_CHOICE) +#define for_all_prompts(sym, st) \ + for (st = sym->prop; st; st = st->next) \ + if (st->text) + +struct menu { + struct menu *next; + struct menu *parent; + struct menu *list; + struct symbol *sym; + struct property *prompt; + struct expr *dep; + unsigned int flags; + //char *help; + struct file *file; + int lineno; + void *data; +}; + +#define MENU_CHANGED 0x0001 +#define MENU_ROOT 0x0002 + +#ifndef SWIG + +extern struct file *file_list; +extern struct file *current_file; +struct file *lookup_file(const char *name); + +extern struct symbol symbol_yes, symbol_no, symbol_mod; +extern struct symbol *modules_sym; +extern int cdebug; +struct expr *expr_alloc_symbol(struct symbol *sym); +struct expr *expr_alloc_one(enum expr_type type, struct expr *ce); +struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2); +struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2); +struct expr *expr_alloc_and(struct expr *e1, struct expr *e2); +struct expr *expr_alloc_or(struct expr *e1, struct expr *e2); +struct expr *expr_copy(struct expr *org); +void expr_free(struct expr *e); +int expr_eq(struct expr *e1, struct expr *e2); +void expr_eliminate_eq(struct expr **ep1, struct expr **ep2); +tristate expr_calc_value(struct expr *e); +struct expr *expr_eliminate_yn(struct expr *e); +struct expr *expr_trans_bool(struct expr *e); +struct expr *expr_eliminate_dups(struct expr *e); +struct expr *expr_transform(struct expr *e); +int expr_contains_symbol(struct expr *dep, struct symbol *sym); +bool expr_depends_symbol(struct expr *dep, struct symbol *sym); +struct expr *expr_extract_eq_and(struct expr **ep1, struct expr **ep2); +struct expr *expr_extract_eq_or(struct expr **ep1, struct expr **ep2); +void expr_extract_eq(enum expr_type type, struct expr **ep, struct expr **ep1, struct expr **ep2); +struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym); + +void expr_fprint(struct expr *e, FILE *out); +struct gstr; /* forward */ +void expr_gstr_print(struct expr *e, struct gstr *gs); + +static inline int expr_is_yes(struct expr *e) +{ + return !e || (e->type == E_SYMBOL && e->left.sym == &symbol_yes); +} + +static inline int expr_is_no(struct expr *e) +{ + return e && (e->type == E_SYMBOL && e->left.sym == &symbol_no); +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXPR_H */ diff --git a/scripts/kconfig/gconf.c b/scripts/kconfig/gconf.c new file mode 100644 index 000000000..566e35e5c --- /dev/null +++ b/scripts/kconfig/gconf.c @@ -0,0 +1,1645 @@ +/* Hey EMACS -*- linux-c -*- */ +/* + * + * Copyright (C) 2002-2003 Romain Lievin + * Released under the terms of the GNU GPL v2.0. + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "lkc.h" +#include "images.c" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +//#define DEBUG + +enum { + SINGLE_VIEW, SPLIT_VIEW, FULL_VIEW +}; + +static gint view_mode = FULL_VIEW; +static gboolean show_name = TRUE; +static gboolean show_range = TRUE; +static gboolean show_value = TRUE; +static gboolean show_all = FALSE; +static gboolean show_debug = FALSE; +static gboolean resizeable = FALSE; + +static gboolean config_changed = FALSE; + +static char nohelp_text[] = + N_("Sorry, no help available for this option yet.\n"); + +GtkWidget *main_wnd = NULL; +GtkWidget *tree1_w = NULL; // left frame +GtkWidget *tree2_w = NULL; // right frame +GtkWidget *text_w = NULL; +GtkWidget *hpaned = NULL; +GtkWidget *vpaned = NULL; +GtkWidget *back_btn = NULL; + +GtkTextTag *tag1, *tag2; +GdkColor color; + +GtkTreeStore *tree1, *tree2, *tree; +GtkTreeModel *model1, *model2; +static GtkTreeIter *parents[256]; +static gint indent; + +static struct menu *current; // current node for SINGLE view +static struct menu *browsed; // browsed node for SPLIT view + +enum { + COL_OPTION, COL_NAME, COL_NO, COL_MOD, COL_YES, COL_VALUE, + COL_MENU, COL_COLOR, COL_EDIT, COL_PIXBUF, + COL_PIXVIS, COL_BTNVIS, COL_BTNACT, COL_BTNINC, COL_BTNRAD, + COL_NUMBER +}; + +static void display_list(void); +static void display_tree(struct menu *menu); +static void display_tree_part(void); +static void update_tree(struct menu *src, GtkTreeIter * dst); +static void set_node(GtkTreeIter * node, struct menu *menu, gchar ** row); +static gchar **fill_row(struct menu *menu); + + +/* Helping/Debugging Functions */ + + +const char *dbg_print_stype(int val) +{ + static char buf[256]; + + memset(buf, 0, 256); + + if (val == S_UNKNOWN) + strcpy(buf, "unknown"); + if (val == S_BOOLEAN) + strcpy(buf, "boolean"); + if (val == S_TRISTATE) + strcpy(buf, "tristate"); + if (val == S_INT) + strcpy(buf, "int"); + if (val == S_HEX) + strcpy(buf, "hex"); + if (val == S_STRING) + strcpy(buf, "string"); + if (val == S_OTHER) + strcpy(buf, "other"); + +#ifdef DEBUG + printf("%s", buf); +#endif + + return buf; +} + +const char *dbg_print_flags(int val) +{ + static char buf[256]; + + memset(buf, 0, 256); + + if (val & SYMBOL_YES) + strcat(buf, "yes/"); + if (val & SYMBOL_MOD) + strcat(buf, "mod/"); + if (val & SYMBOL_NO) + strcat(buf, "no/"); + if (val & SYMBOL_CONST) + strcat(buf, "const/"); + if (val & SYMBOL_CHECK) + strcat(buf, "check/"); + if (val & SYMBOL_CHOICE) + strcat(buf, "choice/"); + if (val & SYMBOL_CHOICEVAL) + strcat(buf, "choiceval/"); + if (val & SYMBOL_PRINTED) + strcat(buf, "printed/"); + if (val & SYMBOL_VALID) + strcat(buf, "valid/"); + if (val & SYMBOL_OPTIONAL) + strcat(buf, "optional/"); + if (val & SYMBOL_WRITE) + strcat(buf, "write/"); + if (val & SYMBOL_CHANGED) + strcat(buf, "changed/"); + if (val & SYMBOL_NEW) + strcat(buf, "new/"); + if (val & SYMBOL_AUTO) + strcat(buf, "auto/"); + + buf[strlen(buf) - 1] = '\0'; +#ifdef DEBUG + printf("%s", buf); +#endif + + return buf; +} + +const char *dbg_print_ptype(int val) +{ + static char buf[256]; + + memset(buf, 0, 256); + + if (val == P_UNKNOWN) + strcpy(buf, "unknown"); + if (val == P_PROMPT) + strcpy(buf, "prompt"); + if (val == P_COMMENT) + strcpy(buf, "comment"); + if (val == P_MENU) + strcpy(buf, "menu"); + if (val == P_DEFAULT) + strcpy(buf, "default"); + if (val == P_CHOICE) + strcpy(buf, "choice"); + +#ifdef DEBUG + printf("%s", buf); +#endif + + return buf; +} + + +void replace_button_icon(GladeXML * xml, GdkDrawable * window, + GtkStyle * style, gchar * btn_name, gchar ** xpm) +{ + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkToolButton *button; + GtkWidget *image; + + pixmap = gdk_pixmap_create_from_xpm_d(window, &mask, + &style->bg[GTK_STATE_NORMAL], + xpm); + + button = GTK_TOOL_BUTTON(glade_xml_get_widget(xml, btn_name)); + image = gtk_image_new_from_pixmap(pixmap, mask); + gtk_widget_show(image); + gtk_tool_button_set_icon_widget(button, image); +} + +/* Main Window Initialization */ +void init_main_window(const gchar * glade_file) +{ + GladeXML *xml; + GtkWidget *widget; + GtkTextBuffer *txtbuf; + char title[256]; + GtkStyle *style; + + xml = glade_xml_new(glade_file, "window1", NULL); + if (!xml) + g_error(_("GUI loading failed !\n")); + glade_xml_signal_autoconnect(xml); + + main_wnd = glade_xml_get_widget(xml, "window1"); + hpaned = glade_xml_get_widget(xml, "hpaned1"); + vpaned = glade_xml_get_widget(xml, "vpaned1"); + tree1_w = glade_xml_get_widget(xml, "treeview1"); + tree2_w = glade_xml_get_widget(xml, "treeview2"); + text_w = glade_xml_get_widget(xml, "textview3"); + + back_btn = glade_xml_get_widget(xml, "button1"); + gtk_widget_set_sensitive(back_btn, FALSE); + + widget = glade_xml_get_widget(xml, "show_name1"); + gtk_check_menu_item_set_active((GtkCheckMenuItem *) widget, + show_name); + + widget = glade_xml_get_widget(xml, "show_range1"); + gtk_check_menu_item_set_active((GtkCheckMenuItem *) widget, + show_range); + + widget = glade_xml_get_widget(xml, "show_data1"); + gtk_check_menu_item_set_active((GtkCheckMenuItem *) widget, + show_value); + + style = gtk_widget_get_style(main_wnd); + widget = glade_xml_get_widget(xml, "toolbar1"); + +#if 0 /* Use stock Gtk icons instead */ + replace_button_icon(xml, main_wnd->window, style, + "button1", (gchar **) xpm_back); + replace_button_icon(xml, main_wnd->window, style, + "button2", (gchar **) xpm_load); + replace_button_icon(xml, main_wnd->window, style, + "button3", (gchar **) xpm_save); +#endif + replace_button_icon(xml, main_wnd->window, style, + "button4", (gchar **) xpm_single_view); + replace_button_icon(xml, main_wnd->window, style, + "button5", (gchar **) xpm_split_view); + replace_button_icon(xml, main_wnd->window, style, + "button6", (gchar **) xpm_tree_view); + +#if 0 + switch (view_mode) { + case SINGLE_VIEW: + widget = glade_xml_get_widget(xml, "button4"); + g_signal_emit_by_name(widget, "clicked"); + break; + case SPLIT_VIEW: + widget = glade_xml_get_widget(xml, "button5"); + g_signal_emit_by_name(widget, "clicked"); + break; + case FULL_VIEW: + widget = glade_xml_get_widget(xml, "button6"); + g_signal_emit_by_name(widget, "clicked"); + break; + } +#endif + txtbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_w)); + tag1 = gtk_text_buffer_create_tag(txtbuf, "mytag1", + "foreground", "red", + "weight", PANGO_WEIGHT_BOLD, + NULL); + tag2 = gtk_text_buffer_create_tag(txtbuf, "mytag2", + /*"style", PANGO_STYLE_OBLIQUE, */ + NULL); + + sprintf(title, _("Linux Kernel v%s Configuration"), + getenv("KERNELVERSION")); + gtk_window_set_title(GTK_WINDOW(main_wnd), title); + + gtk_widget_show(main_wnd); +} + +void init_tree_model(void) +{ + gint i; + + tree = tree2 = gtk_tree_store_new(COL_NUMBER, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_POINTER, GDK_TYPE_COLOR, + G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN); + model2 = GTK_TREE_MODEL(tree2); + + for (parents[0] = NULL, i = 1; i < 256; i++) + parents[i] = (GtkTreeIter *) g_malloc(sizeof(GtkTreeIter)); + + tree1 = gtk_tree_store_new(COL_NUMBER, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_POINTER, GDK_TYPE_COLOR, + G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN); + model1 = GTK_TREE_MODEL(tree1); +} + +void init_left_tree(void) +{ + GtkTreeView *view = GTK_TREE_VIEW(tree1_w); + GtkCellRenderer *renderer; + GtkTreeSelection *sel; + GtkTreeViewColumn *column; + + gtk_tree_view_set_model(view, model1); + gtk_tree_view_set_headers_visible(view, TRUE); + gtk_tree_view_set_rules_hint(view, FALSE); + + column = gtk_tree_view_column_new(); + gtk_tree_view_append_column(view, column); + gtk_tree_view_column_set_title(column, _("Options")); + + renderer = gtk_cell_renderer_toggle_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "active", COL_BTNACT, + "inconsistent", COL_BTNINC, + "visible", COL_BTNVIS, + "radio", COL_BTNRAD, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "text", COL_OPTION, + "foreground-gdk", + COL_COLOR, NULL); + + sel = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE); + gtk_widget_realize(tree1_w); +} + +static void renderer_edited(GtkCellRendererText * cell, + const gchar * path_string, + const gchar * new_text, gpointer user_data); +static void renderer_toggled(GtkCellRendererToggle * cellrenderertoggle, + gchar * arg1, gpointer user_data); + +void init_right_tree(void) +{ + GtkTreeView *view = GTK_TREE_VIEW(tree2_w); + GtkCellRenderer *renderer; + GtkTreeSelection *sel; + GtkTreeViewColumn *column; + gint i; + + gtk_tree_view_set_model(view, model2); + gtk_tree_view_set_headers_visible(view, TRUE); + gtk_tree_view_set_rules_hint(view, FALSE); + + column = gtk_tree_view_column_new(); + gtk_tree_view_append_column(view, column); + gtk_tree_view_column_set_title(column, _("Options")); + + renderer = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "pixbuf", COL_PIXBUF, + "visible", COL_PIXVIS, NULL); + renderer = gtk_cell_renderer_toggle_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "active", COL_BTNACT, + "inconsistent", COL_BTNINC, + "visible", COL_BTNVIS, + "radio", COL_BTNRAD, NULL); + /*g_signal_connect(G_OBJECT(renderer), "toggled", + G_CALLBACK(renderer_toggled), NULL); */ + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "text", COL_OPTION, + "foreground-gdk", + COL_COLOR, NULL); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + _("Name"), renderer, + "text", COL_NAME, + "foreground-gdk", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "N", renderer, + "text", COL_NO, + "foreground-gdk", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "M", renderer, + "text", COL_MOD, + "foreground-gdk", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "Y", renderer, + "text", COL_YES, + "foreground-gdk", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + _("Value"), renderer, + "text", COL_VALUE, + "editable", + COL_EDIT, + "foreground-gdk", + COL_COLOR, NULL); + g_signal_connect(G_OBJECT(renderer), "edited", + G_CALLBACK(renderer_edited), NULL); + + column = gtk_tree_view_get_column(view, COL_NAME); + gtk_tree_view_column_set_visible(column, show_name); + column = gtk_tree_view_get_column(view, COL_NO); + gtk_tree_view_column_set_visible(column, show_range); + column = gtk_tree_view_get_column(view, COL_MOD); + gtk_tree_view_column_set_visible(column, show_range); + column = gtk_tree_view_get_column(view, COL_YES); + gtk_tree_view_column_set_visible(column, show_range); + column = gtk_tree_view_get_column(view, COL_VALUE); + gtk_tree_view_column_set_visible(column, show_value); + + if (resizeable) { + for (i = 0; i < COL_VALUE; i++) { + column = gtk_tree_view_get_column(view, i); + gtk_tree_view_column_set_resizable(column, TRUE); + } + } + + sel = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE); +} + + +/* Utility Functions */ + + +static void text_insert_help(struct menu *menu) +{ + GtkTextBuffer *buffer; + GtkTextIter start, end; + const char *prompt = menu_get_prompt(menu); + gchar *name; + const char *help = _(nohelp_text); + + if (!menu->sym) + help = ""; + else if (menu->sym->help) + help = _(menu->sym->help); + + if (menu->sym && menu->sym->name) + name = g_strdup_printf(_(menu->sym->name)); + else + name = g_strdup(""); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_w)); + gtk_text_buffer_get_bounds(buffer, &start, &end); + gtk_text_buffer_delete(buffer, &start, &end); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_w), 15); + + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, prompt, -1, tag1, + NULL); + gtk_text_buffer_insert_at_cursor(buffer, " ", 1); + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, name, -1, tag1, + NULL); + gtk_text_buffer_insert_at_cursor(buffer, "\n\n", 2); + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, help, -1, tag2, + NULL); +} + + +static void text_insert_msg(const char *title, const char *message) +{ + GtkTextBuffer *buffer; + GtkTextIter start, end; + const char *msg = message; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_w)); + gtk_text_buffer_get_bounds(buffer, &start, &end); + gtk_text_buffer_delete(buffer, &start, &end); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_w), 15); + + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, title, -1, tag1, + NULL); + gtk_text_buffer_insert_at_cursor(buffer, "\n\n", 2); + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, msg, -1, tag2, + NULL); +} + + +/* Main Windows Callbacks */ + +void on_save1_activate(GtkMenuItem * menuitem, gpointer user_data); +gboolean on_window1_delete_event(GtkWidget * widget, GdkEvent * event, + gpointer user_data) +{ + GtkWidget *dialog, *label; + gint result; + + if (config_changed == FALSE) + return FALSE; + + dialog = gtk_dialog_new_with_buttons(_("Warning !"), + GTK_WINDOW(main_wnd), + (GtkDialogFlags) + (GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_STOCK_OK, + GTK_RESPONSE_YES, + GTK_STOCK_NO, + GTK_RESPONSE_NO, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), + GTK_RESPONSE_CANCEL); + + label = gtk_label_new(_("\nSave configuration ?\n")); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label); + gtk_widget_show(label); + + result = gtk_dialog_run(GTK_DIALOG(dialog)); + switch (result) { + case GTK_RESPONSE_YES: + on_save1_activate(NULL, NULL); + return FALSE; + case GTK_RESPONSE_NO: + return FALSE; + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + default: + gtk_widget_destroy(dialog); + return TRUE; + } + + return FALSE; +} + + +void on_window1_destroy(GtkObject * object, gpointer user_data) +{ + gtk_main_quit(); +} + + +void +on_window1_size_request(GtkWidget * widget, + GtkRequisition * requisition, gpointer user_data) +{ + static gint old_h; + gint w, h; + + if (widget->window == NULL) + gtk_window_get_default_size(GTK_WINDOW(main_wnd), &w, &h); + else + gdk_window_get_size(widget->window, &w, &h); + + if (h == old_h) + return; + old_h = h; + + gtk_paned_set_position(GTK_PANED(vpaned), 2 * h / 3); +} + + +/* Menu & Toolbar Callbacks */ + + +static void +load_filename(GtkFileSelection * file_selector, gpointer user_data) +{ + const gchar *fn; + + fn = gtk_file_selection_get_filename(GTK_FILE_SELECTION + (user_data)); + + if (conf_read(fn)) + text_insert_msg(_("Error"), _("Unable to load configuration !")); + else + display_tree(&rootmenu); +} + +void on_load1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkWidget *fs; + + fs = gtk_file_selection_new(_("Load file...")); + g_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button), + "clicked", + G_CALLBACK(load_filename), (gpointer) fs); + g_signal_connect_swapped(GTK_OBJECT + (GTK_FILE_SELECTION(fs)->ok_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer) fs); + g_signal_connect_swapped(GTK_OBJECT + (GTK_FILE_SELECTION(fs)->cancel_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer) fs); + gtk_widget_show(fs); +} + + +void on_save1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + if (conf_write(NULL)) + text_insert_msg(_("Error"), _("Unable to save configuration !")); + + config_changed = FALSE; +} + + +static void +store_filename(GtkFileSelection * file_selector, gpointer user_data) +{ + const gchar *fn; + + fn = gtk_file_selection_get_filename(GTK_FILE_SELECTION + (user_data)); + + if (conf_write(fn)) + text_insert_msg(_("Error"), _("Unable to save configuration !")); + + gtk_widget_destroy(GTK_WIDGET(user_data)); +} + +void on_save_as1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkWidget *fs; + + fs = gtk_file_selection_new(_("Save file as...")); + g_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button), + "clicked", + G_CALLBACK(store_filename), (gpointer) fs); + g_signal_connect_swapped(GTK_OBJECT + (GTK_FILE_SELECTION(fs)->ok_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer) fs); + g_signal_connect_swapped(GTK_OBJECT + (GTK_FILE_SELECTION(fs)->cancel_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer) fs); + gtk_widget_show(fs); +} + + +void on_quit1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + if (!on_window1_delete_event(NULL, NULL, NULL)) + gtk_widget_destroy(GTK_WIDGET(main_wnd)); +} + + +void on_show_name1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkTreeViewColumn *col; + + show_name = GTK_CHECK_MENU_ITEM(menuitem)->active; + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_NAME); + if (col) + gtk_tree_view_column_set_visible(col, show_name); +} + + +void on_show_range1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkTreeViewColumn *col; + + show_range = GTK_CHECK_MENU_ITEM(menuitem)->active; + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_NO); + if (col) + gtk_tree_view_column_set_visible(col, show_range); + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_MOD); + if (col) + gtk_tree_view_column_set_visible(col, show_range); + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_YES); + if (col) + gtk_tree_view_column_set_visible(col, show_range); + +} + + +void on_show_data1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkTreeViewColumn *col; + + show_value = GTK_CHECK_MENU_ITEM(menuitem)->active; + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_VALUE); + if (col) + gtk_tree_view_column_set_visible(col, show_value); +} + + +void +on_show_all_options1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + show_all = GTK_CHECK_MENU_ITEM(menuitem)->active; + + gtk_tree_store_clear(tree2); + display_tree(&rootmenu); // instead of update_tree to speed-up +} + + +void +on_show_debug_info1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + show_debug = GTK_CHECK_MENU_ITEM(menuitem)->active; + update_tree(&rootmenu, NULL); +} + + +void on_introduction1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkWidget *dialog; + const gchar *intro_text = _( + "Welcome to gkc, the GTK+ graphical busybox configuration tool\n" + "for Linux.\n" + "For each option, a blank box indicates the feature is disabled, a\n" + "check indicates it is enabled, and a dot indicates that it is to\n" + "be compiled as a module. Clicking on the box will cycle through the three states.\n" + "\n" + "If you do not see an option (e.g., a device driver) that you\n" + "believe should be present, try turning on Show All Options\n" + "under the Options menu.\n" + "Although there is no cross reference yet to help you figure out\n" + "what other options must be enabled to support the option you\n" + "are interested in, you can still view the help of a grayed-out\n" + "option.\n" + "\n" + "Toggling Show Debug Info under the Options menu will show\n" + "the dependencies, which you can then match by examining other options."); + + dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, intro_text); + g_signal_connect_swapped(GTK_OBJECT(dialog), "response", + G_CALLBACK(gtk_widget_destroy), + GTK_OBJECT(dialog)); + gtk_widget_show_all(dialog); +} + + +void on_about1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkWidget *dialog; + const gchar *about_text = + _("gkc is copyright (c) 2002 Romain Lievin .\n" + "Based on the source code from Roman Zippel.\n"); + + dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, about_text); + g_signal_connect_swapped(GTK_OBJECT(dialog), "response", + G_CALLBACK(gtk_widget_destroy), + GTK_OBJECT(dialog)); + gtk_widget_show_all(dialog); +} + + +void on_license1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkWidget *dialog; + const gchar *license_text = + _("gkc is released under the terms of the GNU GPL v2.\n" + "For more information, please see the source code or\n" + "visit http://www.fsf.org/licenses/licenses.html\n"); + + dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, license_text); + g_signal_connect_swapped(GTK_OBJECT(dialog), "response", + G_CALLBACK(gtk_widget_destroy), + GTK_OBJECT(dialog)); + gtk_widget_show_all(dialog); +} + + +void on_back_clicked(GtkButton * button, gpointer user_data) +{ + enum prop_type ptype; + + current = current->parent; + ptype = current->prompt ? current->prompt->type : P_UNKNOWN; + if (ptype != P_MENU) + current = current->parent; + display_tree_part(); + + if (current == &rootmenu) + gtk_widget_set_sensitive(back_btn, FALSE); +} + + +void on_load_clicked(GtkButton * button, gpointer user_data) +{ + on_load1_activate(NULL, user_data); +} + + +void on_save_clicked(GtkButton * button, gpointer user_data) +{ + on_save1_activate(NULL, user_data); +} + + +void on_single_clicked(GtkButton * button, gpointer user_data) +{ + view_mode = SINGLE_VIEW; + gtk_paned_set_position(GTK_PANED(hpaned), 0); + gtk_widget_hide(tree1_w); + current = &rootmenu; + display_tree_part(); +} + + +void on_split_clicked(GtkButton * button, gpointer user_data) +{ + gint w, h; + view_mode = SPLIT_VIEW; + gtk_widget_show(tree1_w); + gtk_window_get_default_size(GTK_WINDOW(main_wnd), &w, &h); + gtk_paned_set_position(GTK_PANED(hpaned), w / 2); + if (tree2) + gtk_tree_store_clear(tree2); + display_list(); + + /* Disable back btn, like in full mode. */ + gtk_widget_set_sensitive(back_btn, FALSE); +} + + +void on_full_clicked(GtkButton * button, gpointer user_data) +{ + view_mode = FULL_VIEW; + gtk_paned_set_position(GTK_PANED(hpaned), 0); + gtk_widget_hide(tree1_w); + if (tree2) + gtk_tree_store_clear(tree2); + display_tree(&rootmenu); + gtk_widget_set_sensitive(back_btn, FALSE); +} + + +void on_collapse_clicked(GtkButton * button, gpointer user_data) +{ + gtk_tree_view_collapse_all(GTK_TREE_VIEW(tree2_w)); +} + + +void on_expand_clicked(GtkButton * button, gpointer user_data) +{ + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree2_w)); +} + + +/* CTree Callbacks */ + +/* Change hex/int/string value in the cell */ +static void renderer_edited(GtkCellRendererText * cell, + const gchar * path_string, + const gchar * new_text, gpointer user_data) +{ + GtkTreePath *path = gtk_tree_path_new_from_string(path_string); + GtkTreeIter iter; + const char *old_def, *new_def; + struct menu *menu; + struct symbol *sym; + + if (!gtk_tree_model_get_iter(model2, &iter, path)) + return; + + gtk_tree_model_get(model2, &iter, COL_MENU, &menu, -1); + sym = menu->sym; + + gtk_tree_model_get(model2, &iter, COL_VALUE, &old_def, -1); + new_def = new_text; + + sym_set_string_value(sym, new_def); + + config_changed = TRUE; + update_tree(&rootmenu, NULL); + + gtk_tree_path_free(path); +} + +/* Change the value of a symbol and update the tree */ +static void change_sym_value(struct menu *menu, gint col) +{ + struct symbol *sym = menu->sym; + tristate oldval, newval; + + if (!sym) + return; + + if (col == COL_NO) + newval = no; + else if (col == COL_MOD) + newval = mod; + else if (col == COL_YES) + newval = yes; + else + return; + + switch (sym_get_type(sym)) { + case S_BOOLEAN: + case S_TRISTATE: + oldval = sym_get_tristate_value(sym); + if (!sym_tristate_within_range(sym, newval)) + newval = yes; + sym_set_tristate_value(sym, newval); + config_changed = TRUE; + if (view_mode == FULL_VIEW) + update_tree(&rootmenu, NULL); + else if (view_mode == SPLIT_VIEW) { + update_tree(browsed, NULL); + display_list(); + } + else if (view_mode == SINGLE_VIEW) + display_tree_part(); //fixme: keep exp/coll + break; + case S_INT: + case S_HEX: + case S_STRING: + default: + break; + } +} + +static void toggle_sym_value(struct menu *menu) +{ + if (!menu->sym) + return; + + sym_toggle_tristate_value(menu->sym); + if (view_mode == FULL_VIEW) + update_tree(&rootmenu, NULL); + else if (view_mode == SPLIT_VIEW) { + update_tree(browsed, NULL); + display_list(); + } + else if (view_mode == SINGLE_VIEW) + display_tree_part(); //fixme: keep exp/coll +} + +static void renderer_toggled(GtkCellRendererToggle * cell, + gchar * path_string, gpointer user_data) +{ + GtkTreePath *path, *sel_path = NULL; + GtkTreeIter iter, sel_iter; + GtkTreeSelection *sel; + struct menu *menu; + + path = gtk_tree_path_new_from_string(path_string); + if (!gtk_tree_model_get_iter(model2, &iter, path)) + return; + + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree2_w)); + if (gtk_tree_selection_get_selected(sel, NULL, &sel_iter)) + sel_path = gtk_tree_model_get_path(model2, &sel_iter); + if (!sel_path) + goto out1; + if (gtk_tree_path_compare(path, sel_path)) + goto out2; + + gtk_tree_model_get(model2, &iter, COL_MENU, &menu, -1); + toggle_sym_value(menu); + + out2: + gtk_tree_path_free(sel_path); + out1: + gtk_tree_path_free(path); +} + +static gint column2index(GtkTreeViewColumn * column) +{ + gint i; + + for (i = 0; i < COL_NUMBER; i++) { + GtkTreeViewColumn *col; + + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), i); + if (col == column) + return i; + } + + return -1; +} + + +/* User click: update choice (full) or goes down (single) */ +gboolean +on_treeview2_button_press_event(GtkWidget * widget, + GdkEventButton * event, gpointer user_data) +{ + GtkTreeView *view = GTK_TREE_VIEW(widget); + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkTreeIter iter; + struct menu *menu; + gint col; + +#if GTK_CHECK_VERSION(2,1,4) // bug in ctree with earlier version of GTK + gint tx = (gint) event->x; + gint ty = (gint) event->y; + gint cx, cy; + + gtk_tree_view_get_path_at_pos(view, tx, ty, &path, &column, &cx, + &cy); +#else + gtk_tree_view_get_cursor(view, &path, &column); +#endif + if (path == NULL) + return FALSE; + + if (!gtk_tree_model_get_iter(model2, &iter, path)) + return FALSE; + gtk_tree_model_get(model2, &iter, COL_MENU, &menu, -1); + + col = column2index(column); + if (event->type == GDK_2BUTTON_PRESS) { + enum prop_type ptype; + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + + if (ptype == P_MENU && view_mode != FULL_VIEW && col == COL_OPTION) { + // goes down into menu + current = menu; + display_tree_part(); + gtk_widget_set_sensitive(back_btn, TRUE); + } else if ((col == COL_OPTION)) { + toggle_sym_value(menu); + gtk_tree_view_expand_row(view, path, TRUE); + } + } else { + if (col == COL_VALUE) { + toggle_sym_value(menu); + gtk_tree_view_expand_row(view, path, TRUE); + } else if (col == COL_NO || col == COL_MOD + || col == COL_YES) { + change_sym_value(menu, col); + gtk_tree_view_expand_row(view, path, TRUE); + } + } + + return FALSE; +} + +/* Key pressed: update choice */ +gboolean +on_treeview2_key_press_event(GtkWidget * widget, + GdkEventKey * event, gpointer user_data) +{ + GtkTreeView *view = GTK_TREE_VIEW(widget); + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkTreeIter iter; + struct menu *menu; + gint col; + + gtk_tree_view_get_cursor(view, &path, &column); + if (path == NULL) + return FALSE; + + if (event->keyval == GDK_space) { + if (gtk_tree_view_row_expanded(view, path)) + gtk_tree_view_collapse_row(view, path); + else + gtk_tree_view_expand_row(view, path, FALSE); + return TRUE; + } + if (event->keyval == GDK_KP_Enter) { + } + if (widget == tree1_w) + return FALSE; + + gtk_tree_model_get_iter(model2, &iter, path); + gtk_tree_model_get(model2, &iter, COL_MENU, &menu, -1); + + if (!strcasecmp(event->string, "n")) + col = COL_NO; + else if (!strcasecmp(event->string, "m")) + col = COL_MOD; + else if (!strcasecmp(event->string, "y")) + col = COL_YES; + else + col = -1; + change_sym_value(menu, col); + + return FALSE; +} + + +/* Row selection changed: update help */ +void +on_treeview2_cursor_changed(GtkTreeView * treeview, gpointer user_data) +{ + GtkTreeSelection *selection; + GtkTreeIter iter; + struct menu *menu; + + selection = gtk_tree_view_get_selection(treeview); + if (gtk_tree_selection_get_selected(selection, &model2, &iter)) { + gtk_tree_model_get(model2, &iter, COL_MENU, &menu, -1); + text_insert_help(menu); + } +} + + +/* User click: display sub-tree in the right frame. */ +gboolean +on_treeview1_button_press_event(GtkWidget * widget, + GdkEventButton * event, gpointer user_data) +{ + GtkTreeView *view = GTK_TREE_VIEW(widget); + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkTreeIter iter; + struct menu *menu; + + gint tx = (gint) event->x; + gint ty = (gint) event->y; + gint cx, cy; + + gtk_tree_view_get_path_at_pos(view, tx, ty, &path, &column, &cx, + &cy); + if (path == NULL) + return FALSE; + + gtk_tree_model_get_iter(model1, &iter, path); + gtk_tree_model_get(model1, &iter, COL_MENU, &menu, -1); + + if (event->type == GDK_2BUTTON_PRESS) { + toggle_sym_value(menu); + current = menu; + display_tree_part(); + } else { + browsed = menu; + display_tree_part(); + } + + gtk_widget_realize(tree2_w); + gtk_tree_view_set_cursor(view, path, NULL, FALSE); + gtk_widget_grab_focus(tree2_w); + + return FALSE; +} + + +/* Fill a row of strings */ +static gchar **fill_row(struct menu *menu) +{ + static gchar *row[COL_NUMBER]; + struct symbol *sym = menu->sym; + const char *def; + int stype; + tristate val; + enum prop_type ptype; + int i; + + for (i = COL_OPTION; i <= COL_COLOR; i++) + g_free(row[i]); + memset(row, 0, sizeof(row)); + + row[COL_OPTION] = + g_strdup_printf("%s %s", menu_get_prompt(menu), + sym ? (sym-> + flags & SYMBOL_NEW ? "(NEW)" : "") : + ""); + + if (show_all && !menu_is_visible(menu)) + row[COL_COLOR] = g_strdup("DarkGray"); + else + row[COL_COLOR] = g_strdup("Black"); + + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + switch (ptype) { + case P_MENU: + row[COL_PIXBUF] = (gchar *) xpm_menu; + if (view_mode == SINGLE_VIEW) + row[COL_PIXVIS] = GINT_TO_POINTER(TRUE); + row[COL_BTNVIS] = GINT_TO_POINTER(FALSE); + break; + case P_COMMENT: + row[COL_PIXBUF] = (gchar *) xpm_void; + row[COL_PIXVIS] = GINT_TO_POINTER(FALSE); + row[COL_BTNVIS] = GINT_TO_POINTER(FALSE); + break; + default: + row[COL_PIXBUF] = (gchar *) xpm_void; + row[COL_PIXVIS] = GINT_TO_POINTER(FALSE); + row[COL_BTNVIS] = GINT_TO_POINTER(TRUE); + break; + } + + if (!sym) + return row; + row[COL_NAME] = g_strdup(sym->name); + + sym_calc_value(sym); + sym->flags &= ~SYMBOL_CHANGED; + + if (sym_is_choice(sym)) { // parse childs for getting final value + struct menu *child; + struct symbol *def_sym = sym_get_choice_value(sym); + struct menu *def_menu = NULL; + + row[COL_BTNVIS] = GINT_TO_POINTER(FALSE); + + for (child = menu->list; child; child = child->next) { + if (menu_is_visible(child) + && child->sym == def_sym) + def_menu = child; + } + + if (def_menu) + row[COL_VALUE] = + g_strdup(menu_get_prompt(def_menu)); + } + if (sym->flags & SYMBOL_CHOICEVAL) + row[COL_BTNRAD] = GINT_TO_POINTER(TRUE); + + stype = sym_get_type(sym); + switch (stype) { + case S_BOOLEAN: + if (GPOINTER_TO_INT(row[COL_PIXVIS]) == FALSE) + row[COL_BTNVIS] = GINT_TO_POINTER(TRUE); + if (sym_is_choice(sym)) + break; + case S_TRISTATE: + val = sym_get_tristate_value(sym); + switch (val) { + case no: + row[COL_NO] = g_strdup("N"); + row[COL_VALUE] = g_strdup("N"); + row[COL_BTNACT] = GINT_TO_POINTER(FALSE); + row[COL_BTNINC] = GINT_TO_POINTER(FALSE); + break; + case mod: + row[COL_MOD] = g_strdup("M"); + row[COL_VALUE] = g_strdup("M"); + row[COL_BTNINC] = GINT_TO_POINTER(TRUE); + break; + case yes: + row[COL_YES] = g_strdup("Y"); + row[COL_VALUE] = g_strdup("Y"); + row[COL_BTNACT] = GINT_TO_POINTER(TRUE); + row[COL_BTNINC] = GINT_TO_POINTER(FALSE); + break; + } + + if (val != no && sym_tristate_within_range(sym, no)) + row[COL_NO] = g_strdup("_"); + if (val != mod && sym_tristate_within_range(sym, mod)) + row[COL_MOD] = g_strdup("_"); + if (val != yes && sym_tristate_within_range(sym, yes)) + row[COL_YES] = g_strdup("_"); + break; + case S_INT: + case S_HEX: + case S_STRING: + def = sym_get_string_value(sym); + row[COL_VALUE] = g_strdup(def); + row[COL_EDIT] = GINT_TO_POINTER(TRUE); + row[COL_BTNVIS] = GINT_TO_POINTER(FALSE); + break; + } + + return row; +} + + +/* Set the node content with a row of strings */ +static void set_node(GtkTreeIter * node, struct menu *menu, gchar ** row) +{ + GdkColor color; + gboolean success; + GdkPixbuf *pix; + + pix = gdk_pixbuf_new_from_xpm_data((const char **) + row[COL_PIXBUF]); + + gdk_color_parse(row[COL_COLOR], &color); + gdk_colormap_alloc_colors(gdk_colormap_get_system(), &color, 1, + FALSE, FALSE, &success); + + gtk_tree_store_set(tree, node, + COL_OPTION, row[COL_OPTION], + COL_NAME, row[COL_NAME], + COL_NO, row[COL_NO], + COL_MOD, row[COL_MOD], + COL_YES, row[COL_YES], + COL_VALUE, row[COL_VALUE], + COL_MENU, (gpointer) menu, + COL_COLOR, &color, + COL_EDIT, GPOINTER_TO_INT(row[COL_EDIT]), + COL_PIXBUF, pix, + COL_PIXVIS, GPOINTER_TO_INT(row[COL_PIXVIS]), + COL_BTNVIS, GPOINTER_TO_INT(row[COL_BTNVIS]), + COL_BTNACT, GPOINTER_TO_INT(row[COL_BTNACT]), + COL_BTNINC, GPOINTER_TO_INT(row[COL_BTNINC]), + COL_BTNRAD, GPOINTER_TO_INT(row[COL_BTNRAD]), + -1); + + g_object_unref(pix); +} + + +/* Add a node to the tree */ +static void place_node(struct menu *menu, char **row) +{ + GtkTreeIter *parent = parents[indent - 1]; + GtkTreeIter *node = parents[indent]; + + gtk_tree_store_append(tree, node, parent); + set_node(node, menu, row); +} + + +/* Find a node in the GTK+ tree */ +static GtkTreeIter found; + +/* + * Find a menu in the GtkTree starting at parent. + */ +GtkTreeIter *gtktree_iter_find_node(GtkTreeIter * parent, + struct menu *tofind) +{ + GtkTreeIter iter; + GtkTreeIter *child = &iter; + gboolean valid; + GtkTreeIter *ret; + + valid = gtk_tree_model_iter_children(model2, child, parent); + while (valid) { + struct menu *menu; + + gtk_tree_model_get(model2, child, 6, &menu, -1); + + if (menu == tofind) { + memcpy(&found, child, sizeof(GtkTreeIter)); + return &found; + } + + ret = gtktree_iter_find_node(child, tofind); + if (ret) + return ret; + + valid = gtk_tree_model_iter_next(model2, child); + } + + return NULL; +} + + +/* + * Update the tree by adding/removing entries + * Does not change other nodes + */ +static void update_tree(struct menu *src, GtkTreeIter * dst) +{ + struct menu *child1; + GtkTreeIter iter, tmp; + GtkTreeIter *child2 = &iter; + gboolean valid; + GtkTreeIter *sibling; + struct symbol *sym; + struct property *prop; + struct menu *menu1, *menu2; + + if (src == &rootmenu) + indent = 1; + + valid = gtk_tree_model_iter_children(model2, child2, dst); + for (child1 = src->list; child1; child1 = child1->next) { + + prop = child1->prompt; + sym = child1->sym; + + reparse: + menu1 = child1; + if (valid) + gtk_tree_model_get(model2, child2, COL_MENU, + &menu2, -1); + else + menu2 = NULL; // force adding of a first child + +#ifdef DEBUG + printf("%*c%s | %s\n", indent, ' ', + menu1 ? menu_get_prompt(menu1) : "nil", + menu2 ? menu_get_prompt(menu2) : "nil"); +#endif + + if (!menu_is_visible(child1) && !show_all) { // remove node + if (gtktree_iter_find_node(dst, menu1) != NULL) { + memcpy(&tmp, child2, sizeof(GtkTreeIter)); + valid = gtk_tree_model_iter_next(model2, + child2); + gtk_tree_store_remove(tree2, &tmp); + if (!valid) + return; // next parent + else + goto reparse; // next child + } else + continue; + } + + if (menu1 != menu2) { + if (gtktree_iter_find_node(dst, menu1) == NULL) { // add node + if (!valid && !menu2) + sibling = NULL; + else + sibling = child2; + gtk_tree_store_insert_before(tree2, + child2, + dst, sibling); + set_node(child2, menu1, fill_row(menu1)); + if (menu2 == NULL) + valid = TRUE; + } else { // remove node + memcpy(&tmp, child2, sizeof(GtkTreeIter)); + valid = gtk_tree_model_iter_next(model2, + child2); + gtk_tree_store_remove(tree2, &tmp); + if (!valid) + return; // next parent + else + goto reparse; // next child + } + } else if (sym && (sym->flags & SYMBOL_CHANGED)) { + set_node(child2, menu1, fill_row(menu1)); + } + + indent++; + update_tree(child1, child2); + indent--; + + valid = gtk_tree_model_iter_next(model2, child2); + } +} + + +/* Display the whole tree (single/split/full view) */ +static void display_tree(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + enum prop_type ptype; + + if (menu == &rootmenu) { + indent = 1; + current = &rootmenu; + } + + for (child = menu->list; child; child = child->next) { + prop = child->prompt; + sym = child->sym; + ptype = prop ? prop->type : P_UNKNOWN; + + if (sym) + sym->flags &= ~SYMBOL_CHANGED; + + if ((view_mode == SPLIT_VIEW) + && !(child->flags & MENU_ROOT) && (tree == tree1)) + continue; + + if ((view_mode == SPLIT_VIEW) && (child->flags & MENU_ROOT) + && (tree == tree2)) + continue; + + if (menu_is_visible(child) || show_all) + place_node(child, fill_row(child)); +#ifdef DEBUG + printf("%*c%s: ", indent, ' ', menu_get_prompt(child)); + printf("%s", child->flags & MENU_ROOT ? "rootmenu | " : ""); + dbg_print_ptype(ptype); + printf(" | "); + if (sym) { + dbg_print_stype(sym->type); + printf(" | "); + dbg_print_flags(sym->flags); + printf("\n"); + } else + printf("\n"); +#endif + if ((view_mode != FULL_VIEW) && (ptype == P_MENU) + && (tree == tree2)) + continue; +/* + if (((menu != &rootmenu) && !(menu->flags & MENU_ROOT)) + || (view_mode == FULL_VIEW) + || (view_mode == SPLIT_VIEW))*/ + if (((view_mode == SINGLE_VIEW) && (menu->flags & MENU_ROOT)) + || (view_mode == FULL_VIEW) + || (view_mode == SPLIT_VIEW)) { + indent++; + display_tree(child); + indent--; + } + } +} + +/* Display a part of the tree starting at current node (single/split view) */ +static void display_tree_part(void) +{ + if (tree2) + gtk_tree_store_clear(tree2); + if (view_mode == SINGLE_VIEW) + display_tree(current); + else if (view_mode == SPLIT_VIEW) + display_tree(browsed); + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree2_w)); +} + +/* Display the list in the left frame (split view) */ +static void display_list(void) +{ + if (tree1) + gtk_tree_store_clear(tree1); + + tree = tree1; + display_tree(&rootmenu); + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree1_w)); + tree = tree2; +} + +void fixup_rootmenu(struct menu *menu) +{ + struct menu *child; + static int menu_cnt = 0; + + menu->flags |= MENU_ROOT; + for (child = menu->list; child; child = child->next) { + if (child->prompt && child->prompt->type == P_MENU) { + menu_cnt++; + fixup_rootmenu(child); + menu_cnt--; + } else if (!menu_cnt) + fixup_rootmenu(child); + } +} + + +/* Main */ +int main(int ac, char *av[]) +{ + const char *name; + char *env; + gchar *glade_file; + +#ifndef LKC_DIRECT_LINK + kconfig_load(); +#endif + + bindtextdomain(PACKAGE, LOCALEDIR); + bind_textdomain_codeset(PACKAGE, "UTF-8"); + textdomain(PACKAGE); + + /* GTK stuffs */ + gtk_set_locale(); + gtk_init(&ac, &av); + glade_init(); + + //add_pixmap_directory (PACKAGE_DATA_DIR "/" PACKAGE "/pixmaps"); + //add_pixmap_directory (PACKAGE_SOURCE_DIR "/pixmaps"); + + /* Determine GUI path */ + env = getenv(SRCTREE); + if (env) + glade_file = g_strconcat(env, "/scripts/kconfig/gconf.glade", NULL); + else if (av[0][0] == '/') + glade_file = g_strconcat(av[0], ".glade", NULL); + else + glade_file = g_strconcat(g_get_current_dir(), "/", av[0], ".glade", NULL); + + /* Load the interface and connect signals */ + init_main_window(glade_file); + init_tree_model(); + init_left_tree(); + init_right_tree(); + + /* Conf stuffs */ + if (ac > 1 && av[1][0] == '-') { + switch (av[1][1]) { + case 'a': + //showAll = 1; + break; + case 'h': + case '?': + printf("%s \n", av[0]); + exit(0); + } + name = av[2]; + } else + name = av[1]; + + conf_parse(name); + fixup_rootmenu(&rootmenu); + conf_read(NULL); + + switch (view_mode) { + case SINGLE_VIEW: + display_tree_part(); + break; + case SPLIT_VIEW: + display_list(); + break; + case FULL_VIEW: + display_tree(&rootmenu); + break; + } + + gtk_main(); + + return 0; +} diff --git a/scripts/kconfig/gconf.glade b/scripts/kconfig/gconf.glade new file mode 100644 index 000000000..f8744ed64 --- /dev/null +++ b/scripts/kconfig/gconf.glade @@ -0,0 +1,648 @@ + + + + + + + True + Gtk Kernel Configurator + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 640 + 480 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + + + + + + + True + False + 0 + + + + True + + + + True + _File + True + + + + + + + True + Load a config file + _Load + True + + + + + + True + gtk-open + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Save the config in .config + _Save + True + + + + + + True + gtk-save + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Save the config in a file + Save _as + True + + + + + True + gtk-save-as + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + + + True + _Quit + True + + + + + + True + gtk-quit + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + + + + + True + _Options + True + + + + + + + True + Show name + Show _name + True + False + + + + + + + True + Show range (Y/M/N) + Show _range + True + False + + + + + + + True + Show value of the option + Show _data + True + False + + + + + + + True + + + + + + True + Show all options + Show all _options + True + False + + + + + + + True + Show masked options + Show _debug info + True + False + + + + + + + + + + + True + _Help + True + + + + + + + True + _Introduction + True + + + + + + True + gtk-dialog-question + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + _About + True + + + + + + True + gtk-properties + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + _License + True + + + + + True + gtk-justify-fill + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + + + + 0 + False + False + + + + + + True + GTK_SHADOW_OUT + GTK_POS_LEFT + GTK_POS_TOP + + + + True + GTK_ORIENTATION_HORIZONTAL + GTK_TOOLBAR_BOTH + True + True + + + + True + Goes up of one level (single view) + Back + True + gtk-undo + True + True + False + + + + False + True + + + + + + True + True + True + False + + + + True + + + + + False + False + + + + + + True + Load a config file + Load + True + gtk-open + True + True + False + + + + False + True + + + + + + True + Save a config file + Save + True + gtk-save + True + True + False + + + + False + True + + + + + + True + True + True + False + + + + True + + + + + False + False + + + + + + True + Single view + Single + True + gtk-missing-image + True + True + False + + + + False + True + + + + + + True + Split view + Split + True + gtk-missing-image + True + True + False + + + + False + True + + + + + + True + Full view + Full + True + gtk-missing-image + True + True + False + + + + False + True + + + + + + True + True + True + False + + + + True + + + + + False + False + + + + + + True + Collapse the whole tree in the right frame + Collapse + True + gtk-remove + True + True + False + + + + False + True + + + + + + True + Expand the whole tree in the right frame + Expand + True + gtk-add + True + True + False + + + + False + True + + + + + + + 0 + False + False + + + + + + 1 + True + True + 0 + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + False + True + + + + + + + + True + False + + + + + + True + True + 0 + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + True + False + False + True + + + + + + + + True + False + + + + + + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_WORD + True + 0 + 0 + 0 + 0 + 0 + 0 + Sorry, no help available for this option yet. + + + + + True + True + + + + + True + True + + + + + 0 + True + True + + + + + + + diff --git a/scripts/kconfig/images.c b/scripts/kconfig/images.c new file mode 100644 index 000000000..d4f84bd4a --- /dev/null +++ b/scripts/kconfig/images.c @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +static const char *xpm_load[] = { +"22 22 5 1", +". c None", +"# c #000000", +"c c #838100", +"a c #ffff00", +"b c #ffffff", +"......................", +"......................", +"......................", +"............####....#.", +"...........#....##.##.", +"..................###.", +".................####.", +".####...........#####.", +"#abab##########.......", +"#babababababab#.......", +"#ababababababa#.......", +"#babababababab#.......", +"#ababab###############", +"#babab##cccccccccccc##", +"#abab##cccccccccccc##.", +"#bab##cccccccccccc##..", +"#ab##cccccccccccc##...", +"#b##cccccccccccc##....", +"###cccccccccccc##.....", +"##cccccccccccc##......", +"###############.......", +"......................"}; + +static const char *xpm_save[] = { +"22 22 5 1", +". c None", +"# c #000000", +"a c #838100", +"b c #c5c2c5", +"c c #cdb6d5", +"......................", +".####################.", +".#aa#bbbbbbbbbbbb#bb#.", +".#aa#bbbbbbbbbbbb#bb#.", +".#aa#bbbbbbbbbcbb####.", +".#aa#bbbccbbbbbbb#aa#.", +".#aa#bbbccbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aaa############aaa#.", +".#aaaaaaaaaaaaaaaaaa#.", +".#aaaaaaaaaaaaaaaaaa#.", +".#aaa#############aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +"..##################..", +"......................"}; + +static const char *xpm_back[] = { +"22 22 3 1", +". c None", +"# c #000083", +"a c #838183", +"......................", +"......................", +"......................", +"......................", +"......................", +"...........######a....", +"..#......##########...", +"..##...####......##a..", +"..###.###.........##..", +"..######..........##..", +"..#####...........##..", +"..######..........##..", +"..#######.........##..", +"..########.......##a..", +"...............a###...", +"...............###....", +"......................", +"......................", +"......................", +"......................", +"......................", +"......................"}; + +static const char *xpm_tree_view[] = { +"22 22 2 1", +". c None", +"# c #000000", +"......................", +"......................", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......########........", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......########........", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......########........", +"......................", +"......................"}; + +static const char *xpm_single_view[] = { +"22 22 2 1", +". c None", +"# c #000000", +"......................", +"......................", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"......................", +"......................"}; + +static const char *xpm_split_view[] = { +"22 22 2 1", +". c None", +"# c #000000", +"......................", +"......................", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......................", +"......................"}; + +static const char *xpm_symbol_no[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" .......... ", +" "}; + +static const char *xpm_symbol_mod[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . . ", +" . .. . ", +" . .... . ", +" . .... . ", +" . .. . ", +" . . ", +" . . ", +" .......... ", +" "}; + +static const char *xpm_symbol_yes[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . . ", +" . . . ", +" . .. . ", +" . . .. . ", +" . .... . ", +" . .. . ", +" . . ", +" .......... ", +" "}; + +static const char *xpm_choice_no[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .... ", +" .. .. ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" .. .. ", +" .... ", +" "}; + +static const char *xpm_choice_yes[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .... ", +" .. .. ", +" . . ", +" . .. . ", +" . .... . ", +" . .... . ", +" . .. . ", +" . . ", +" .. .. ", +" .... ", +" "}; + +static const char *xpm_menu[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . .. . ", +" . .... . ", +" . ...... . ", +" . ...... . ", +" . .... . ", +" . .. . ", +" . . ", +" .......... ", +" "}; + +static const char *xpm_menu_inv[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" .......... ", +" .. ...... ", +" .. .... ", +" .. .. ", +" .. .. ", +" .. .... ", +" .. ...... ", +" .......... ", +" .......... ", +" "}; + +static const char *xpm_menuback[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . .. . ", +" . .... . ", +" . ...... . ", +" . ...... . ", +" . .... . ", +" . .. . ", +" . . ", +" .......... ", +" "}; + +static const char *xpm_void[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/scripts/kconfig/kconfig_load.c b/scripts/kconfig/kconfig_load.c new file mode 100644 index 000000000..dbdcaad82 --- /dev/null +++ b/scripts/kconfig/kconfig_load.c @@ -0,0 +1,35 @@ +#include +#include +#include + +#include "lkc.h" + +#define P(name,type,arg) type (*name ## _p) arg +#include "lkc_proto.h" +#undef P + +void kconfig_load(void) +{ + void *handle; + char *error; + + handle = dlopen("./libkconfig.so", RTLD_LAZY); + if (!handle) { + handle = dlopen("./scripts/kconfig/libkconfig.so", RTLD_LAZY); + if (!handle) { + fprintf(stderr, "%s\n", dlerror()); + exit(1); + } + } + +#define P(name,type,arg) \ +{ \ + name ## _p = dlsym(handle, #name); \ + if ((error = dlerror())) { \ + fprintf(stderr, "%s\n", error); \ + exit(1); \ + } \ +} +#include "lkc_proto.h" +#undef P +} diff --git a/scripts/kconfig/kxgettext.c b/scripts/kconfig/kxgettext.c new file mode 100644 index 000000000..abee55ca6 --- /dev/null +++ b/scripts/kconfig/kxgettext.c @@ -0,0 +1,227 @@ +/* + * Arnaldo Carvalho de Melo , 2005 + * + * Released under the terms of the GNU GPL v2.0 + */ + +#include +#include + +#define LKC_DIRECT_LINK +#include "lkc.h" + +static char *escape(const char* text, char *bf, int len) +{ + char *bfp = bf; + int multiline = strchr(text, '\n') != NULL; + int eol = 0; + int textlen = strlen(text); + + if ((textlen > 0) && (text[textlen-1] == '\n')) + eol = 1; + + *bfp++ = '"'; + --len; + + if (multiline) { + *bfp++ = '"'; + *bfp++ = '\n'; + *bfp++ = '"'; + len -= 3; + } + + while (*text != '\0' && len > 1) { + if (*text == '"') + *bfp++ = '\\'; + else if (*text == '\n') { + *bfp++ = '\\'; + *bfp++ = 'n'; + *bfp++ = '"'; + *bfp++ = '\n'; + *bfp++ = '"'; + len -= 5; + ++text; + goto next; + } + *bfp++ = *text++; +next: + --len; + } + + if (multiline && eol) + bfp -= 3; + + *bfp++ = '"'; + *bfp = '\0'; + + return bf; +} + +struct file_line { + struct file_line *next; + char* file; + int lineno; +}; + +static struct file_line *file_line__new(char *file, int lineno) +{ + struct file_line *self = malloc(sizeof(*self)); + + if (self == NULL) + goto out; + + self->file = file; + self->lineno = lineno; + self->next = NULL; +out: + return self; +} + +struct message { + const char *msg; + const char *option; + struct message *next; + struct file_line *files; +}; + +static struct message *message__list; + +static struct message *message__new(const char *msg, char *option, char *file, int lineno) +{ + struct message *self = malloc(sizeof(*self)); + + if (self == NULL) + goto out; + + self->files = file_line__new(file, lineno); + if (self->files == NULL) + goto out_fail; + + self->msg = strdup(msg); + if (self->msg == NULL) + goto out_fail_msg; + + self->option = option; + self->next = NULL; +out: + return self; +out_fail_msg: + free(self->files); +out_fail: + free(self); + self = NULL; + goto out; +} + +static struct message *mesage__find(const char *msg) +{ + struct message *m = message__list; + + while (m != NULL) { + if (strcmp(m->msg, msg) == 0) + break; + m = m->next; + } + + return m; +} + +static int message__add_file_line(struct message *self, char *file, int lineno) +{ + int rc = -1; + struct file_line *fl = file_line__new(file, lineno); + + if (fl == NULL) + goto out; + + fl->next = self->files; + self->files = fl; + rc = 0; +out: + return rc; +} + +static int message__add(const char *msg, char *option, char *file, int lineno) +{ + int rc = 0; + char bf[16384]; + char *escaped = escape(msg, bf, sizeof(bf)); + struct message *m = mesage__find(escaped); + + if (m != NULL) + rc = message__add_file_line(m, file, lineno); + else { + m = message__new(escaped, option, file, lineno); + + if (m != NULL) { + m->next = message__list; + message__list = m; + } else + rc = -1; + } + return rc; +} + +void menu_build_message_list(struct menu *menu) +{ + struct menu *child; + + message__add(menu_get_prompt(menu), NULL, + menu->file == NULL ? "Root Menu" : menu->file->name, + menu->lineno); + + if (menu->sym != NULL && menu->sym->help != NULL) + message__add(menu->sym->help, menu->sym->name, + menu->file == NULL ? "Root Menu" : menu->file->name, + menu->lineno); + + for (child = menu->list; child != NULL; child = child->next) + if (child->prompt != NULL) + menu_build_message_list(child); +} + +static void message__print_file_lineno(struct message *self) +{ + struct file_line *fl = self->files; + + putchar('\n'); + if (self->option != NULL) + printf("# %s:00000\n", self->option); + + printf("#: %s:%d", fl->file, fl->lineno); + fl = fl->next; + + while (fl != NULL) { + printf(", %s:%d", fl->file, fl->lineno); + fl = fl->next; + } + + putchar('\n'); +} + +static void message__print_gettext_msgid_msgstr(struct message *self) +{ + message__print_file_lineno(self); + + printf("msgid %s\n" + "msgstr \"\"\n", self->msg); +} + +void menu__xgettext(void) +{ + struct message *m = message__list; + + while (m != NULL) { + message__print_gettext_msgid_msgstr(m); + m = m->next; + } +} + +int main(int ac, char **av) +{ + conf_parse(av[1]); + + menu_build_message_list(menu_get_root_menu(NULL)); + menu__xgettext(); + return 0; +} diff --git a/scripts/kconfig/lex.zconf.c_shipped b/scripts/kconfig/lex.zconf.c_shipped new file mode 100644 index 000000000..24e3c8cbb --- /dev/null +++ b/scripts/kconfig/lex.zconf.c_shipped @@ -0,0 +1,2317 @@ + +#line 3 "scripts/kconfig/lex.zconf.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 31 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE zconfrestart(zconfin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +extern int zconfleng; + +extern FILE *zconfin, *zconfout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up zconftext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up zconftext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, (yytext_ptr) ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef unsigned int yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via zconfrestart()), so that the user can continue scanning by + * just pointing zconfin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when zconftext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int zconfleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 1; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow zconfwrap()'s to do buffer switches + * instead of setting up a fresh zconfin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void zconfrestart (FILE *input_file ); +void zconf_switch_to_buffer (YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE zconf_create_buffer (FILE *file,int size ); +void zconf_delete_buffer (YY_BUFFER_STATE b ); +void zconf_flush_buffer (YY_BUFFER_STATE b ); +void zconfpush_buffer_state (YY_BUFFER_STATE new_buffer ); +void zconfpop_buffer_state (void ); + +static void zconfensure_buffer_stack (void ); +static void zconf_load_buffer_state (void ); +static void zconf_init_buffer (YY_BUFFER_STATE b,FILE *file ); + +#define YY_FLUSH_BUFFER zconf_flush_buffer(YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE zconf_scan_buffer (char *base,yy_size_t size ); +YY_BUFFER_STATE zconf_scan_string (yyconst char *yy_str ); +YY_BUFFER_STATE zconf_scan_bytes (yyconst char *bytes,int len ); + +void *zconfalloc (yy_size_t ); +void *zconfrealloc (void *,yy_size_t ); +void zconffree (void * ); + +#define yy_new_buffer zconf_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + zconfensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + zconf_create_buffer(zconfin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + zconfensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + zconf_create_buffer(zconfin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define zconfwrap() 1 +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +FILE *zconfin = (FILE *) 0, *zconfout = (FILE *) 0; + +typedef int yy_state_type; + +extern int zconflineno; + +int zconflineno = 1; + +extern char *zconftext; +#define yytext_ptr zconftext +static yyconst flex_int16_t yy_nxt[][17] = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + }, + + { + 11, 12, 13, 14, 12, 12, 15, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12 + }, + + { + 11, 12, 13, 14, 12, 12, 15, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12 + }, + + { + 11, 16, 16, 17, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 18, 16, 16, 16 + }, + + { + 11, 16, 16, 17, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 18, 16, 16, 16 + + }, + + { + 11, 19, 20, 21, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19 + }, + + { + 11, 19, 20, 21, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19 + }, + + { + 11, 22, 22, 23, 22, 24, 22, 22, 24, 22, + 22, 22, 22, 22, 22, 25, 22 + }, + + { + 11, 22, 22, 23, 22, 24, 22, 22, 24, 22, + 22, 22, 22, 22, 22, 25, 22 + }, + + { + 11, 26, 26, 27, 28, 29, 30, 31, 29, 32, + 33, 34, 35, 35, 36, 37, 38 + + }, + + { + 11, 26, 26, 27, 28, 29, 30, 31, 29, 32, + 33, 34, 35, 35, 36, 37, 38 + }, + + { + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11 + }, + + { + 11, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12 + }, + + { + 11, -13, 39, 40, -13, -13, 41, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13 + }, + + { + 11, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14 + + }, + + { + 11, 42, 42, 43, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42 + }, + + { + 11, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16 + }, + + { + 11, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17 + }, + + { + 11, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, 44, -18, -18, -18 + }, + + { + 11, 45, 45, -19, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45 + + }, + + { + 11, -20, 46, 47, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20 + }, + + { + 11, 48, -21, -21, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48 + }, + + { + 11, 49, 49, 50, 49, -22, 49, 49, -22, 49, + 49, 49, 49, 49, 49, -22, 49 + }, + + { + 11, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23 + }, + + { + 11, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24 + + }, + + { + 11, 51, 51, 52, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51 + }, + + { + 11, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26 + }, + + { + 11, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27 + }, + + { + 11, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, 53, -28, -28 + }, + + { + 11, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29 + + }, + + { + 11, 54, 54, -30, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54 + }, + + { + 11, -31, -31, -31, -31, -31, -31, 55, -31, -31, + -31, -31, -31, -31, -31, -31, -31 + }, + + { + 11, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32 + }, + + { + 11, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33 + }, + + { + 11, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, 56, 57, 57, -34, -34, -34 + + }, + + { + 11, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, 57, 57, 57, -35, -35, -35 + }, + + { + 11, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36 + }, + + { + 11, -37, -37, 58, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37 + }, + + { + 11, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, 59 + }, + + { + 11, -39, 39, 40, -39, -39, 41, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39 + + }, + + { + 11, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40 + }, + + { + 11, 42, 42, 43, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42 + }, + + { + 11, 42, 42, 43, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42 + }, + + { + 11, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43 + }, + + { + 11, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, 44, -44, -44, -44 + + }, + + { + 11, 45, 45, -45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45 + }, + + { + 11, -46, 46, 47, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46 + }, + + { + 11, 48, -47, -47, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48 + }, + + { + 11, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48 + }, + + { + 11, 49, 49, 50, 49, -49, 49, 49, -49, 49, + 49, 49, 49, 49, 49, -49, 49 + + }, + + { + 11, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50 + }, + + { + 11, -51, -51, 52, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51 + }, + + { + 11, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52 + }, + + { + 11, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53 + }, + + { + 11, 54, 54, -54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54 + + }, + + { + 11, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55 + }, + + { + 11, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, 60, 57, 57, -56, -56, -56 + }, + + { + 11, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, 57, 57, 57, -57, -57, -57 + }, + + { + 11, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58 + }, + + { + 11, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59 + + }, + + { + 11, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, 57, 57, 57, -60, -60, -60 + }, + + } ; + +static yy_state_type yy_get_previous_state (void ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); +static int yy_get_next_buffer (void ); +static void yy_fatal_error (yyconst char msg[] ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up zconftext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + zconfleng = (size_t) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; + +#define YY_NUM_RULES 33 +#define YY_END_OF_BUFFER 34 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[61] = + { 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 34, 5, 4, 2, 3, 7, 8, 6, 32, 29, + 31, 24, 28, 27, 26, 22, 17, 13, 16, 20, + 22, 11, 12, 19, 19, 14, 22, 22, 4, 2, + 3, 3, 1, 6, 32, 29, 31, 30, 24, 23, + 26, 25, 15, 20, 9, 19, 19, 21, 10, 18 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 4, 5, 6, 1, 1, 7, 8, 9, + 10, 1, 1, 1, 11, 12, 12, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 1, 1, 1, + 14, 1, 1, 1, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 1, 15, 1, 1, 13, 1, 13, 13, 13, 13, + + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 1, 16, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +extern int zconf_flex_debug; +int zconf_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +char *zconftext; + +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include + +#define LKC_DIRECT_LINK +#include "lkc.h" + +#define START_STRSIZE 16 + +static struct { + struct file *file; + int lineno; +} current_pos; + +static char *text; +static int text_size, text_asize; + +struct buffer { + struct buffer *parent; + YY_BUFFER_STATE state; +}; + +struct buffer *current_buf; + +static int last_ts, first_ts; + +static void zconf_endhelp(void); +static void zconf_endfile(void); + +void new_string(void) +{ + text = malloc(START_STRSIZE); + text_asize = START_STRSIZE; + text_size = 0; + *text = 0; +} + +void append_string(const char *str, int size) +{ + int new_size = text_size + size + 1; + if (new_size > text_asize) { + new_size += START_STRSIZE - 1; + new_size &= -START_STRSIZE; + text = realloc(text, new_size); + text_asize = new_size; + } + memcpy(text + text_size, str, size); + text_size += size; + text[text_size] = 0; +} + +void alloc_string(const char *str, int size) +{ + text = malloc(size + 1); + memcpy(text, str, size); + text[size] = 0; +} + +#define INITIAL 0 +#define COMMAND 1 +#define HELP 2 +#define STRING 3 +#define PARAM 4 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int zconfwrap (void ); +#else +extern int zconfwrap (void ); +#endif +#endif + + static void yyunput (int c,char *buf_ptr ); + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (void ); +#else +static int input (void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO (void) fwrite( zconftext, zconfleng, 1, zconfout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + errno=0; \ + while ( (result = read( fileno(zconfin), (char *) buf, max_size )) < 0 ) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(zconfin); \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int zconflex (void); + +#define YY_DECL int zconflex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after zconftext and zconfleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + + int str = 0; + int ts, i; + + if ( (yy_init) ) + { + (yy_init) = 0; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! zconfin ) + zconfin = stdin; + + if ( ! zconfout ) + zconfout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + zconfensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + zconf_create_buffer(zconfin,YY_BUF_SIZE ); + } + + zconf_load_buffer_state( ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of zconftext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); +yy_match: + while ( (yy_current_state = yy_nxt[yy_current_state][ yy_ec[YY_SC_TO_UI(*yy_cp)] ]) > 0 ) + ++yy_cp; + + yy_current_state = -yy_current_state; + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ +case 1: +/* rule 1 can match eol */ +case 2: +/* rule 2 can match eol */ +YY_RULE_SETUP +{ + current_file->lineno++; + return T_EOL; +} + YY_BREAK +case 3: +YY_RULE_SETUP + + YY_BREAK +case 4: +YY_RULE_SETUP +{ + BEGIN(COMMAND); +} + YY_BREAK +case 5: +YY_RULE_SETUP +{ + unput(zconftext[0]); + BEGIN(COMMAND); +} + YY_BREAK + +case 6: +YY_RULE_SETUP +{ + struct kconf_id *id = kconf_id_lookup(zconftext, zconfleng); + BEGIN(PARAM); + current_pos.file = current_file; + current_pos.lineno = current_file->lineno; + if (id && id->flags & TF_COMMAND) { + zconflval.id = id; + return id->token; + } + alloc_string(zconftext, zconfleng); + zconflval.string = text; + return T_WORD; + } + YY_BREAK +case 7: +YY_RULE_SETUP + + YY_BREAK +case 8: +/* rule 8 can match eol */ +YY_RULE_SETUP +{ + BEGIN(INITIAL); + current_file->lineno++; + return T_EOL; + } + YY_BREAK + +case 9: +YY_RULE_SETUP +return T_AND; + YY_BREAK +case 10: +YY_RULE_SETUP +return T_OR; + YY_BREAK +case 11: +YY_RULE_SETUP +return T_OPEN_PAREN; + YY_BREAK +case 12: +YY_RULE_SETUP +return T_CLOSE_PAREN; + YY_BREAK +case 13: +YY_RULE_SETUP +return T_NOT; + YY_BREAK +case 14: +YY_RULE_SETUP +return T_EQUAL; + YY_BREAK +case 15: +YY_RULE_SETUP +return T_UNEQUAL; + YY_BREAK +case 16: +YY_RULE_SETUP +{ + str = zconftext[0]; + new_string(); + BEGIN(STRING); + } + YY_BREAK +case 17: +/* rule 17 can match eol */ +YY_RULE_SETUP +BEGIN(INITIAL); current_file->lineno++; return T_EOL; + YY_BREAK +case 18: +YY_RULE_SETUP +/* ignore */ + YY_BREAK +case 19: +YY_RULE_SETUP +{ + struct kconf_id *id = kconf_id_lookup(zconftext, zconfleng); + if (id && id->flags & TF_PARAM) { + zconflval.id = id; + return id->token; + } + alloc_string(zconftext, zconfleng); + zconflval.string = text; + return T_WORD; + } + YY_BREAK +case 20: +YY_RULE_SETUP +/* comment */ + YY_BREAK +case 21: +/* rule 21 can match eol */ +YY_RULE_SETUP +current_file->lineno++; + YY_BREAK +case 22: +YY_RULE_SETUP + + YY_BREAK +case YY_STATE_EOF(PARAM): +{ + BEGIN(INITIAL); + } + YY_BREAK + +case 23: +/* rule 23 can match eol */ +*yy_cp = (yy_hold_char); /* undo effects of setting up zconftext */ +(yy_c_buf_p) = yy_cp -= 1; +YY_DO_BEFORE_ACTION; /* set up zconftext again */ +YY_RULE_SETUP +{ + append_string(zconftext, zconfleng); + zconflval.string = text; + return T_WORD_QUOTE; + } + YY_BREAK +case 24: +YY_RULE_SETUP +{ + append_string(zconftext, zconfleng); + } + YY_BREAK +case 25: +/* rule 25 can match eol */ +*yy_cp = (yy_hold_char); /* undo effects of setting up zconftext */ +(yy_c_buf_p) = yy_cp -= 1; +YY_DO_BEFORE_ACTION; /* set up zconftext again */ +YY_RULE_SETUP +{ + append_string(zconftext + 1, zconfleng - 1); + zconflval.string = text; + return T_WORD_QUOTE; + } + YY_BREAK +case 26: +YY_RULE_SETUP +{ + append_string(zconftext + 1, zconfleng - 1); + } + YY_BREAK +case 27: +YY_RULE_SETUP +{ + if (str == zconftext[0]) { + BEGIN(PARAM); + zconflval.string = text; + return T_WORD_QUOTE; + } else + append_string(zconftext, 1); + } + YY_BREAK +case 28: +/* rule 28 can match eol */ +YY_RULE_SETUP +{ + printf("%s:%d:warning: multi-line strings not supported\n", zconf_curname(), zconf_lineno()); + current_file->lineno++; + BEGIN(INITIAL); + return T_EOL; + } + YY_BREAK +case YY_STATE_EOF(STRING): +{ + BEGIN(INITIAL); + } + YY_BREAK + +case 29: +YY_RULE_SETUP +{ + ts = 0; + for (i = 0; i < zconfleng; i++) { + if (zconftext[i] == '\t') + ts = (ts & ~7) + 8; + else + ts++; + } + last_ts = ts; + if (first_ts) { + if (ts < first_ts) { + zconf_endhelp(); + return T_HELPTEXT; + } + ts -= first_ts; + while (ts > 8) { + append_string(" ", 8); + ts -= 8; + } + append_string(" ", ts); + } + } + YY_BREAK +case 30: +/* rule 30 can match eol */ +*yy_cp = (yy_hold_char); /* undo effects of setting up zconftext */ +(yy_c_buf_p) = yy_cp -= 1; +YY_DO_BEFORE_ACTION; /* set up zconftext again */ +YY_RULE_SETUP +{ + current_file->lineno++; + zconf_endhelp(); + return T_HELPTEXT; + } + YY_BREAK +case 31: +/* rule 31 can match eol */ +YY_RULE_SETUP +{ + current_file->lineno++; + append_string("\n", 1); + } + YY_BREAK +case 32: +YY_RULE_SETUP +{ + append_string(zconftext, zconfleng); + if (!first_ts) + first_ts = last_ts; + } + YY_BREAK +case YY_STATE_EOF(HELP): +{ + zconf_endhelp(); + return T_HELPTEXT; + } + YY_BREAK + +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(COMMAND): +{ + if (current_file) { + zconf_endfile(); + return T_EOL; + } + fclose(zconfin); + yyterminate(); +} + YY_BREAK +case 33: +YY_RULE_SETUP +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed zconfin at a new source and called + * zconflex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = zconfin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_c_buf_p); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( zconfwrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * zconftext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of zconflex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = (yytext_ptr); + register int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + size_t num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + zconfrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + zconfrestart(zconfin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + yy_current_state = yy_nxt[yy_current_state][(*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1)]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + register int yy_is_jam; + + yy_current_state = yy_nxt[yy_current_state][1]; + yy_is_jam = (yy_current_state <= 0); + + return yy_is_jam ? 0 : yy_current_state; +} + + static void yyunput (int c, register char * yy_bp ) +{ + register char *yy_cp; + + yy_cp = (yy_c_buf_p); + + /* undo effects of setting up zconftext */ + *yy_cp = (yy_hold_char); + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = (yy_n_chars) + 2; + register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + register char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + (yytext_ptr) = yy_bp; + (yy_hold_char) = *yy_cp; + (yy_c_buf_p) = yy_cp; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (yy_c_buf_p) - (yytext_ptr); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + zconfrestart(zconfin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( zconfwrap( ) ) + return EOF; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve zconftext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void zconfrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + zconfensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + zconf_create_buffer(zconfin,YY_BUF_SIZE ); + } + + zconf_init_buffer(YY_CURRENT_BUFFER,input_file ); + zconf_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void zconf_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * zconfpop_buffer_state(); + * zconfpush_buffer_state(new_buffer); + */ + zconfensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + zconf_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (zconfwrap()) processing, but the only time this flag + * is looked at is after zconfwrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void zconf_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + zconfin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE zconf_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) zconfalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in zconf_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) zconfalloc(b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in zconf_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + zconf_init_buffer(b,file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with zconf_create_buffer() + * + */ + void zconf_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + zconffree((void *) b->yy_ch_buf ); + + zconffree((void *) b ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a zconfrestart() or at EOF. + */ + static void zconf_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + zconf_flush_buffer(b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then zconf_init_buffer was _probably_ + * called from zconfrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void zconf_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + zconf_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void zconfpush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + zconfensure_buffer_stack(); + + /* This block is copied from zconf_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from zconf_switch_to_buffer. */ + zconf_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void zconfpop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + zconf_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + zconf_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void zconfensure_buffer_stack (void) +{ + int num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + (yy_buffer_stack) = (struct yy_buffer_state**)zconfalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)zconfrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE zconf_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) zconfalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in zconf_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + zconf_switch_to_buffer(b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to zconflex() will + * scan from a @e copy of @a str. + * @param yy_str a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * zconf_scan_bytes() instead. + */ +YY_BUFFER_STATE zconf_scan_string (yyconst char * yy_str ) +{ + + return zconf_scan_bytes(yy_str,strlen(yy_str) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to zconflex() will + * scan from a @e copy of @a bytes. + * @param bytes the byte buffer to scan + * @param len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE zconf_scan_bytes (yyconst char * bytes, int len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = len + 2; + buf = (char *) zconfalloc(n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in zconf_scan_bytes()" ); + + for ( i = 0; i < len; ++i ) + buf[i] = bytes[i]; + + buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR; + + b = zconf_scan_buffer(buf,n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in zconf_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg ) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up zconftext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + zconftext[zconfleng] = (yy_hold_char); \ + (yy_c_buf_p) = zconftext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + zconfleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +int zconfget_lineno (void) +{ + + return zconflineno; +} + +/** Get the input stream. + * + */ +FILE *zconfget_in (void) +{ + return zconfin; +} + +/** Get the output stream. + * + */ +FILE *zconfget_out (void) +{ + return zconfout; +} + +/** Get the length of the current token. + * + */ +int zconfget_leng (void) +{ + return zconfleng; +} + +/** Get the current token. + * + */ + +char *zconfget_text (void) +{ + return zconftext; +} + +/** Set the current line number. + * @param line_number + * + */ +void zconfset_lineno (int line_number ) +{ + + zconflineno = line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * + * @see zconf_switch_to_buffer + */ +void zconfset_in (FILE * in_str ) +{ + zconfin = in_str ; +} + +void zconfset_out (FILE * out_str ) +{ + zconfout = out_str ; +} + +int zconfget_debug (void) +{ + return zconf_flex_debug; +} + +void zconfset_debug (int bdebug ) +{ + zconf_flex_debug = bdebug ; +} + +/* zconflex_destroy is for both reentrant and non-reentrant scanners. */ +int zconflex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + zconf_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + zconfpop_buffer_state(); + } + + /* Destroy the stack itself. */ + zconffree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s ) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *zconfalloc (yy_size_t size ) +{ + return (void *) malloc( size ); +} + +void *zconfrealloc (void * ptr, yy_size_t size ) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void zconffree (void * ptr ) +{ + free( (char *) ptr ); /* see zconfrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#undef YY_NEW_FILE +#undef YY_FLUSH_BUFFER +#undef yy_set_bol +#undef yy_new_buffer +#undef yy_set_interactive +#undef yytext_ptr +#undef YY_DO_BEFORE_ACTION + +#ifdef YY_DECL_IS_OURS +#undef YY_DECL_IS_OURS +#undef YY_DECL +#endif + +void zconf_starthelp(void) +{ + new_string(); + last_ts = first_ts = 0; + BEGIN(HELP); +} + +static void zconf_endhelp(void) +{ + zconflval.string = text; + BEGIN(INITIAL); +} + +/* + * Try to open specified file with following names: + * ./name + * $(srctree)/name + * The latter is used when srctree is separate from objtree + * when compiling the kernel. + * Return NULL if file is not found. + */ +FILE *zconf_fopen(const char *name) +{ + char *env, fullname[PATH_MAX+1]; + FILE *f; + + f = fopen(name, "r"); + if (!f && name[0] != '/') { + env = getenv(SRCTREE); + if (env) { + sprintf(fullname, "%s/%s", env, name); + f = fopen(fullname, "r"); + } + } + return f; +} + +void zconf_initscan(const char *name) +{ + zconfin = zconf_fopen(name); + if (!zconfin) { + printf("can't find file %s\n", name); + exit(1); + } + + current_buf = malloc(sizeof(*current_buf)); + memset(current_buf, 0, sizeof(*current_buf)); + + current_file = file_lookup(name); + current_file->lineno = 1; + current_file->flags = FILE_BUSY; +} + +void zconf_nextfile(const char *name) +{ + struct file *file = file_lookup(name); + struct buffer *buf = malloc(sizeof(*buf)); + memset(buf, 0, sizeof(*buf)); + + current_buf->state = YY_CURRENT_BUFFER; + zconfin = zconf_fopen(name); + if (!zconfin) { + printf("%s:%d: can't open file \"%s\"\n", zconf_curname(), zconf_lineno(), name); + exit(1); + } + zconf_switch_to_buffer(zconf_create_buffer(zconfin,YY_BUF_SIZE)); + buf->parent = current_buf; + current_buf = buf; + + if (file->flags & FILE_BUSY) { + printf("recursive scan (%s)?\n", name); + exit(1); + } + if (file->flags & FILE_SCANNED) { + printf("file %s already scanned?\n", name); + exit(1); + } + file->flags |= FILE_BUSY; + file->lineno = 1; + file->parent = current_file; + current_file = file; +} + +static void zconf_endfile(void) +{ + struct buffer *parent; + + current_file->flags |= FILE_SCANNED; + current_file->flags &= ~FILE_BUSY; + current_file = current_file->parent; + + parent = current_buf->parent; + if (parent) { + fclose(zconfin); + zconf_delete_buffer(YY_CURRENT_BUFFER); + zconf_switch_to_buffer(parent->state); + } + free(current_buf); + current_buf = parent; +} + +int zconf_lineno(void) +{ + return current_pos.lineno; +} + +char *zconf_curname(void) +{ + return current_pos.file ? current_pos.file->name : ""; +} + diff --git a/scripts/kconfig/lkc.h b/scripts/kconfig/lkc.h new file mode 100644 index 000000000..527f60c99 --- /dev/null +++ b/scripts/kconfig/lkc.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef LKC_H +#define LKC_H + +#include "expr.h" + +#ifndef KBUILD_NO_NLS +# include +#else +# define gettext(Msgid) ((const char *) (Msgid)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef LKC_DIRECT_LINK +#define P(name,type,arg) extern type name arg +#else +#include "lkc_defs.h" +#define P(name,type,arg) extern type (*name ## _p) arg +#endif +#include "lkc_proto.h" +#undef P + +#define SRCTREE "srctree" + +#define PACKAGE "linux" +#define LOCALEDIR "/usr/share/locale" + +#define _(text) gettext(text) +#define N_(text) (text) + + +#define TF_COMMAND 0x0001 +#define TF_PARAM 0x0002 + +struct kconf_id { + int name; + int token; + unsigned int flags; + enum symbol_type stype; +}; + +int zconfparse(void); +void zconfdump(FILE *out); + +extern int zconfdebug; +void zconf_starthelp(void); +FILE *zconf_fopen(const char *name); +void zconf_initscan(const char *name); +void zconf_nextfile(const char *name); +int zconf_lineno(void); +char *zconf_curname(void); + +/* confdata.c */ +extern const char conf_def_filename[]; + +char *conf_get_default_confname(void); + +/* kconfig_load.c */ +void kconfig_load(void); + +/* menu.c */ +void menu_init(void); +struct menu *menu_add_menu(void); +void menu_end_menu(void); +void menu_add_entry(struct symbol *sym); +void menu_end_entry(void); +void menu_add_dep(struct expr *dep); +struct property *menu_add_prop(enum prop_type type, char *prompt, struct expr *expr, struct expr *dep); +struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep); +void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep); +void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep); +void menu_finalize(struct menu *parent); +void menu_set_type(int type); + +/* util.c */ +struct file *file_lookup(const char *name); +int file_write_dep(const char *name); + +struct gstr { + size_t len; + char *s; +}; +struct gstr str_new(void); +struct gstr str_assign(const char *s); +void str_free(struct gstr *gs); +void str_append(struct gstr *gs, const char *s); +void str_printf(struct gstr *gs, const char *fmt, ...); +const char *str_get(struct gstr *gs); + +/* symbol.c */ +void sym_init(void); +void sym_clear_all_valid(void); +void sym_set_changed(struct symbol *sym); +struct symbol *sym_check_deps(struct symbol *sym); +struct property *prop_alloc(enum prop_type type, struct symbol *sym); +struct symbol *prop_get_symbol(struct property *prop); + +static inline tristate sym_get_tristate_value(struct symbol *sym) +{ + return sym->curr.tri; +} + + +static inline struct symbol *sym_get_choice_value(struct symbol *sym) +{ + return (struct symbol *)sym->curr.val; +} + +static inline bool sym_set_choice_value(struct symbol *ch, struct symbol *chval) +{ + return sym_set_tristate_value(chval, yes); +} + +static inline bool sym_is_choice(struct symbol *sym) +{ + return sym->flags & SYMBOL_CHOICE ? true : false; +} + +static inline bool sym_is_choice_value(struct symbol *sym) +{ + return sym->flags & SYMBOL_CHOICEVAL ? true : false; +} + +static inline bool sym_is_optional(struct symbol *sym) +{ + return sym->flags & SYMBOL_OPTIONAL ? true : false; +} + +static inline bool sym_has_value(struct symbol *sym) +{ + return sym->flags & SYMBOL_NEW ? false : true; +} + +#ifdef __cplusplus +} +#endif + +#endif /* LKC_H */ diff --git a/scripts/kconfig/lkc_proto.h b/scripts/kconfig/lkc_proto.h new file mode 100644 index 000000000..b6a389c5f --- /dev/null +++ b/scripts/kconfig/lkc_proto.h @@ -0,0 +1,41 @@ + +/* confdata.c */ +P(conf_parse,void,(const char *name)); +P(conf_read,int,(const char *name)); +P(conf_read_simple,int,(const char *name)); +P(conf_write,int,(const char *name)); + +/* menu.c */ +P(rootmenu,struct menu,); + +P(menu_is_visible,bool,(struct menu *menu)); +P(menu_get_prompt,const char *,(struct menu *menu)); +P(menu_get_root_menu,struct menu *,(struct menu *menu)); +P(menu_get_parent_menu,struct menu *,(struct menu *menu)); + +/* symbol.c */ +P(symbol_hash,struct symbol *,[SYMBOL_HASHSIZE]); +P(sym_change_count,int,); + +P(sym_lookup,struct symbol *,(const char *name, int isconst)); +P(sym_find,struct symbol *,(const char *name)); +P(sym_re_search,struct symbol **,(const char *pattern)); +P(sym_type_name,const char *,(enum symbol_type type)); +P(sym_calc_value,void,(struct symbol *sym)); +P(sym_get_type,enum symbol_type,(struct symbol *sym)); +P(sym_tristate_within_range,bool,(struct symbol *sym,tristate tri)); +P(sym_set_tristate_value,bool,(struct symbol *sym,tristate tri)); +P(sym_toggle_tristate_value,tristate,(struct symbol *sym)); +P(sym_string_valid,bool,(struct symbol *sym, const char *newval)); +P(sym_string_within_range,bool,(struct symbol *sym, const char *str)); +P(sym_set_string_value,bool,(struct symbol *sym, const char *newval)); +P(sym_is_changable,bool,(struct symbol *sym)); +P(sym_get_choice_prop,struct property *,(struct symbol *sym)); +P(sym_get_default_prop,struct property *,(struct symbol *sym)); +P(sym_get_string_value,const char *,(struct symbol *sym)); + +P(prop_get_type_name,const char *,(enum prop_type type)); + +/* expr.c */ +P(expr_compare_type,int,(enum expr_type t1, enum expr_type t2)); +P(expr_print,void,(struct expr *e, void (*fn)(void *, const char *), void *data, int prevtoken)); diff --git a/scripts/kconfig/lxdialog/BIG.FAT.WARNING b/scripts/kconfig/lxdialog/BIG.FAT.WARNING new file mode 100644 index 000000000..c9bfbb902 --- /dev/null +++ b/scripts/kconfig/lxdialog/BIG.FAT.WARNING @@ -0,0 +1,4 @@ +This is NOT the official version of dialog. This version has been +significantly modified from the original. It is for use by the Linux +busybox configuration script. Please do not bother Savio Lam with +questions about this program. diff --git a/scripts/kconfig/lxdialog/Makefile b/scripts/kconfig/lxdialog/Makefile new file mode 100644 index 000000000..a8b026326 --- /dev/null +++ b/scripts/kconfig/lxdialog/Makefile @@ -0,0 +1,21 @@ +# Makefile to build lxdialog package +# + +check-lxdialog := $(srctree)/$(src)/check-lxdialog.sh + +# Use reursively expanded variables so we do not call gcc unless +# we really need to do so. (Do not call gcc as part of make mrproper) +HOST_EXTRACFLAGS = $(shell $(CONFIG_SHELL) $(check-lxdialog) -ccflags) +HOST_LOADLIBES = $(shell $(CONFIG_SHELL) $(check-lxdialog) -ldflags $(HOSTCC)) + +HOST_EXTRACFLAGS += -DLOCALE + +PHONY += dochecklxdialog +$(obj)/dochecklxdialog: + $(Q)$(CONFIG_SHELL) $(check-lxdialog) -check $(HOSTCC) $(HOST_LOADLIBES) + +hostprogs-y := lxdialog +always := $(hostprogs-y) dochecklxdialog + +lxdialog-objs := checklist.o menubox.o textbox.o yesno.o inputbox.o \ + util.o lxdialog.o msgbox.o diff --git a/scripts/kconfig/lxdialog/check-lxdialog.sh b/scripts/kconfig/lxdialog/check-lxdialog.sh new file mode 100644 index 000000000..120d624e6 --- /dev/null +++ b/scripts/kconfig/lxdialog/check-lxdialog.sh @@ -0,0 +1,84 @@ +#!/bin/sh +# Check ncurses compatibility + +# What library to link +ldflags() +{ + $cc -print-file-name=libncursesw.so | grep -q / + if [ $? -eq 0 ]; then + echo '-lncursesw' + exit + fi + $cc -print-file-name=libncurses.so | grep -q / + if [ $? -eq 0 ]; then + echo '-lncurses' + exit + fi + $cc -print-file-name=libcurses.so | grep -q / + if [ $? -eq 0 ]; then + echo '-lcurses' + exit + fi + exit 1 +} + +# Where is ncurses.h? +ccflags() +{ + if [ -f /usr/include/ncurses/ncurses.h ]; then + echo '-I/usr/include/ncurses -DCURSES_LOC=""' + elif [ -f /usr/include/ncurses/curses.h ]; then + echo '-I/usr/include/ncurses -DCURSES_LOC=""' + elif [ -f /usr/include/ncurses.h ]; then + echo '-DCURSES_LOC=""' + else + echo '-DCURSES_LOC=""' + fi +} + +# Temp file, try to clean up after us +tmp=.lxdialog.tmp +trap "rm -f $tmp" 0 1 2 3 15 + +# Check if we can link to ncurses +check() { + echo "main() {}" | $cc -xc - -o $tmp 2> /dev/null + if [ $? != 0 ]; then + echo " *** Unable to find the ncurses libraries." 1>&2 + echo " *** make menuconfig require the ncurses libraries" 1>&2 + echo " *** " 1>&2 + echo " *** Install ncurses (ncurses-devel) and try again" 1>&2 + echo " *** " 1>&2 + exit 1 + fi +} + +usage() { + printf "Usage: $0 [-check compiler options|-header|-library]\n" +} + +if [ $# == 0 ]; then + usage + exit 1 +fi + +cc="" +case "$1" in + "-check") + shift + cc="$@" + check + ;; + "-ccflags") + ccflags + ;; + "-ldflags") + shift + cc="$@" + ldflags + ;; + "*") + usage + exit 1 + ;; +esac diff --git a/scripts/kconfig/lxdialog/checklist.c b/scripts/kconfig/lxdialog/checklist.c new file mode 100644 index 000000000..be0200e9c --- /dev/null +++ b/scripts/kconfig/lxdialog/checklist.c @@ -0,0 +1,333 @@ +/* + * checklist.c -- implements the checklist box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * Stuart Herbert - S.Herbert@sheffield.ac.uk: radiolist extension + * Alessandro Rubini - rubini@ipvvis.unipv.it: merged the two + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" + +static int list_width, check_x, item_x; + +/* + * Print list item + */ +static void print_item(WINDOW * win, const char *item, int status, int choice, + int selected) +{ + int i; + + /* Clear 'residue' of last item */ + wattrset(win, menubox_attr); + wmove(win, choice, 0); + for (i = 0; i < list_width; i++) + waddch(win, ' '); + + wmove(win, choice, check_x); + wattrset(win, selected ? check_selected_attr : check_attr); + wprintw(win, "(%c)", status ? 'X' : ' '); + + wattrset(win, selected ? tag_selected_attr : tag_attr); + mvwaddch(win, choice, item_x, item[0]); + wattrset(win, selected ? item_selected_attr : item_attr); + waddstr(win, (char *)item + 1); + if (selected) { + wmove(win, choice, check_x + 1); + wrefresh(win); + } +} + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int choice, int item_no, int scroll, + int y, int x, int height) +{ + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, uarrow_attr); + waddch(win, ACS_UARROW); + waddstr(win, "(-)"); + } else { + wattrset(win, menubox_attr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + + if ((height < item_no) && (scroll + choice < item_no - 1)) { + wattrset(win, darrow_attr); + waddch(win, ACS_DARROW); + waddstr(win, "(+)"); + } else { + wattrset(win, menubox_border_attr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } +} + +/* + * Display the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 11; + int y = height - 2; + + print_button(dialog, "Select", y, x, selected == 0); + print_button(dialog, " Help ", y, x + 14, selected == 1); + + wmove(dialog, y, x + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with a list of options that can be turned on or off + * in the style of radiolist (only one option turned on at a time). + */ +int dialog_checklist(const char *title, const char *prompt, int height, + int width, int list_height, int item_no, + const char *const *items) +{ + int i, x, y, box_x, box_y; + int key = 0, button = 0, choice = 0, scroll = 0, max_choice, *status; + WINDOW *dialog, *list; + + /* Allocate space for storing item on/off status */ + if ((status = malloc(sizeof(int) * item_no)) == NULL) { + endwin(); + fprintf(stderr, + "\nCan't allocate memory in dialog_checklist().\n"); + exit(-1); + } + + /* Initializes status */ + for (i = 0; i < item_no; i++) { + status[i] = !strcasecmp(items[i * 3 + 2], "on"); + if ((!choice && status[i]) + || !strcasecmp(items[i * 3 + 2], "selected")) + choice = i + 1; + } + if (choice) + choice--; + + max_choice = MIN(list_height, item_no); + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dialog_attr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + list_width = width - 6; + box_y = height - list_height - 5; + box_x = (width - list_width) / 2 - 1; + + /* create new window for the list */ + list = subwin(dialog, list_height, list_width, y + box_y + 1, + x + box_x + 1); + + keypad(list, TRUE); + + /* draw a box around the list items */ + draw_box(dialog, box_y, box_x, list_height + 2, list_width + 2, + menubox_border_attr, menubox_attr); + + /* Find length of longest item in order to center checklist */ + check_x = 0; + for (i = 0; i < item_no; i++) + check_x = MAX(check_x, +strlen(items[i * 3 + 1]) + 4); + + check_x = (list_width - check_x) / 2; + item_x = check_x + 4; + + if (choice >= list_height) { + scroll = choice - list_height + 1; + choice -= scroll; + } + + /* Print the list */ + for (i = 0; i < max_choice; i++) { + print_item(list, items[(scroll + i) * 3 + 1], + status[i + scroll], i, i == choice); + } + + print_arrows(dialog, choice, item_no, scroll, + box_y, box_x + check_x + 5, list_height); + + print_buttons(dialog, height, width, 0); + + wnoutrefresh(dialog); + wnoutrefresh(list); + doupdate(); + + while (key != ESC) { + key = wgetch(dialog); + + for (i = 0; i < max_choice; i++) + if (toupper(key) == + toupper(items[(scroll + i) * 3 + 1][0])) + break; + + if (i < max_choice || key == KEY_UP || key == KEY_DOWN || + key == '+' || key == '-') { + if (key == KEY_UP || key == '-') { + if (!choice) { + if (!scroll) + continue; + /* Scroll list down */ + if (list_height > 1) { + /* De-highlight current first item */ + print_item(list, items[scroll * 3 + 1], + status[scroll], 0, FALSE); + scrollok(list, TRUE); + wscrl(list, -1); + scrollok(list, FALSE); + } + scroll--; + print_item(list, items[scroll * 3 + 1], status[scroll], 0, TRUE); + print_arrows(dialog, choice, item_no, + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice - 1; + } else if (key == KEY_DOWN || key == '+') { + if (choice == max_choice - 1) { + if (scroll + choice >= item_no - 1) + continue; + /* Scroll list up */ + if (list_height > 1) { + /* De-highlight current last item before scrolling up */ + print_item(list, items[(scroll + max_choice - 1) * 3 + 1], + status[scroll + max_choice - 1], + max_choice - 1, FALSE); + scrollok(list, TRUE); + wscrl(list, 1); + scrollok(list, FALSE); + } + scroll++; + print_item(list, items[(scroll + max_choice - 1) * 3 + 1], + status[scroll + max_choice - 1], max_choice - 1, TRUE); + + print_arrows(dialog, choice, item_no, + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice + 1; + } + if (i != choice) { + /* De-highlight current item */ + print_item(list, items[(scroll + choice) * 3 + 1], + status[scroll + choice], choice, FALSE); + /* Highlight new item */ + choice = i; + print_item(list, items[(scroll + choice) * 3 + 1], + status[scroll + choice], choice, TRUE); + wnoutrefresh(dialog); + wrefresh(list); + } + continue; /* wait for another key press */ + } + switch (key) { + case 'H': + case 'h': + case '?': + fprintf(stderr, "%s", items[(scroll + choice) * 3]); + delwin(dialog); + free(status); + return 1; + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? 1 : (button > 1 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(dialog); + break; + case 'S': + case 's': + case ' ': + case '\n': + if (!button) { + if (!status[scroll + choice]) { + for (i = 0; i < item_no; i++) + status[i] = 0; + status[scroll + choice] = 1; + for (i = 0; i < max_choice; i++) + print_item(list, items[(scroll + i) * 3 + 1], + status[scroll + i], i, i == choice); + } + wnoutrefresh(dialog); + wrefresh(list); + + for (i = 0; i < item_no; i++) + if (status[i]) + fprintf(stderr, "%s", items[i * 3]); + } else + fprintf(stderr, "%s", items[(scroll + choice) * 3]); + delwin(dialog); + free(status); + return button; + case 'X': + case 'x': + key = ESC; + case ESC: + break; + } + + /* Now, update everything... */ + doupdate(); + } + + delwin(dialog); + free(status); + return -1; /* ESC pressed */ +} diff --git a/scripts/kconfig/lxdialog/colors.h b/scripts/kconfig/lxdialog/colors.h new file mode 100644 index 000000000..db071df12 --- /dev/null +++ b/scripts/kconfig/lxdialog/colors.h @@ -0,0 +1,154 @@ +/* + * colors.h -- color attribute definitions + * + * AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Default color definitions + * + * *_FG = foreground + * *_BG = background + * *_HL = highlight? + */ +#define SCREEN_FG COLOR_CYAN +#define SCREEN_BG COLOR_BLUE +#define SCREEN_HL TRUE + +#define SHADOW_FG COLOR_BLACK +#define SHADOW_BG COLOR_BLACK +#define SHADOW_HL TRUE + +#define DIALOG_FG COLOR_BLACK +#define DIALOG_BG COLOR_WHITE +#define DIALOG_HL FALSE + +#define TITLE_FG COLOR_YELLOW +#define TITLE_BG COLOR_WHITE +#define TITLE_HL TRUE + +#define BORDER_FG COLOR_WHITE +#define BORDER_BG COLOR_WHITE +#define BORDER_HL TRUE + +#define BUTTON_ACTIVE_FG COLOR_WHITE +#define BUTTON_ACTIVE_BG COLOR_BLUE +#define BUTTON_ACTIVE_HL TRUE + +#define BUTTON_INACTIVE_FG COLOR_BLACK +#define BUTTON_INACTIVE_BG COLOR_WHITE +#define BUTTON_INACTIVE_HL FALSE + +#define BUTTON_KEY_ACTIVE_FG COLOR_WHITE +#define BUTTON_KEY_ACTIVE_BG COLOR_BLUE +#define BUTTON_KEY_ACTIVE_HL TRUE + +#define BUTTON_KEY_INACTIVE_FG COLOR_RED +#define BUTTON_KEY_INACTIVE_BG COLOR_WHITE +#define BUTTON_KEY_INACTIVE_HL FALSE + +#define BUTTON_LABEL_ACTIVE_FG COLOR_YELLOW +#define BUTTON_LABEL_ACTIVE_BG COLOR_BLUE +#define BUTTON_LABEL_ACTIVE_HL TRUE + +#define BUTTON_LABEL_INACTIVE_FG COLOR_BLACK +#define BUTTON_LABEL_INACTIVE_BG COLOR_WHITE +#define BUTTON_LABEL_INACTIVE_HL TRUE + +#define INPUTBOX_FG COLOR_BLACK +#define INPUTBOX_BG COLOR_WHITE +#define INPUTBOX_HL FALSE + +#define INPUTBOX_BORDER_FG COLOR_BLACK +#define INPUTBOX_BORDER_BG COLOR_WHITE +#define INPUTBOX_BORDER_HL FALSE + +#define SEARCHBOX_FG COLOR_BLACK +#define SEARCHBOX_BG COLOR_WHITE +#define SEARCHBOX_HL FALSE + +#define SEARCHBOX_TITLE_FG COLOR_YELLOW +#define SEARCHBOX_TITLE_BG COLOR_WHITE +#define SEARCHBOX_TITLE_HL TRUE + +#define SEARCHBOX_BORDER_FG COLOR_WHITE +#define SEARCHBOX_BORDER_BG COLOR_WHITE +#define SEARCHBOX_BORDER_HL TRUE + +#define POSITION_INDICATOR_FG COLOR_YELLOW +#define POSITION_INDICATOR_BG COLOR_WHITE +#define POSITION_INDICATOR_HL TRUE + +#define MENUBOX_FG COLOR_BLACK +#define MENUBOX_BG COLOR_WHITE +#define MENUBOX_HL FALSE + +#define MENUBOX_BORDER_FG COLOR_WHITE +#define MENUBOX_BORDER_BG COLOR_WHITE +#define MENUBOX_BORDER_HL TRUE + +#define ITEM_FG COLOR_BLACK +#define ITEM_BG COLOR_WHITE +#define ITEM_HL FALSE + +#define ITEM_SELECTED_FG COLOR_WHITE +#define ITEM_SELECTED_BG COLOR_BLUE +#define ITEM_SELECTED_HL TRUE + +#define TAG_FG COLOR_YELLOW +#define TAG_BG COLOR_WHITE +#define TAG_HL TRUE + +#define TAG_SELECTED_FG COLOR_YELLOW +#define TAG_SELECTED_BG COLOR_BLUE +#define TAG_SELECTED_HL TRUE + +#define TAG_KEY_FG COLOR_YELLOW +#define TAG_KEY_BG COLOR_WHITE +#define TAG_KEY_HL TRUE + +#define TAG_KEY_SELECTED_FG COLOR_YELLOW +#define TAG_KEY_SELECTED_BG COLOR_BLUE +#define TAG_KEY_SELECTED_HL TRUE + +#define CHECK_FG COLOR_BLACK +#define CHECK_BG COLOR_WHITE +#define CHECK_HL FALSE + +#define CHECK_SELECTED_FG COLOR_WHITE +#define CHECK_SELECTED_BG COLOR_BLUE +#define CHECK_SELECTED_HL TRUE + +#define UARROW_FG COLOR_GREEN +#define UARROW_BG COLOR_WHITE +#define UARROW_HL TRUE + +#define DARROW_FG COLOR_GREEN +#define DARROW_BG COLOR_WHITE +#define DARROW_HL TRUE + +/* End of default color definitions */ + +#define C_ATTR(x,y) ((x ? A_BOLD : 0) | COLOR_PAIR((y))) +#define COLOR_NAME_LEN 10 +#define COLOR_COUNT 8 + +/* + * Global variables + */ + +extern int color_table[][3]; diff --git a/scripts/kconfig/lxdialog/dialog.h b/scripts/kconfig/lxdialog/dialog.h new file mode 100644 index 000000000..af3cf716e --- /dev/null +++ b/scripts/kconfig/lxdialog/dialog.h @@ -0,0 +1,177 @@ +/* + * dialog.h -- common declarations for all dialog modules + * + * AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef __sun__ +#define CURS_MACROS +#endif +#include CURSES_LOC + +/* + * Colors in ncurses 1.9.9e do not work properly since foreground and + * background colors are OR'd rather than separately masked. This version + * of dialog was hacked to work with ncurses 1.9.9e, making it incompatible + * with standard curses. The simplest fix (to make this work with standard + * curses) uses the wbkgdset() function, not used in the original hack. + * Turn it off if we're building with 1.9.9e, since it just confuses things. + */ +#if defined(NCURSES_VERSION) && defined(_NEED_WRAP) && !defined(GCC_PRINTFLIKE) +#define OLD_NCURSES 1 +#undef wbkgdset +#define wbkgdset(w,p) /*nothing */ +#else +#define OLD_NCURSES 0 +#endif + +#define TR(params) _tracef params + +#define ESC 27 +#define TAB 9 +#define MAX_LEN 2048 +#define BUF_SIZE (10*1024) +#define MIN(x,y) (x < y ? x : y) +#define MAX(x,y) (x > y ? x : y) + +#ifndef ACS_ULCORNER +#define ACS_ULCORNER '+' +#endif +#ifndef ACS_LLCORNER +#define ACS_LLCORNER '+' +#endif +#ifndef ACS_URCORNER +#define ACS_URCORNER '+' +#endif +#ifndef ACS_LRCORNER +#define ACS_LRCORNER '+' +#endif +#ifndef ACS_HLINE +#define ACS_HLINE '-' +#endif +#ifndef ACS_VLINE +#define ACS_VLINE '|' +#endif +#ifndef ACS_LTEE +#define ACS_LTEE '+' +#endif +#ifndef ACS_RTEE +#define ACS_RTEE '+' +#endif +#ifndef ACS_UARROW +#define ACS_UARROW '^' +#endif +#ifndef ACS_DARROW +#define ACS_DARROW 'v' +#endif + +/* + * Attribute names + */ +#define screen_attr attributes[0] +#define shadow_attr attributes[1] +#define dialog_attr attributes[2] +#define title_attr attributes[3] +#define border_attr attributes[4] +#define button_active_attr attributes[5] +#define button_inactive_attr attributes[6] +#define button_key_active_attr attributes[7] +#define button_key_inactive_attr attributes[8] +#define button_label_active_attr attributes[9] +#define button_label_inactive_attr attributes[10] +#define inputbox_attr attributes[11] +#define inputbox_border_attr attributes[12] +#define searchbox_attr attributes[13] +#define searchbox_title_attr attributes[14] +#define searchbox_border_attr attributes[15] +#define position_indicator_attr attributes[16] +#define menubox_attr attributes[17] +#define menubox_border_attr attributes[18] +#define item_attr attributes[19] +#define item_selected_attr attributes[20] +#define tag_attr attributes[21] +#define tag_selected_attr attributes[22] +#define tag_key_attr attributes[23] +#define tag_key_selected_attr attributes[24] +#define check_attr attributes[25] +#define check_selected_attr attributes[26] +#define uarrow_attr attributes[27] +#define darrow_attr attributes[28] + +/* number of attributes */ +#define ATTRIBUTE_COUNT 29 + +/* + * Global variables + */ +extern bool use_colors; +extern bool use_shadow; + +extern chtype attributes[]; + +extern const char *backtitle; + +/* + * Function prototypes + */ +extern void create_rc(const char *filename); +extern int parse_rc(void); + +void init_dialog(void); +void end_dialog(void); +void attr_clear(WINDOW * win, int height, int width, chtype attr); +void dialog_clear(void); +void color_setup(void); +void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x); +void print_button(WINDOW * win, const char *label, int y, int x, int selected); +void print_title(WINDOW *dialog, const char *title, int width); +void draw_box(WINDOW * win, int y, int x, int height, int width, chtype box, + chtype border); +void draw_shadow(WINDOW * win, int y, int x, int height, int width); + +int first_alpha(const char *string, const char *exempt); +int dialog_yesno(const char *title, const char *prompt, int height, int width); +int dialog_msgbox(const char *title, const char *prompt, int height, + int width, int pause); +int dialog_textbox(const char *title, const char *file, int height, int width); +int dialog_menu(const char *title, const char *prompt, int height, int width, + int menu_height, const char *choice, int item_no, + const char *const *items); +int dialog_checklist(const char *title, const char *prompt, int height, + int width, int list_height, int item_no, + const char *const *items); +extern char dialog_input_result[]; +int dialog_inputbox(const char *title, const char *prompt, int height, + int width, const char *init); + +/* + * This is the base for fictitious keys, which activate + * the buttons. + * + * Mouse-generated keys are the following: + * -- the first 32 are used as numbers, in addition to '0'-'9' + * -- the lowercase are used to signal mouse-enter events (M_EVENT + 'o') + * -- uppercase chars are used to invoke the button (M_EVENT + 'O') + */ +#define M_EVENT (KEY_MAX+1) diff --git a/scripts/kconfig/lxdialog/inputbox.c b/scripts/kconfig/lxdialog/inputbox.c new file mode 100644 index 000000000..779503726 --- /dev/null +++ b/scripts/kconfig/lxdialog/inputbox.c @@ -0,0 +1,224 @@ +/* + * inputbox.c -- implements the input box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" + +char dialog_input_result[MAX_LEN + 1]; + +/* + * Print the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 11; + int y = height - 2; + + print_button(dialog, " Ok ", y, x, selected == 0); + print_button(dialog, " Help ", y, x + 14, selected == 1); + + wmove(dialog, y, x + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box for inputing a string + */ +int dialog_inputbox(const char *title, const char *prompt, int height, int width, + const char *init) +{ + int i, x, y, box_y, box_x, box_width; + int input_x = 0, scroll = 0, key = 0, button = -1; + char *instr = dialog_input_result; + WINDOW *dialog; + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dialog_attr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + /* Draw the input field box */ + box_width = width - 6; + getyx(dialog, y, x); + box_y = y + 2; + box_x = (width - box_width) / 2; + draw_box(dialog, y + 1, box_x - 1, 3, box_width + 2, border_attr, dialog_attr); + + print_buttons(dialog, height, width, 0); + + /* Set up the initial value */ + wmove(dialog, box_y, box_x); + wattrset(dialog, inputbox_attr); + + if (!init) + instr[0] = '\0'; + else + strcpy(instr, init); + + input_x = strlen(instr); + + if (input_x >= box_width) { + scroll = input_x - box_width + 1; + input_x = box_width - 1; + for (i = 0; i < box_width - 1; i++) + waddch(dialog, instr[scroll + i]); + } else { + waddstr(dialog, instr); + } + + wmove(dialog, box_y, box_x + input_x); + + wrefresh(dialog); + + while (key != ESC) { + key = wgetch(dialog); + + if (button == -1) { /* Input box selected */ + switch (key) { + case TAB: + case KEY_UP: + case KEY_DOWN: + break; + case KEY_LEFT: + continue; + case KEY_RIGHT: + continue; + case KEY_BACKSPACE: + case 127: + if (input_x || scroll) { + wattrset(dialog, inputbox_attr); + if (!input_x) { + scroll = scroll < box_width - 1 ? 0 : scroll - (box_width - 1); + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) + waddch(dialog, + instr[scroll + input_x + i] ? + instr[scroll + input_x + i] : ' '); + input_x = strlen(instr) - scroll; + } else + input_x--; + instr[scroll + input_x] = '\0'; + mvwaddch(dialog, box_y, input_x + box_x, ' '); + wmove(dialog, box_y, input_x + box_x); + wrefresh(dialog); + } + continue; + default: + if (key < 0x100 && isprint(key)) { + if (scroll + input_x < MAX_LEN) { + wattrset(dialog, inputbox_attr); + instr[scroll + input_x] = key; + instr[scroll + input_x + 1] = '\0'; + if (input_x == box_width - 1) { + scroll++; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width - 1; i++) + waddch(dialog, instr [scroll + i]); + } else { + wmove(dialog, box_y, input_x++ + box_x); + waddch(dialog, key); + } + wrefresh(dialog); + } else + flash(); /* Alarm user about overflow */ + continue; + } + } + } + switch (key) { + case 'O': + case 'o': + delwin(dialog); + return 0; + case 'H': + case 'h': + delwin(dialog); + return 1; + case KEY_UP: + case KEY_LEFT: + switch (button) { + case -1: + button = 1; /* Indicates "Cancel" button is selected */ + print_buttons(dialog, height, width, 1); + break; + case 0: + button = -1; /* Indicates input box is selected */ + print_buttons(dialog, height, width, 0); + wmove(dialog, box_y, box_x + input_x); + wrefresh(dialog); + break; + case 1: + button = 0; /* Indicates "OK" button is selected */ + print_buttons(dialog, height, width, 0); + break; + } + break; + case TAB: + case KEY_DOWN: + case KEY_RIGHT: + switch (button) { + case -1: + button = 0; /* Indicates "OK" button is selected */ + print_buttons(dialog, height, width, 0); + break; + case 0: + button = 1; /* Indicates "Cancel" button is selected */ + print_buttons(dialog, height, width, 1); + break; + case 1: + button = -1; /* Indicates input box is selected */ + print_buttons(dialog, height, width, 0); + wmove(dialog, box_y, box_x + input_x); + wrefresh(dialog); + break; + } + break; + case ' ': + case '\n': + delwin(dialog); + return (button == -1 ? 0 : button); + case 'X': + case 'x': + key = ESC; + case ESC: + break; + } + } + + delwin(dialog); + return -1; /* ESC pressed */ +} diff --git a/scripts/kconfig/lxdialog/lxdialog.c b/scripts/kconfig/lxdialog/lxdialog.c new file mode 100644 index 000000000..79f6c5fb5 --- /dev/null +++ b/scripts/kconfig/lxdialog/lxdialog.c @@ -0,0 +1,204 @@ +/* + * dialog - Display simple dialog boxes from shell scripts + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" + +static void Usage(const char *name); + +typedef int (jumperFn) (const char *title, int argc, const char *const *argv); + +struct Mode { + char *name; + int argmin, argmax, argmod; + jumperFn *jumper; +}; + +jumperFn j_menu, j_radiolist, j_yesno, j_textbox, j_inputbox; +jumperFn j_msgbox, j_infobox; + +static struct Mode modes[] = { + {"--menu", 9, 0, 3, j_menu}, + {"--radiolist", 9, 0, 3, j_radiolist}, + {"--yesno", 5, 5, 1, j_yesno}, + {"--textbox", 5, 5, 1, j_textbox}, + {"--inputbox", 5, 6, 1, j_inputbox}, + {"--msgbox", 5, 5, 1, j_msgbox}, + {"--infobox", 5, 5, 1, j_infobox}, + {NULL, 0, 0, 0, NULL} +}; + +static struct Mode *modePtr; + +#ifdef LOCALE +#include +#endif + +int main(int argc, const char *const *argv) +{ + int offset = 0, opt_clear = 0, end_common_opts = 0, retval; + const char *title = NULL; + +#ifdef LOCALE + (void)setlocale(LC_ALL, ""); +#endif + +#ifdef TRACE + trace(TRACE_CALLS | TRACE_UPDATE); +#endif + if (argc < 2) { + Usage(argv[0]); + exit(-1); + } + + while (offset < argc - 1 && !end_common_opts) { /* Common options */ + if (!strcmp(argv[offset + 1], "--title")) { + if (argc - offset < 3 || title != NULL) { + Usage(argv[0]); + exit(-1); + } else { + title = argv[offset + 2]; + offset += 2; + } + } else if (!strcmp(argv[offset + 1], "--backtitle")) { + if (backtitle != NULL) { + Usage(argv[0]); + exit(-1); + } else { + backtitle = argv[offset + 2]; + offset += 2; + } + } else if (!strcmp(argv[offset + 1], "--clear")) { + if (opt_clear) { /* Hey, "--clear" can't appear twice! */ + Usage(argv[0]); + exit(-1); + } else if (argc == 2) { /* we only want to clear the screen */ + init_dialog(); + refresh(); /* init_dialog() will clear the screen for us */ + end_dialog(); + return 0; + } else { + opt_clear = 1; + offset++; + } + } else /* no more common options */ + end_common_opts = 1; + } + + if (argc - 1 == offset) { /* no more options */ + Usage(argv[0]); + exit(-1); + } + /* use a table to look for the requested mode, to avoid code duplication */ + + for (modePtr = modes; modePtr->name; modePtr++) /* look for the mode */ + if (!strcmp(argv[offset + 1], modePtr->name)) + break; + + if (!modePtr->name) + Usage(argv[0]); + if (argc - offset < modePtr->argmin) + Usage(argv[0]); + if (modePtr->argmax && argc - offset > modePtr->argmax) + Usage(argv[0]); + + init_dialog(); + retval = (*(modePtr->jumper)) (title, argc - offset, argv + offset); + + if (opt_clear) { /* clear screen before exit */ + attr_clear(stdscr, LINES, COLS, screen_attr); + refresh(); + } + end_dialog(); + + exit(retval); +} + +/* + * Print program usage + */ +static void Usage(const char *name) +{ + fprintf(stderr, "\ +\ndialog, by Savio Lam (lam836@cs.cuhk.hk).\ +\n patched by Stuart Herbert (S.Herbert@shef.ac.uk)\ +\n modified/gutted for use as a Linux kernel config tool by \ +\n William Roadcap (roadcapw@cfw.com)\ +\n\ +\n* Display dialog boxes from shell scripts *\ +\n\ +\nUsage: %s --clear\ +\n %s [--title ] [--backtitle <backtitle>] --clear <Box options>\ +\n\ +\nBox options:\ +\n\ +\n --menu <text> <height> <width> <menu height> <tag1> <item1>...\ +\n --radiolist <text> <height> <width> <list height> <tag1> <item1> <status1>...\ +\n --textbox <file> <height> <width>\ +\n --inputbox <text> <height> <width> [<init>]\ +\n --yesno <text> <height> <width>\ +\n", name, name); + exit(-1); +} + +/* + * These are the program jumpers + */ + +int j_menu(const char *t, int ac, const char *const *av) +{ + return dialog_menu(t, av[2], atoi(av[3]), atoi(av[4]), + atoi(av[5]), av[6], (ac - 6) / 2, av + 7); +} + +int j_radiolist(const char *t, int ac, const char *const *av) +{ + return dialog_checklist(t, av[2], atoi(av[3]), atoi(av[4]), + atoi(av[5]), (ac - 6) / 3, av + 6); +} + +int j_textbox(const char *t, int ac, const char *const *av) +{ + return dialog_textbox(t, av[2], atoi(av[3]), atoi(av[4])); +} + +int j_yesno(const char *t, int ac, const char *const *av) +{ + return dialog_yesno(t, av[2], atoi(av[3]), atoi(av[4])); +} + +int j_inputbox(const char *t, int ac, const char *const *av) +{ + int ret = dialog_inputbox(t, av[2], atoi(av[3]), atoi(av[4]), + ac == 6 ? av[5] : (char *)NULL); + if (ret == 0) + fprintf(stderr, dialog_input_result); + return ret; +} + +int j_msgbox(const char *t, int ac, const char *const *av) +{ + return dialog_msgbox(t, av[2], atoi(av[3]), atoi(av[4]), 1); +} + +int j_infobox(const char *t, int ac, const char *const *av) +{ + return dialog_msgbox(t, av[2], atoi(av[3]), atoi(av[4]), 0); +} diff --git a/scripts/kconfig/lxdialog/menubox.c b/scripts/kconfig/lxdialog/menubox.c new file mode 100644 index 000000000..bf8052f4f --- /dev/null +++ b/scripts/kconfig/lxdialog/menubox.c @@ -0,0 +1,426 @@ +/* + * menubox.c -- implements the menu box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcapw@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * Changes by Clifford Wolf (god@clifford.at) + * + * [ 1998-06-13 ] + * + * *) A bugfix for the Page-Down problem + * + * *) Formerly when I used Page Down and Page Up, the cursor would be set + * to the first position in the menu box. Now lxdialog is a bit + * smarter and works more like other menu systems (just have a look at + * it). + * + * *) Formerly if I selected something my scrolling would be broken because + * lxdialog is re-invoked by the Menuconfig shell script, can't + * remember the last scrolling position, and just sets it so that the + * cursor is at the bottom of the box. Now it writes the temporary file + * lxdialog.scrltmp which contains this information. The file is + * deleted by lxdialog if the user leaves a submenu or enters a new + * one, but it would be nice if Menuconfig could make another "rm -f" + * just to be sure. Just try it out - you will recognise a difference! + * + * [ 1998-06-14 ] + * + * *) Now lxdialog is crash-safe against broken "lxdialog.scrltmp" files + * and menus change their size on the fly. + * + * *) If for some reason the last scrolling position is not saved by + * lxdialog, it sets the scrolling so that the selected item is in the + * middle of the menu box, not at the bottom. + * + * 02 January 1999, Michael Elizabeth Chastain (mec@shout.net) + * Reset 'scroll' to 0 if the value from lxdialog.scrltmp is bogus. + * This fixes a bug in Menuconfig where using ' ' to descend into menus + * would leave mis-synchronized lxdialog.scrltmp files lying around, + * fscanf would read in 'scroll', and eventually that value would get used. + */ + +#include "dialog.h" + +static int menu_width, item_x; + +/* + * Print menu item + */ +static void do_print_item(WINDOW * win, const char *item, int choice, + int selected, int hotkey) +{ + int j; + char *menu_item = malloc(menu_width + 1); + + strncpy(menu_item, item, menu_width - item_x); + menu_item[menu_width] = 0; + j = first_alpha(menu_item, "YyNnMmHh"); + + /* Clear 'residue' of last item */ + wattrset(win, menubox_attr); + wmove(win, choice, 0); +#if OLD_NCURSES + { + int i; + for (i = 0; i < menu_width; i++) + waddch(win, ' '); + } +#else + wclrtoeol(win); +#endif + wattrset(win, selected ? item_selected_attr : item_attr); + mvwaddstr(win, choice, item_x, menu_item); + if (hotkey) { + wattrset(win, selected ? tag_key_selected_attr : tag_key_attr); + mvwaddch(win, choice, item_x + j, menu_item[j]); + } + if (selected) { + wmove(win, choice, item_x + 1); + } + free(menu_item); + wrefresh(win); +} + +#define print_item(index, choice, selected) \ +do {\ + int hotkey = (items[(index) * 2][0] != ':'); \ + do_print_item(menu, items[(index) * 2 + 1], choice, selected, hotkey); \ +} while (0) + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int item_no, int scroll, int y, int x, + int height) +{ + int cur_y, cur_x; + + getyx(win, cur_y, cur_x); + + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, uarrow_attr); + waddch(win, ACS_UARROW); + waddstr(win, "(-)"); + } else { + wattrset(win, menubox_attr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + wrefresh(win); + + if ((height < item_no) && (scroll + height < item_no)) { + wattrset(win, darrow_attr); + waddch(win, ACS_DARROW); + waddstr(win, "(+)"); + } else { + wattrset(win, menubox_border_attr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + wmove(win, cur_y, cur_x); + wrefresh(win); +} + +/* + * Display the termination buttons. + */ +static void print_buttons(WINDOW * win, int height, int width, int selected) +{ + int x = width / 2 - 16; + int y = height - 2; + + print_button(win, "Select", y, x, selected == 0); + print_button(win, " Exit ", y, x + 12, selected == 1); + print_button(win, " Help ", y, x + 24, selected == 2); + + wmove(win, y, x + 1 + 12 * selected); + wrefresh(win); +} + +/* scroll up n lines (n may be negative) */ +static void do_scroll(WINDOW *win, int *scroll, int n) +{ + /* Scroll menu up */ + scrollok(win, TRUE); + wscrl(win, n); + scrollok(win, FALSE); + *scroll = *scroll + n; + wrefresh(win); +} + +/* + * Display a menu for choosing among a number of options + */ +int dialog_menu(const char *title, const char *prompt, int height, int width, + int menu_height, const char *current, int item_no, + const char *const *items) +{ + int i, j, x, y, box_x, box_y; + int key = 0, button = 0, scroll = 0, choice = 0; + int first_item = 0, max_choice; + WINDOW *dialog, *menu; + FILE *f; + + max_choice = MIN(menu_height, item_no); + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + wbkgdset(dialog, dialog_attr & A_COLOR); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dialog_attr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + menu_width = width - 6; + box_y = height - menu_height - 5; + box_x = (width - menu_width) / 2 - 1; + + /* create new window for the menu */ + menu = subwin(dialog, menu_height, menu_width, + y + box_y + 1, x + box_x + 1); + keypad(menu, TRUE); + + /* draw a box around the menu items */ + draw_box(dialog, box_y, box_x, menu_height + 2, menu_width + 2, + menubox_border_attr, menubox_attr); + + item_x = (menu_width - 70) / 2; + + /* Set choice to default item */ + for (i = 0; i < item_no; i++) + if (strcmp(current, items[i * 2]) == 0) + choice = i; + + /* get the scroll info from the temp file */ + if ((f = fopen("lxdialog.scrltmp", "r")) != NULL) { + if ((fscanf(f, "%d\n", &scroll) == 1) && (scroll <= choice) && + (scroll + max_choice > choice) && (scroll >= 0) && + (scroll + max_choice <= item_no)) { + first_item = scroll; + choice = choice - scroll; + fclose(f); + } else { + scroll = 0; + remove("lxdialog.scrltmp"); + fclose(f); + f = NULL; + } + } + if ((choice >= max_choice) || (f == NULL && choice >= max_choice / 2)) { + if (choice >= item_no - max_choice / 2) + scroll = first_item = item_no - max_choice; + else + scroll = first_item = choice - max_choice / 2; + choice = choice - scroll; + } + + /* Print the menu */ + for (i = 0; i < max_choice; i++) { + print_item(first_item + i, i, i == choice); + } + + wnoutrefresh(menu); + + print_arrows(dialog, item_no, scroll, + box_y, box_x + item_x + 1, menu_height); + + print_buttons(dialog, height, width, 0); + wmove(menu, choice, item_x + 1); + wrefresh(menu); + + while (key != ESC) { + key = wgetch(menu); + + if (key < 256 && isalpha(key)) + key = tolower(key); + + if (strchr("ynmh", key)) + i = max_choice; + else { + for (i = choice + 1; i < max_choice; i++) { + j = first_alpha(items[(scroll + i) * 2 + 1], "YyNnMmHh"); + if (key == tolower(items[(scroll + i) * 2 + 1][j])) + break; + } + if (i == max_choice) + for (i = 0; i < max_choice; i++) { + j = first_alpha(items [(scroll + i) * 2 + 1], "YyNnMmHh"); + if (key == tolower(items[(scroll + i) * 2 + 1][j])) + break; + } + } + + if (i < max_choice || + key == KEY_UP || key == KEY_DOWN || + key == '-' || key == '+' || + key == KEY_PPAGE || key == KEY_NPAGE) { + /* Remove highligt of current item */ + print_item(scroll + choice, choice, FALSE); + + if (key == KEY_UP || key == '-') { + if (choice < 2 && scroll) { + /* Scroll menu down */ + do_scroll(menu, &scroll, -1); + + print_item(scroll, 0, FALSE); + } else + choice = MAX(choice - 1, 0); + + } else if (key == KEY_DOWN || key == '+') { + print_item(scroll+choice, choice, FALSE); + + if ((choice > max_choice - 3) && + (scroll + max_choice < item_no)) { + /* Scroll menu up */ + do_scroll(menu, &scroll, 1); + + print_item(scroll+max_choice - 1, + max_choice - 1, FALSE); + } else + choice = MIN(choice + 1, max_choice - 1); + + } else if (key == KEY_PPAGE) { + scrollok(menu, TRUE); + for (i = 0; (i < max_choice); i++) { + if (scroll > 0) { + do_scroll(menu, &scroll, -1); + print_item(scroll, 0, FALSE); + } else { + if (choice > 0) + choice--; + } + } + + } else if (key == KEY_NPAGE) { + for (i = 0; (i < max_choice); i++) { + if (scroll + max_choice < item_no) { + do_scroll(menu, &scroll, 1); + print_item(scroll+max_choice-1, + max_choice - 1, FALSE); + } else { + if (choice + 1 < max_choice) + choice++; + } + } + } else + choice = i; + + print_item(scroll + choice, choice, TRUE); + + print_arrows(dialog, item_no, scroll, + box_y, box_x + item_x + 1, menu_height); + + wnoutrefresh(dialog); + wrefresh(menu); + + continue; /* wait for another key press */ + } + + switch (key) { + case KEY_LEFT: + case TAB: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? 2 : (button > 2 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(menu); + break; + case ' ': + case 's': + case 'y': + case 'n': + case 'm': + case '/': + /* save scroll info */ + if ((f = fopen("lxdialog.scrltmp", "w")) != NULL) { + fprintf(f, "%d\n", scroll); + fclose(f); + } + delwin(dialog); + fprintf(stderr, "%s\n", items[(scroll + choice) * 2]); + switch (key) { + case 's': + return 3; + case 'y': + return 3; + case 'n': + return 4; + case 'm': + return 5; + case ' ': + return 6; + case '/': + return 7; + } + return 0; + case 'h': + case '?': + button = 2; + case '\n': + delwin(dialog); + if (button == 2) + fprintf(stderr, "%s \"%s\"\n", + items[(scroll + choice) * 2], + items[(scroll + choice) * 2 + 1] + + first_alpha(items [(scroll + choice) * 2 + 1], "")); + else + fprintf(stderr, "%s\n", + items[(scroll + choice) * 2]); + + remove("lxdialog.scrltmp"); + return button; + case 'e': + case 'x': + key = ESC; + case ESC: + break; + } + } + + delwin(dialog); + remove("lxdialog.scrltmp"); + return -1; /* ESC pressed */ +} diff --git a/scripts/kconfig/lxdialog/msgbox.c b/scripts/kconfig/lxdialog/msgbox.c new file mode 100644 index 000000000..7323f5471 --- /dev/null +++ b/scripts/kconfig/lxdialog/msgbox.c @@ -0,0 +1,71 @@ +/* + * msgbox.c -- implements the message box and info box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcapw@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" + +/* + * Display a message box. Program will pause and display an "OK" button + * if the parameter 'pause' is non-zero. + */ +int dialog_msgbox(const char *title, const char *prompt, int height, int width, + int pause) +{ + int i, x, y, key = 0; + WINDOW *dialog; + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + + print_title(dialog, title, width); + + wattrset(dialog, dialog_attr); + print_autowrap(dialog, prompt, width - 2, 1, 2); + + if (pause) { + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + waddch(dialog, ACS_RTEE); + + print_button(dialog, " Ok ", height - 2, width / 2 - 4, TRUE); + + wrefresh(dialog); + while (key != ESC && key != '\n' && key != ' ' && + key != 'O' && key != 'o' && key != 'X' && key != 'x') + key = wgetch(dialog); + } else { + key = '\n'; + wrefresh(dialog); + } + + delwin(dialog); + return key == ESC ? -1 : 0; +} diff --git a/scripts/kconfig/lxdialog/textbox.c b/scripts/kconfig/lxdialog/textbox.c new file mode 100644 index 000000000..77848bb8e --- /dev/null +++ b/scripts/kconfig/lxdialog/textbox.c @@ -0,0 +1,533 @@ +/* + * textbox.c -- implements the text box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" + +static void back_lines(int n); +static void print_page(WINDOW * win, int height, int width); +static void print_line(WINDOW * win, int row, int width); +static char *get_line(void); +static void print_position(WINDOW * win, int height, int width); + +static int hscroll, fd, file_size, bytes_read; +static int begin_reached = 1, end_reached, page_length; +static char *buf, *page; + +/* + * Display text from a file in a dialog box. + */ +int dialog_textbox(const char *title, const char *file, int height, int width) +{ + int i, x, y, cur_x, cur_y, fpos, key = 0; + int passed_end; + char search_term[MAX_LEN + 1]; + WINDOW *dialog, *text; + + search_term[0] = '\0'; /* no search term entered yet */ + + /* Open input file for reading */ + if ((fd = open(file, O_RDONLY)) == -1) { + endwin(); + fprintf(stderr, "\nCan't open input file in dialog_textbox().\n"); + exit(-1); + } + /* Get file size. Actually, 'file_size' is the real file size - 1, + since it's only the last byte offset from the beginning */ + if ((file_size = lseek(fd, 0, SEEK_END)) == -1) { + endwin(); + fprintf(stderr, "\nError getting file size in dialog_textbox().\n"); + exit(-1); + } + /* Restore file pointer to beginning of file after getting file size */ + if (lseek(fd, 0, SEEK_SET) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n"); + exit(-1); + } + /* Allocate space for read buffer */ + if ((buf = malloc(BUF_SIZE + 1)) == NULL) { + endwin(); + fprintf(stderr, "\nCan't allocate memory in dialog_textbox().\n"); + exit(-1); + } + if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in dialog_textbox().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; /* mark end of valid data */ + page = buf; /* page is pointer to start of page to be displayed */ + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + /* Create window for text region, used for scrolling text */ + text = subwin(dialog, height - 4, width - 2, y + 1, x + 1); + wattrset(text, dialog_attr); + wbkgdset(text, dialog_attr & A_COLOR); + + keypad(text, TRUE); + + /* register the new window, along with its borders */ + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + wbkgdset(dialog, dialog_attr & A_COLOR); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + print_button(dialog, " Exit ", height - 2, width / 2 - 4, TRUE); + wnoutrefresh(dialog); + getyx(dialog, cur_y, cur_x); /* Save cursor position */ + + /* Print first page of text */ + attr_clear(text, height - 4, width - 2, dialog_attr); + print_page(text, height - 4, width - 2); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + + while ((key != ESC) && (key != '\n')) { + key = wgetch(dialog); + switch (key) { + case 'E': /* Exit */ + case 'e': + case 'X': + case 'x': + delwin(dialog); + free(buf); + close(fd); + return 0; + case 'g': /* First page */ + case KEY_HOME: + if (!begin_reached) { + begin_reached = 1; + /* First page not in buffer? */ + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n"); + exit(-1); + } + if (fpos > bytes_read) { /* Yes, we have to read it in */ + if (lseek(fd, 0, SEEK_SET) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in " + "dialog_textbox().\n"); + exit(-1); + } + if ((bytes_read = + read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in dialog_textbox().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; + } + page = buf; + print_page(text, height - 4, width - 2); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + } + break; + case 'G': /* Last page */ + case KEY_END: + + end_reached = 1; + /* Last page not in buffer? */ + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n"); + exit(-1); + } + if (fpos < file_size) { /* Yes, we have to read it in */ + if (lseek(fd, -BUF_SIZE, SEEK_END) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n"); + exit(-1); + } + if ((bytes_read = + read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in dialog_textbox().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; + } + page = buf + bytes_read; + back_lines(height - 4); + print_page(text, height - 4, width - 2); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + break; + case 'K': /* Previous line */ + case 'k': + case KEY_UP: + if (!begin_reached) { + back_lines(page_length + 1); + + /* We don't call print_page() here but use scrolling to ensure + faster screen update. However, 'end_reached' and + 'page_length' should still be updated, and 'page' should + point to start of next page. This is done by calling + get_line() in the following 'for' loop. */ + scrollok(text, TRUE); + wscrl(text, -1); /* Scroll text region down one line */ + scrollok(text, FALSE); + page_length = 0; + passed_end = 0; + for (i = 0; i < height - 4; i++) { + if (!i) { + /* print first line of page */ + print_line(text, 0, width - 2); + wnoutrefresh(text); + } else + /* Called to update 'end_reached' and 'page' */ + get_line(); + if (!passed_end) + page_length++; + if (end_reached && !passed_end) + passed_end = 1; + } + + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + } + break; + case 'B': /* Previous page */ + case 'b': + case KEY_PPAGE: + if (begin_reached) + break; + back_lines(page_length + height - 4); + print_page(text, height - 4, width - 2); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); + wrefresh(dialog); + break; + case 'J': /* Next line */ + case 'j': + case KEY_DOWN: + if (!end_reached) { + begin_reached = 0; + scrollok(text, TRUE); + scroll(text); /* Scroll text region up one line */ + scrollok(text, FALSE); + print_line(text, height - 5, width - 2); + wnoutrefresh(text); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + } + break; + case KEY_NPAGE: /* Next page */ + case ' ': + if (end_reached) + break; + + begin_reached = 0; + print_page(text, height - 4, width - 2); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); + wrefresh(dialog); + break; + case '0': /* Beginning of line */ + case 'H': /* Scroll left */ + case 'h': + case KEY_LEFT: + if (hscroll <= 0) + break; + + if (key == '0') + hscroll = 0; + else + hscroll--; + /* Reprint current page to scroll horizontally */ + back_lines(page_length); + print_page(text, height - 4, width - 2); + wmove(dialog, cur_y, cur_x); + wrefresh(dialog); + break; + case 'L': /* Scroll right */ + case 'l': + case KEY_RIGHT: + if (hscroll >= MAX_LEN) + break; + hscroll++; + /* Reprint current page to scroll horizontally */ + back_lines(page_length); + print_page(text, height - 4, width - 2); + wmove(dialog, cur_y, cur_x); + wrefresh(dialog); + break; + case ESC: + break; + } + } + + delwin(dialog); + free(buf); + close(fd); + return -1; /* ESC pressed */ +} + +/* + * Go back 'n' lines in text file. Called by dialog_textbox(). + * 'page' will be updated to point to the desired line in 'buf'. + */ +static void back_lines(int n) +{ + int i, fpos; + + begin_reached = 0; + /* We have to distinguish between end_reached and !end_reached + since at end of file, the line is not ended by a '\n'. + The code inside 'if' basically does a '--page' to move one + character backward so as to skip '\n' of the previous line */ + if (!end_reached) { + /* Either beginning of buffer or beginning of file reached? */ + if (page == buf) { + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in " + "back_lines().\n"); + exit(-1); + } + if (fpos > bytes_read) { /* Not beginning of file yet */ + /* We've reached beginning of buffer, but not beginning of + file yet, so read previous part of file into buffer. + Note that we only move backward for BUF_SIZE/2 bytes, + but not BUF_SIZE bytes to avoid re-reading again in + print_page() later */ + /* Really possible to move backward BUF_SIZE/2 bytes? */ + if (fpos < BUF_SIZE / 2 + bytes_read) { + /* No, move less then */ + if (lseek(fd, 0, SEEK_SET) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in " + "back_lines().\n"); + exit(-1); + } + page = buf + fpos - bytes_read; + } else { /* Move backward BUF_SIZE/2 bytes */ + if (lseek (fd, -(BUF_SIZE / 2 + bytes_read), SEEK_CUR) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer " + "in back_lines().\n"); + exit(-1); + } + page = buf + BUF_SIZE / 2; + } + if ((bytes_read = + read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in back_lines().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; + } else { /* Beginning of file reached */ + begin_reached = 1; + return; + } + } + if (*(--page) != '\n') { /* '--page' here */ + /* Something's wrong... */ + endwin(); + fprintf(stderr, "\nInternal error in back_lines().\n"); + exit(-1); + } + } + /* Go back 'n' lines */ + for (i = 0; i < n; i++) + do { + if (page == buf) { + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in back_lines().\n"); + exit(-1); + } + if (fpos > bytes_read) { + /* Really possible to move backward BUF_SIZE/2 bytes? */ + if (fpos < BUF_SIZE / 2 + bytes_read) { + /* No, move less then */ + if (lseek(fd, 0, SEEK_SET) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer " + "in back_lines().\n"); + exit(-1); + } + page = buf + fpos - bytes_read; + } else { /* Move backward BUF_SIZE/2 bytes */ + if (lseek (fd, -(BUF_SIZE / 2 + bytes_read), SEEK_CUR) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer" + " in back_lines().\n"); + exit(-1); + } + page = buf + BUF_SIZE / 2; + } + if ((bytes_read = + read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in " + "back_lines().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; + } else { /* Beginning of file reached */ + begin_reached = 1; + return; + } + } + } while (*(--page) != '\n'); + page++; +} + +/* + * Print a new page of text. Called by dialog_textbox(). + */ +static void print_page(WINDOW * win, int height, int width) +{ + int i, passed_end = 0; + + page_length = 0; + for (i = 0; i < height; i++) { + print_line(win, i, width); + if (!passed_end) + page_length++; + if (end_reached && !passed_end) + passed_end = 1; + } + wnoutrefresh(win); +} + +/* + * Print a new line of text. Called by dialog_textbox() and print_page(). + */ +static void print_line(WINDOW * win, int row, int width) +{ + int y, x; + char *line; + + line = get_line(); + line += MIN(strlen(line), hscroll); /* Scroll horizontally */ + wmove(win, row, 0); /* move cursor to correct line */ + waddch(win, ' '); + waddnstr(win, line, MIN(strlen(line), width - 2)); + + getyx(win, y, x); + /* Clear 'residue' of previous line */ +#if OLD_NCURSES + { + int i; + for (i = 0; i < width - x; i++) + waddch(win, ' '); + } +#else + wclrtoeol(win); +#endif +} + +/* + * Return current line of text. Called by dialog_textbox() and print_line(). + * 'page' should point to start of current line before calling, and will be + * updated to point to start of next line. + */ +static char *get_line(void) +{ + int i = 0, fpos; + static char line[MAX_LEN + 1]; + + end_reached = 0; + while (*page != '\n') { + if (*page == '\0') { + /* Either end of file or end of buffer reached */ + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in " + "get_line().\n"); + exit(-1); + } + if (fpos < file_size) { /* Not end of file yet */ + /* We've reached end of buffer, but not end of file yet, + so read next part of file into buffer */ + if ((bytes_read = + read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in get_line().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; + page = buf; + } else { + if (!end_reached) + end_reached = 1; + break; + } + } else if (i < MAX_LEN) + line[i++] = *(page++); + else { + /* Truncate lines longer than MAX_LEN characters */ + if (i == MAX_LEN) + line[i++] = '\0'; + page++; + } + } + if (i <= MAX_LEN) + line[i] = '\0'; + if (!end_reached) + page++; /* move pass '\n' */ + + return line; +} + +/* + * Print current position + */ +static void print_position(WINDOW * win, int height, int width) +{ + int fpos, percent; + + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in print_position().\n"); + exit(-1); + } + wattrset(win, position_indicator_attr); + wbkgdset(win, position_indicator_attr & A_COLOR); + percent = !file_size ? + 100 : ((fpos - bytes_read + page - buf) * 100) / file_size; + wmove(win, height - 3, width - 9); + wprintw(win, "(%3d%%)", percent); +} diff --git a/scripts/kconfig/lxdialog/util.c b/scripts/kconfig/lxdialog/util.c new file mode 100644 index 000000000..f82cebb9f --- /dev/null +++ b/scripts/kconfig/lxdialog/util.c @@ -0,0 +1,362 @@ +/* + * util.c + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" + +/* use colors by default? */ +bool use_colors = 1; + +const char *backtitle = NULL; + +/* + * Attribute values, default is for mono display + */ +chtype attributes[] = { + A_NORMAL, /* screen_attr */ + A_NORMAL, /* shadow_attr */ + A_NORMAL, /* dialog_attr */ + A_BOLD, /* title_attr */ + A_NORMAL, /* border_attr */ + A_REVERSE, /* button_active_attr */ + A_DIM, /* button_inactive_attr */ + A_REVERSE, /* button_key_active_attr */ + A_BOLD, /* button_key_inactive_attr */ + A_REVERSE, /* button_label_active_attr */ + A_NORMAL, /* button_label_inactive_attr */ + A_NORMAL, /* inputbox_attr */ + A_NORMAL, /* inputbox_border_attr */ + A_NORMAL, /* searchbox_attr */ + A_BOLD, /* searchbox_title_attr */ + A_NORMAL, /* searchbox_border_attr */ + A_BOLD, /* position_indicator_attr */ + A_NORMAL, /* menubox_attr */ + A_NORMAL, /* menubox_border_attr */ + A_NORMAL, /* item_attr */ + A_REVERSE, /* item_selected_attr */ + A_BOLD, /* tag_attr */ + A_REVERSE, /* tag_selected_attr */ + A_BOLD, /* tag_key_attr */ + A_REVERSE, /* tag_key_selected_attr */ + A_BOLD, /* check_attr */ + A_REVERSE, /* check_selected_attr */ + A_BOLD, /* uarrow_attr */ + A_BOLD /* darrow_attr */ +}; + +#include "colors.h" + +/* + * Table of color values + */ +int color_table[][3] = { + {SCREEN_FG, SCREEN_BG, SCREEN_HL}, + {SHADOW_FG, SHADOW_BG, SHADOW_HL}, + {DIALOG_FG, DIALOG_BG, DIALOG_HL}, + {TITLE_FG, TITLE_BG, TITLE_HL}, + {BORDER_FG, BORDER_BG, BORDER_HL}, + {BUTTON_ACTIVE_FG, BUTTON_ACTIVE_BG, BUTTON_ACTIVE_HL}, + {BUTTON_INACTIVE_FG, BUTTON_INACTIVE_BG, BUTTON_INACTIVE_HL}, + {BUTTON_KEY_ACTIVE_FG, BUTTON_KEY_ACTIVE_BG, BUTTON_KEY_ACTIVE_HL}, + {BUTTON_KEY_INACTIVE_FG, BUTTON_KEY_INACTIVE_BG, + BUTTON_KEY_INACTIVE_HL}, + {BUTTON_LABEL_ACTIVE_FG, BUTTON_LABEL_ACTIVE_BG, + BUTTON_LABEL_ACTIVE_HL}, + {BUTTON_LABEL_INACTIVE_FG, BUTTON_LABEL_INACTIVE_BG, + BUTTON_LABEL_INACTIVE_HL}, + {INPUTBOX_FG, INPUTBOX_BG, INPUTBOX_HL}, + {INPUTBOX_BORDER_FG, INPUTBOX_BORDER_BG, INPUTBOX_BORDER_HL}, + {SEARCHBOX_FG, SEARCHBOX_BG, SEARCHBOX_HL}, + {SEARCHBOX_TITLE_FG, SEARCHBOX_TITLE_BG, SEARCHBOX_TITLE_HL}, + {SEARCHBOX_BORDER_FG, SEARCHBOX_BORDER_BG, SEARCHBOX_BORDER_HL}, + {POSITION_INDICATOR_FG, POSITION_INDICATOR_BG, POSITION_INDICATOR_HL}, + {MENUBOX_FG, MENUBOX_BG, MENUBOX_HL}, + {MENUBOX_BORDER_FG, MENUBOX_BORDER_BG, MENUBOX_BORDER_HL}, + {ITEM_FG, ITEM_BG, ITEM_HL}, + {ITEM_SELECTED_FG, ITEM_SELECTED_BG, ITEM_SELECTED_HL}, + {TAG_FG, TAG_BG, TAG_HL}, + {TAG_SELECTED_FG, TAG_SELECTED_BG, TAG_SELECTED_HL}, + {TAG_KEY_FG, TAG_KEY_BG, TAG_KEY_HL}, + {TAG_KEY_SELECTED_FG, TAG_KEY_SELECTED_BG, TAG_KEY_SELECTED_HL}, + {CHECK_FG, CHECK_BG, CHECK_HL}, + {CHECK_SELECTED_FG, CHECK_SELECTED_BG, CHECK_SELECTED_HL}, + {UARROW_FG, UARROW_BG, UARROW_HL}, + {DARROW_FG, DARROW_BG, DARROW_HL}, +}; /* color_table */ + +/* + * Set window to attribute 'attr' + */ +void attr_clear(WINDOW * win, int height, int width, chtype attr) +{ + int i, j; + + wattrset(win, attr); + for (i = 0; i < height; i++) { + wmove(win, i, 0); + for (j = 0; j < width; j++) + waddch(win, ' '); + } + touchwin(win); +} + +void dialog_clear(void) +{ + attr_clear(stdscr, LINES, COLS, screen_attr); + /* Display background title if it exists ... - SLH */ + if (backtitle != NULL) { + int i; + + wattrset(stdscr, screen_attr); + mvwaddstr(stdscr, 0, 1, (char *)backtitle); + wmove(stdscr, 1, 1); + for (i = 1; i < COLS - 1; i++) + waddch(stdscr, ACS_HLINE); + } + wnoutrefresh(stdscr); +} + +/* + * Do some initialization for dialog + */ +void init_dialog(void) +{ + initscr(); /* Init curses */ + keypad(stdscr, TRUE); + cbreak(); + noecho(); + + if (use_colors) /* Set up colors */ + color_setup(); + + dialog_clear(); +} + +/* + * Setup for color display + */ +void color_setup(void) +{ + int i; + + if (has_colors()) { /* Terminal supports color? */ + start_color(); + + /* Initialize color pairs */ + for (i = 0; i < ATTRIBUTE_COUNT; i++) + init_pair(i + 1, color_table[i][0], color_table[i][1]); + + /* Setup color attributes */ + for (i = 0; i < ATTRIBUTE_COUNT; i++) + attributes[i] = C_ATTR(color_table[i][2], i + 1); + } +} + +/* + * End using dialog functions. + */ +void end_dialog(void) +{ + endwin(); +} + +/* Print the title of the dialog. Center the title and truncate + * tile if wider than dialog (- 2 chars). + **/ +void print_title(WINDOW *dialog, const char *title, int width) +{ + if (title) { + int tlen = MIN(width - 2, strlen(title)); + wattrset(dialog, title_attr); + mvwaddch(dialog, 0, (width - tlen) / 2 - 1, ' '); + mvwaddnstr(dialog, 0, (width - tlen)/2, title, tlen); + waddch(dialog, ' '); + } +} + +/* + * Print a string of text in a window, automatically wrap around to the + * next line if the string is too long to fit on one line. Newline + * characters '\n' are replaced by spaces. We start on a new line + * if there is no room for at least 4 nonblanks following a double-space. + */ +void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x) +{ + int newl, cur_x, cur_y; + int i, prompt_len, room, wlen; + char tempstr[MAX_LEN + 1], *word, *sp, *sp2; + + strcpy(tempstr, prompt); + + prompt_len = strlen(tempstr); + + /* + * Remove newlines + */ + for (i = 0; i < prompt_len; i++) { + if (tempstr[i] == '\n') + tempstr[i] = ' '; + } + + if (prompt_len <= width - x * 2) { /* If prompt is short */ + wmove(win, y, (width - prompt_len) / 2); + waddstr(win, tempstr); + } else { + cur_x = x; + cur_y = y; + newl = 1; + word = tempstr; + while (word && *word) { + sp = index(word, ' '); + if (sp) + *sp++ = 0; + + /* Wrap to next line if either the word does not fit, + or it is the first word of a new sentence, and it is + short, and the next word does not fit. */ + room = width - cur_x; + wlen = strlen(word); + if (wlen > room || + (newl && wlen < 4 && sp + && wlen + 1 + strlen(sp) > room + && (!(sp2 = index(sp, ' ')) + || wlen + 1 + (sp2 - sp) > room))) { + cur_y++; + cur_x = x; + } + wmove(win, cur_y, cur_x); + waddstr(win, word); + getyx(win, cur_y, cur_x); + cur_x++; + if (sp && *sp == ' ') { + cur_x++; /* double space */ + while (*++sp == ' ') ; + newl = 1; + } else + newl = 0; + word = sp; + } + } +} + +/* + * Print a button + */ +void print_button(WINDOW * win, const char *label, int y, int x, int selected) +{ + int i, temp; + + wmove(win, y, x); + wattrset(win, selected ? button_active_attr : button_inactive_attr); + waddstr(win, "<"); + temp = strspn(label, " "); + label += temp; + wattrset(win, selected ? button_label_active_attr + : button_label_inactive_attr); + for (i = 0; i < temp; i++) + waddch(win, ' '); + wattrset(win, selected ? button_key_active_attr + : button_key_inactive_attr); + waddch(win, label[0]); + wattrset(win, selected ? button_label_active_attr + : button_label_inactive_attr); + waddstr(win, (char *)label + 1); + wattrset(win, selected ? button_active_attr : button_inactive_attr); + waddstr(win, ">"); + wmove(win, y, x + temp + 1); +} + +/* + * Draw a rectangular box with line drawing characters + */ +void +draw_box(WINDOW * win, int y, int x, int height, int width, + chtype box, chtype border) +{ + int i, j; + + wattrset(win, 0); + for (i = 0; i < height; i++) { + wmove(win, y + i, x); + for (j = 0; j < width; j++) + if (!i && !j) + waddch(win, border | ACS_ULCORNER); + else if (i == height - 1 && !j) + waddch(win, border | ACS_LLCORNER); + else if (!i && j == width - 1) + waddch(win, box | ACS_URCORNER); + else if (i == height - 1 && j == width - 1) + waddch(win, box | ACS_LRCORNER); + else if (!i) + waddch(win, border | ACS_HLINE); + else if (i == height - 1) + waddch(win, box | ACS_HLINE); + else if (!j) + waddch(win, border | ACS_VLINE); + else if (j == width - 1) + waddch(win, box | ACS_VLINE); + else + waddch(win, box | ' '); + } +} + +/* + * Draw shadows along the right and bottom edge to give a more 3D look + * to the boxes + */ +void draw_shadow(WINDOW * win, int y, int x, int height, int width) +{ + int i; + + if (has_colors()) { /* Whether terminal supports color? */ + wattrset(win, shadow_attr); + wmove(win, y + height, x + 2); + for (i = 0; i < width; i++) + waddch(win, winch(win) & A_CHARTEXT); + for (i = y + 1; i < y + height + 1; i++) { + wmove(win, i, x + width); + waddch(win, winch(win) & A_CHARTEXT); + waddch(win, winch(win) & A_CHARTEXT); + } + wnoutrefresh(win); + } +} + +/* + * Return the position of the first alphabetic character in a string. + */ +int first_alpha(const char *string, const char *exempt) +{ + int i, in_paren = 0, c; + + for (i = 0; i < strlen(string); i++) { + c = tolower(string[i]); + + if (strchr("<[(", c)) + ++in_paren; + if (strchr(">])", c) && in_paren > 0) + --in_paren; + + if ((!in_paren) && isalpha(c) && strchr(exempt, c) == 0) + return i; + } + + return 0; +} diff --git a/scripts/kconfig/lxdialog/yesno.c b/scripts/kconfig/lxdialog/yesno.c new file mode 100644 index 000000000..cb2568aae --- /dev/null +++ b/scripts/kconfig/lxdialog/yesno.c @@ -0,0 +1,102 @@ +/* + * yesno.c -- implements the yes/no box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "dialog.h" + +/* + * Display termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 10; + int y = height - 2; + + print_button(dialog, " Yes ", y, x, selected == 0); + print_button(dialog, " No ", y, x + 13, selected == 1); + + wmove(dialog, y, x + 1 + 13 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with two buttons - Yes and No + */ +int dialog_yesno(const char *title, const char *prompt, int height, int width) +{ + int i, x, y, key = 0, button = 0; + WINDOW *dialog; + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dialog_attr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + print_buttons(dialog, height, width, 0); + + while (key != ESC) { + key = wgetch(dialog); + switch (key) { + case 'Y': + case 'y': + delwin(dialog); + return 0; + case 'N': + case 'n': + delwin(dialog); + return 1; + + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) ? 1 : (button > 1 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(dialog); + break; + case ' ': + case '\n': + delwin(dialog); + return button; + case ESC: + break; + } + } + + delwin(dialog); + return -1; /* ESC pressed */ +} diff --git a/scripts/kconfig/mconf.c b/scripts/kconfig/mconf.c new file mode 100644 index 000000000..31a1f4cd9 --- /dev/null +++ b/scripts/kconfig/mconf.c @@ -0,0 +1,1098 @@ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + * + * Introduced single menu mode (show all sub-menus in one large tree). + * 2002-11-06 Petr Baudis <pasky@ucw.cz> + * + * i18n, 2005, Arnaldo Carvalho de Melo <acme@conectiva.com.br> + */ + +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> +#include <locale.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +static char menu_backtitle[128]; +static const char mconf_readme[] = N_( +"Overview\n" +"--------\n" +"Some kernel features may be built directly into the kernel.\n" +"Some may be made into loadable runtime modules. Some features\n" +"may be completely removed altogether. There are also certain\n" +"kernel parameters which are not really features, but must be\n" +"entered in as decimal or hexadecimal numbers or possibly text.\n" +"\n" +"Menu items beginning with [*], <M> or [ ] represent features\n" +"configured to be built in, modularized or removed respectively.\n" +"Pointed brackets <> represent module capable features.\n" +"\n" +"To change any of these features, highlight it with the cursor\n" +"keys and press <Y> to build it in, <M> to make it a module or\n" +"<N> to removed it. You may also press the <Space Bar> to cycle\n" +"through the available options (ie. Y->N->M->Y).\n" +"\n" +"Some additional keyboard hints:\n" +"\n" +"Menus\n" +"----------\n" +"o Use the Up/Down arrow keys (cursor keys) to highlight the item\n" +" you wish to change or submenu wish to select and press <Enter>.\n" +" Submenus are designated by \"--->\".\n" +"\n" +" Shortcut: Press the option's highlighted letter (hotkey).\n" +" Pressing a hotkey more than once will sequence\n" +" through all visible items which use that hotkey.\n" +"\n" +" You may also use the <PAGE UP> and <PAGE DOWN> keys to scroll\n" +" unseen options into view.\n" +"\n" +"o To exit a menu use the cursor keys to highlight the <Exit> button\n" +" and press <ENTER>.\n" +"\n" +" Shortcut: Press <ESC><ESC> or <E> or <X> if there is no hotkey\n" +" using those letters. You may press a single <ESC>, but\n" +" there is a delayed response which you may find annoying.\n" +"\n" +" Also, the <TAB> and cursor keys will cycle between <Select>,\n" +" <Exit> and <Help>\n" +"\n" +"o To get help with an item, use the cursor keys to highlight <Help>\n" +" and Press <ENTER>.\n" +"\n" +" Shortcut: Press <H> or <?>.\n" +"\n" +"\n" +"Radiolists (Choice lists)\n" +"-----------\n" +"o Use the cursor keys to select the option you wish to set and press\n" +" <S> or the <SPACE BAR>.\n" +"\n" +" Shortcut: Press the first letter of the option you wish to set then\n" +" press <S> or <SPACE BAR>.\n" +"\n" +"o To see available help for the item, use the cursor keys to highlight\n" +" <Help> and Press <ENTER>.\n" +"\n" +" Shortcut: Press <H> or <?>.\n" +"\n" +" Also, the <TAB> and cursor keys will cycle between <Select> and\n" +" <Help>\n" +"\n" +"\n" +"Data Entry\n" +"-----------\n" +"o Enter the requested information and press <ENTER>\n" +" If you are entering hexadecimal values, it is not necessary to\n" +" add the '0x' prefix to the entry.\n" +"\n" +"o For help, use the <TAB> or cursor keys to highlight the help option\n" +" and press <ENTER>. You can try <TAB><H> as well.\n" +"\n" +"\n" +"Text Box (Help Window)\n" +"--------\n" +"o Use the cursor keys to scroll up/down/left/right. The VI editor\n" +" keys h,j,k,l function here as do <SPACE BAR> and <B> for those\n" +" who are familiar with less and lynx.\n" +"\n" +"o Press <E>, <X>, <Enter> or <Esc><Esc> to exit.\n" +"\n" +"\n" +"Alternate Configuration Files\n" +"-----------------------------\n" +"Menuconfig supports the use of alternate configuration files for\n" +"those who, for various reasons, find it necessary to switch\n" +"between different busybox configurations.\n" +"\n" +"At the end of the main menu you will find two options. One is\n" +"for saving the current configuration to a file of your choosing.\n" +"The other option is for loading a previously saved alternate\n" +"configuration.\n" +"\n" +"Even if you don't use alternate configuration files, but you\n" +"find during a Menuconfig session that you have completely messed\n" +"up your settings, you may use the \"Load Alternate...\" option to\n" +"restore your previously saved settings from \".config\" without\n" +"restarting Menuconfig.\n" +"\n" +"Other information\n" +"-----------------\n" +"If you use Menuconfig in an XTERM window make sure you have your\n" +"$TERM variable set to point to a xterm definition which supports color.\n" +"Otherwise, Menuconfig will look rather bad. Menuconfig will not\n" +"display correctly in a RXVT window because rxvt displays only one\n" +"intensity of color, bright.\n" +"\n" +"Menuconfig will display larger menus on screens or xterms which are\n" +"set to display more than the standard 25 row by 80 column geometry.\n" +"In order for this to work, the \"stty size\" command must be able to\n" +"display the screen's current row and column geometry. I STRONGLY\n" +"RECOMMEND that you make sure you do NOT have the shell variables\n" +"LINES and COLUMNS exported into your environment. Some distributions\n" +"export those variables via /etc/profile. Some ncurses programs can\n" +"become confused when those variables (LINES & COLUMNS) don't reflect\n" +"the true screen size.\n" +"\n" +"Optional personality available\n" +"------------------------------\n" +"If you prefer to have all of the kernel options listed in a single\n" +"menu, rather than the default multimenu hierarchy, run the menuconfig\n" +"with MENUCONFIG_MODE environment variable set to single_menu. Example:\n" +"\n" +"make MENUCONFIG_MODE=single_menu menuconfig\n" +"\n" +"<Enter> will then unroll the appropriate category, or enfold it if it\n" +"is already unrolled.\n" +"\n" +"Note that this mode can eventually be a little more CPU expensive\n" +"(especially with a larger number of unrolled categories) than the\n" +"default mode.\n"), +menu_instructions[] = N_( + "Arrow keys navigate the menu. " + "<Enter> selects submenus --->. " + "Highlighted letters are hotkeys. " + "Pressing <Y> includes, <N> excludes, <M> modularizes features. " + "Press <Esc><Esc> to exit, <?> for Help, </> for Search. " + "Legend: [*] built-in [ ] excluded <M> module < > module capable"), +radiolist_instructions[] = N_( + "Use the arrow keys to navigate this window or " + "press the hotkey of the item you wish to select " + "followed by the <SPACE BAR>. " + "Press <?> for additional information about this option."), +inputbox_instructions_int[] = N_( + "Please enter a decimal value. " + "Fractions will not be accepted. " + "Use the <TAB> key to move from the input field to the buttons below it."), +inputbox_instructions_hex[] = N_( + "Please enter a hexadecimal value. " + "Use the <TAB> key to move from the input field to the buttons below it."), +inputbox_instructions_string[] = N_( + "Please enter a string value. " + "Use the <TAB> key to move from the input field to the buttons below it."), +setmod_text[] = N_( + "This feature depends on another which has been configured as a module.\n" + "As a result, this feature will be built as a module."), +nohelp_text[] = N_( + "There is no help available for this kernel option.\n"), +load_config_text[] = N_( + "Enter the name of the configuration file you wish to load. " + "Accept the name shown to restore the configuration you " + "last retrieved. Leave blank to abort."), +load_config_help[] = N_( + "\n" + "For various reasons, one may wish to keep several different kernel\n" + "configurations available on a single machine.\n" + "\n" + "If you have saved a previous configuration in a file other than the\n" + "kernel's default, entering the name of the file here will allow you\n" + "to modify that configuration.\n" + "\n" + "If you are uncertain, then you have probably never used alternate\n" + "configuration files. You should therefor leave this blank to abort.\n"), +save_config_text[] = N_( + "Enter a filename to which this configuration should be saved " + "as an alternate. Leave blank to abort."), +save_config_help[] = N_( + "\n" + "For various reasons, one may wish to keep different kernel\n" + "configurations available on a single machine.\n" + "\n" + "Entering a file name here will allow you to later retrieve, modify\n" + "and use the current configuration as an alternate to whatever\n" + "configuration options you have selected at that time.\n" + "\n" + "If you are uncertain what all this means then you should probably\n" + "leave this blank.\n"), +search_help[] = N_( + "\n" + "Search for CONFIG_ symbols and display their relations.\n" + "Regular expressions are allowed.\n" + "Example: search for \"^FOO\"\n" + "Result:\n" + "-----------------------------------------------------------------\n" + "Symbol: FOO [=m]\n" + "Prompt: Foo bus is used to drive the bar HW\n" + "Defined at drivers/pci/Kconfig:47\n" + "Depends on: X86_LOCAL_APIC && X86_IO_APIC || IA64\n" + "Location:\n" + " -> Bus options (PCI, PCMCIA, EISA, MCA, ISA)\n" + " -> PCI support (PCI [=y])\n" + " -> PCI access mode (<choice> [=y])\n" + "Selects: LIBCRC32\n" + "Selected by: BAR\n" + "-----------------------------------------------------------------\n" + "o The line 'Prompt:' shows the text used in the menu structure for\n" + " this CONFIG_ symbol\n" + "o The 'Defined at' line tell at what file / line number the symbol\n" + " is defined\n" + "o The 'Depends on:' line tell what symbols needs to be defined for\n" + " this symbol to be visible in the menu (selectable)\n" + "o The 'Location:' lines tell where in the menu structure this symbol\n" + " is located\n" + " A location followed by a [=y] indicate that this is a selectable\n" + " menu item - and current value is displayed inside brackets.\n" + "o The 'Selects:' line tell what symbol will be automatically\n" + " selected if this symbol is selected (y or m)\n" + "o The 'Selected by' line tell what symbol has selected this symbol\n" + "\n" + "Only relevant lines are shown.\n" + "\n\n" + "Search examples:\n" + "Examples: USB => find all CONFIG_ symbols containing USB\n" + " ^USB => find all CONFIG_ symbols starting with USB\n" + " USB$ => find all CONFIG_ symbols ending with USB\n" + "\n"); + +static char buf[4096], *bufptr = buf; +static char input_buf[4096]; +static char filename[PATH_MAX+1] = ".config"; +static char *args[1024], **argptr = args; +static int indent; +static struct termios ios_org; +static int rows = 0, cols = 0; +static struct menu *current_menu; +static int child_count; +static int do_resize; +static int single_menu_mode; + +static void conf(struct menu *menu); +static void conf_choice(struct menu *menu); +static void conf_string(struct menu *menu); +static void conf_load(void); +static void conf_save(void); +static void show_textbox(const char *title, const char *text, int r, int c); +static void show_helptext(const char *title, const char *text); +static void show_help(struct menu *menu); +static void show_file(const char *filename, const char *title, int r, int c); + +static void cprint_init(void); +static int cprint1(const char *fmt, ...); +static void cprint_done(void); +static int cprint(const char *fmt, ...); + +static void init_wsize(void) +{ + struct winsize ws; + char *env; + + if (!ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)) { + rows = ws.ws_row; + cols = ws.ws_col; + } + + if (!rows) { + env = getenv("LINES"); + if (env) + rows = atoi(env); + if (!rows) + rows = 24; + } + if (!cols) { + env = getenv("COLUMNS"); + if (env) + cols = atoi(env); + if (!cols) + cols = 80; + } + + if (rows < 19 || cols < 80) { + fprintf(stderr, N_("Your display is too small to run Menuconfig!\n")); + fprintf(stderr, N_("It must be at least 19 lines by 80 columns.\n")); + exit(1); + } + + rows -= 4; + cols -= 5; +} + +static void cprint_init(void) +{ + bufptr = buf; + argptr = args; + memset(args, 0, sizeof(args)); + indent = 0; + child_count = 0; + cprint("./scripts/kconfig/lxdialog/lxdialog"); + cprint("--backtitle"); + cprint(menu_backtitle); +} + +static int cprint1(const char *fmt, ...) +{ + va_list ap; + int res; + + if (!*argptr) + *argptr = bufptr; + va_start(ap, fmt); + res = vsprintf(bufptr, fmt, ap); + va_end(ap); + bufptr += res; + + return res; +} + +static void cprint_done(void) +{ + *bufptr++ = 0; + argptr++; +} + +static int cprint(const char *fmt, ...) +{ + va_list ap; + int res; + + *argptr++ = bufptr; + va_start(ap, fmt); + res = vsprintf(bufptr, fmt, ap); + va_end(ap); + bufptr += res; + *bufptr++ = 0; + + return res; +} + +static void get_prompt_str(struct gstr *r, struct property *prop) +{ + int i, j; + struct menu *submenu[8], *menu; + + str_printf(r, "Prompt: %s\n", prop->text); + str_printf(r, " Defined at %s:%d\n", prop->menu->file->name, + prop->menu->lineno); + if (!expr_is_yes(prop->visible.expr)) { + str_append(r, " Depends on: "); + expr_gstr_print(prop->visible.expr, r); + str_append(r, "\n"); + } + menu = prop->menu->parent; + for (i = 0; menu != &rootmenu && i < 8; menu = menu->parent) + submenu[i++] = menu; + if (i > 0) { + str_printf(r, " Location:\n"); + for (j = 4; --i >= 0; j += 2) { + menu = submenu[i]; + str_printf(r, "%*c-> %s", j, ' ', menu_get_prompt(menu)); + if (menu->sym) { + str_printf(r, " (%s [=%s])", menu->sym->name ? + menu->sym->name : "<choice>", + sym_get_string_value(menu->sym)); + } + str_append(r, "\n"); + } + } +} + +static void get_symbol_str(struct gstr *r, struct symbol *sym) +{ + bool hit; + struct property *prop; + + str_printf(r, "Symbol: %s [=%s]\n", sym->name, + sym_get_string_value(sym)); + for_all_prompts(sym, prop) + get_prompt_str(r, prop); + hit = false; + for_all_properties(sym, prop, P_SELECT) { + if (!hit) { + str_append(r, " Selects: "); + hit = true; + } else + str_printf(r, " && "); + expr_gstr_print(prop->expr, r); + } + if (hit) + str_append(r, "\n"); + if (sym->rev_dep.expr) { + str_append(r, " Selected by: "); + expr_gstr_print(sym->rev_dep.expr, r); + str_append(r, "\n"); + } + str_append(r, "\n\n"); +} + +static struct gstr get_relations_str(struct symbol **sym_arr) +{ + struct symbol *sym; + struct gstr res = str_new(); + int i; + + for (i = 0; sym_arr && (sym = sym_arr[i]); i++) + get_symbol_str(&res, sym); + if (!i) + str_append(&res, "No matches found.\n"); + return res; +} + +pid_t pid; + +static void winch_handler(int sig) +{ + if (!do_resize) { + kill(pid, SIGINT); + do_resize = 1; + } +} + +static int exec_conf(void) +{ + int pipefd[2], stat, size; + struct sigaction sa; + sigset_t sset, osset; + + sigemptyset(&sset); + sigaddset(&sset, SIGINT); + sigprocmask(SIG_BLOCK, &sset, &osset); + + signal(SIGINT, SIG_DFL); + + sa.sa_handler = winch_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGWINCH, &sa, NULL); + + *argptr++ = NULL; + + pipe(pipefd); + pid = fork(); + if (pid == 0) { + sigprocmask(SIG_SETMASK, &osset, NULL); + dup2(pipefd[1], 2); + close(pipefd[0]); + close(pipefd[1]); + execv(args[0], args); + _exit(EXIT_FAILURE); + } + + close(pipefd[1]); + bufptr = input_buf; + while (1) { + size = input_buf + sizeof(input_buf) - bufptr; + size = read(pipefd[0], bufptr, size); + if (size <= 0) { + if (size < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + perror("read"); + } + break; + } + bufptr += size; + } + *bufptr++ = 0; + close(pipefd[0]); + waitpid(pid, &stat, 0); + + if (do_resize) { + init_wsize(); + do_resize = 0; + sigprocmask(SIG_SETMASK, &osset, NULL); + return -1; + } + if (WIFSIGNALED(stat)) { + printf("\finterrupted(%d)\n", WTERMSIG(stat)); + exit(1); + } +#if 0 + printf("\fexit state: %d\nexit data: '%s'\n", WEXITSTATUS(stat), input_buf); + sleep(1); +#endif + sigpending(&sset); + if (sigismember(&sset, SIGINT)) { + printf("\finterrupted\n"); + exit(1); + } + sigprocmask(SIG_SETMASK, &osset, NULL); + + return WEXITSTATUS(stat); +} + +static void search_conf(void) +{ + struct symbol **sym_arr; + int stat; + struct gstr res; + +again: + cprint_init(); + cprint("--title"); + cprint(_("Search Configuration Parameter")); + cprint("--inputbox"); + cprint(_("Enter CONFIG_ (sub)string to search for (omit CONFIG_)")); + cprint("10"); + cprint("75"); + cprint(""); + stat = exec_conf(); + if (stat < 0) + goto again; + switch (stat) { + case 0: + break; + case 1: + show_helptext(_("Search Configuration"), search_help); + goto again; + default: + return; + } + + sym_arr = sym_re_search(input_buf); + res = get_relations_str(sym_arr); + free(sym_arr); + show_textbox(_("Search Results"), str_get(&res), 0, 0); + str_free(&res); +} + +static void build_conf(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + int type, tmp, doint = 2; + tristate val; + char ch; + + if (!menu_is_visible(menu)) + return; + + sym = menu->sym; + prop = menu->prompt; + if (!sym) { + if (prop && menu != current_menu) { + const char *prompt = menu_get_prompt(menu); + switch (prop->type) { + case P_MENU: + child_count++; + cprint("m%p", menu); + + if (single_menu_mode) { + cprint1("%s%*c%s", + menu->data ? "-->" : "++>", + indent + 1, ' ', prompt); + } else + cprint1(" %*c%s --->", indent + 1, ' ', prompt); + + cprint_done(); + if (single_menu_mode && menu->data) + goto conf_childs; + return; + default: + if (prompt) { + child_count++; + cprint(":%p", menu); + cprint("---%*c%s", indent + 1, ' ', prompt); + } + } + } else + doint = 0; + goto conf_childs; + } + + type = sym_get_type(sym); + if (sym_is_choice(sym)) { + struct symbol *def_sym = sym_get_choice_value(sym); + struct menu *def_menu = NULL; + + child_count++; + for (child = menu->list; child; child = child->next) { + if (menu_is_visible(child) && child->sym == def_sym) + def_menu = child; + } + + val = sym_get_tristate_value(sym); + if (sym_is_changable(sym)) { + cprint("t%p", menu); + switch (type) { + case S_BOOLEAN: + cprint1("[%c]", val == no ? ' ' : '*'); + break; + case S_TRISTATE: + switch (val) { + case yes: ch = '*'; break; + case mod: ch = 'M'; break; + default: ch = ' '; break; + } + cprint1("<%c>", ch); + break; + } + } else { + cprint("%c%p", def_menu ? 't' : ':', menu); + cprint1(" "); + } + + cprint1("%*c%s", indent + 1, ' ', menu_get_prompt(menu)); + if (val == yes) { + if (def_menu) { + cprint1(" (%s)", menu_get_prompt(def_menu)); + cprint1(" --->"); + cprint_done(); + if (def_menu->list) { + indent += 2; + build_conf(def_menu); + indent -= 2; + } + } else + cprint_done(); + return; + } + cprint_done(); + } else { + if (menu == current_menu) { + cprint(":%p", menu); + cprint("---%*c%s", indent + 1, ' ', menu_get_prompt(menu)); + goto conf_childs; + } + child_count++; + val = sym_get_tristate_value(sym); + if (sym_is_choice_value(sym) && val == yes) { + cprint(":%p", menu); + cprint1(" "); + } else { + switch (type) { + case S_BOOLEAN: + cprint("t%p", menu); + if (sym_is_changable(sym)) + cprint1("[%c]", val == no ? ' ' : '*'); + else + cprint1("---"); + break; + case S_TRISTATE: + cprint("t%p", menu); + switch (val) { + case yes: ch = '*'; break; + case mod: ch = 'M'; break; + default: ch = ' '; break; + } + if (sym_is_changable(sym)) + cprint1("<%c>", ch); + else + cprint1("---"); + break; + default: + cprint("s%p", menu); + tmp = cprint1("(%s)", sym_get_string_value(sym)); + tmp = indent - tmp + 4; + if (tmp < 0) + tmp = 0; + cprint1("%*c%s%s", tmp, ' ', menu_get_prompt(menu), + (sym_has_value(sym) || !sym_is_changable(sym)) ? + "" : " (NEW)"); + cprint_done(); + goto conf_childs; + } + } + cprint1("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu), + (sym_has_value(sym) || !sym_is_changable(sym)) ? + "" : " (NEW)"); + if (menu->prompt->type == P_MENU) { + cprint1(" --->"); + cprint_done(); + return; + } + cprint_done(); + } + +conf_childs: + indent += doint; + for (child = menu->list; child; child = child->next) + build_conf(child); + indent -= doint; +} + +static void conf(struct menu *menu) +{ + struct menu *submenu; + const char *prompt = menu_get_prompt(menu); + struct symbol *sym; + char active_entry[40]; + int stat, type, i; + + unlink("lxdialog.scrltmp"); + active_entry[0] = 0; + while (1) { + cprint_init(); + cprint("--title"); + cprint("%s", prompt ? prompt : _("Main Menu")); + cprint("--menu"); + cprint(_(menu_instructions)); + cprint("%d", rows); + cprint("%d", cols); + cprint("%d", rows - 10); + cprint("%s", active_entry); + current_menu = menu; + build_conf(menu); + if (!child_count) + break; + if (menu == &rootmenu) { + cprint(":"); + cprint("--- "); + cprint("L"); + cprint(_(" Load an Alternate Configuration File")); + cprint("S"); + cprint(_(" Save Configuration to an Alternate File")); + } + stat = exec_conf(); + if (stat < 0) + continue; + + if (stat == 1 || stat == 255) + break; + + type = input_buf[0]; + if (!type) + continue; + + for (i = 0; input_buf[i] && !isspace(input_buf[i]); i++) + ; + if (i >= sizeof(active_entry)) + i = sizeof(active_entry) - 1; + input_buf[i] = 0; + strcpy(active_entry, input_buf); + + sym = NULL; + submenu = NULL; + if (sscanf(input_buf + 1, "%p", &submenu) == 1) + sym = submenu->sym; + + switch (stat) { + case 0: + switch (type) { + case 'm': + if (single_menu_mode) + submenu->data = (void *) (long) !submenu->data; + else + conf(submenu); + break; + case 't': + if (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes) + conf_choice(submenu); + else if (submenu->prompt->type == P_MENU) + conf(submenu); + break; + case 's': + conf_string(submenu); + break; + case 'L': + conf_load(); + break; + case 'S': + conf_save(); + break; + } + break; + case 2: + if (sym) + show_help(submenu); + else + show_helptext("README", _(mconf_readme)); + break; + case 3: + if (type == 't') { + if (sym_set_tristate_value(sym, yes)) + break; + if (sym_set_tristate_value(sym, mod)) + show_textbox(NULL, setmod_text, 6, 74); + } + break; + case 4: + if (type == 't') + sym_set_tristate_value(sym, no); + break; + case 5: + if (type == 't') + sym_set_tristate_value(sym, mod); + break; + case 6: + if (type == 't') + sym_toggle_tristate_value(sym); + else if (type == 'm') + conf(submenu); + break; + case 7: + search_conf(); + break; + } + } +} + +static void show_textbox(const char *title, const char *text, int r, int c) +{ + int fd; + + fd = creat(".help.tmp", 0777); + write(fd, text, strlen(text)); + close(fd); + show_file(".help.tmp", title, r, c); + unlink(".help.tmp"); +} + +static void show_helptext(const char *title, const char *text) +{ + show_textbox(title, text, 0, 0); +} + +static void show_help(struct menu *menu) +{ + struct gstr help = str_new(); + struct symbol *sym = menu->sym; + + if (sym->help) + { + if (sym->name) { + str_printf(&help, "CONFIG_%s:\n\n", sym->name); + str_append(&help, _(sym->help)); + str_append(&help, "\n"); + } + } else { + str_append(&help, nohelp_text); + } + get_symbol_str(&help, sym); + show_helptext(menu_get_prompt(menu), str_get(&help)); + str_free(&help); +} + +static void show_file(const char *filename, const char *title, int r, int c) +{ + do { + cprint_init(); + if (title) { + cprint("--title"); + cprint("%s", title); + } + cprint("--textbox"); + cprint("%s", filename); + cprint("%d", r ? r : rows); + cprint("%d", c ? c : cols); + } while (exec_conf() < 0); +} + +static void conf_choice(struct menu *menu) +{ + const char *prompt = menu_get_prompt(menu); + struct menu *child; + struct symbol *active; + int stat; + + active = sym_get_choice_value(menu->sym); + while (1) { + cprint_init(); + cprint("--title"); + cprint("%s", prompt ? prompt : _("Main Menu")); + cprint("--radiolist"); + cprint(_(radiolist_instructions)); + cprint("15"); + cprint("70"); + cprint("6"); + + current_menu = menu; + for (child = menu->list; child; child = child->next) { + if (!menu_is_visible(child)) + continue; + cprint("%p", child); + cprint("%s", menu_get_prompt(child)); + if (child->sym == sym_get_choice_value(menu->sym)) + cprint("ON"); + else if (child->sym == active) + cprint("SELECTED"); + else + cprint("OFF"); + } + + stat = exec_conf(); + switch (stat) { + case 0: + if (sscanf(input_buf, "%p", &child) != 1) + break; + sym_set_tristate_value(child->sym, yes); + return; + case 1: + if (sscanf(input_buf, "%p", &child) == 1) { + show_help(child); + active = child->sym; + } else + show_help(menu); + break; + case 255: + return; + } + } +} + +static void conf_string(struct menu *menu) +{ + const char *prompt = menu_get_prompt(menu); + int stat; + + while (1) { + cprint_init(); + cprint("--title"); + cprint("%s", prompt ? prompt : _("Main Menu")); + cprint("--inputbox"); + switch (sym_get_type(menu->sym)) { + case S_INT: + cprint(_(inputbox_instructions_int)); + break; + case S_HEX: + cprint(_(inputbox_instructions_hex)); + break; + case S_STRING: + cprint(_(inputbox_instructions_string)); + break; + default: + /* panic? */; + } + cprint("10"); + cprint("75"); + cprint("%s", sym_get_string_value(menu->sym)); + stat = exec_conf(); + switch (stat) { + case 0: + if (sym_set_string_value(menu->sym, input_buf)) + return; + show_textbox(NULL, _("You have made an invalid entry."), 5, 43); + break; + case 1: + show_help(menu); + break; + case 255: + return; + } + } +} + +static void conf_load(void) +{ + int stat; + + while (1) { + cprint_init(); + cprint("--inputbox"); + cprint(load_config_text); + cprint("11"); + cprint("55"); + cprint("%s", filename); + stat = exec_conf(); + switch(stat) { + case 0: + if (!input_buf[0]) + return; + if (!conf_read(input_buf)) + return; + show_textbox(NULL, _("File does not exist!"), 5, 38); + break; + case 1: + show_helptext(_("Load Alternate Configuration"), load_config_help); + break; + case 255: + return; + } + } +} + +static void conf_save(void) +{ + int stat; + + while (1) { + cprint_init(); + cprint("--inputbox"); + cprint(save_config_text); + cprint("11"); + cprint("55"); + cprint("%s", filename); + stat = exec_conf(); + switch(stat) { + case 0: + if (!input_buf[0]) + return; + if (!conf_write(input_buf)) + return; + show_textbox(NULL, _("Can't create file! Probably a nonexistent directory."), 5, 60); + break; + case 1: + show_helptext(_("Save Alternate Configuration"), save_config_help); + break; + case 255: + return; + } + } +} + +static void conf_cleanup(void) +{ + tcsetattr(1, TCSAFLUSH, &ios_org); + unlink(".help.tmp"); + unlink("lxdialog.scrltmp"); +} + +int main(int ac, char **av) +{ + struct symbol *sym; + char *mode; + int stat; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + conf_parse(av[1]); + conf_read(NULL); + + sym = sym_lookup("KERNELVERSION", 0); + sym_calc_value(sym); + sprintf(menu_backtitle, _("Linux Kernel v%s Configuration"), + sym_get_string_value(sym)); + + mode = getenv("MENUCONFIG_MODE"); + if (mode) { + if (!strcasecmp(mode, "single_menu")) + single_menu_mode = 1; + } + + tcgetattr(1, &ios_org); + atexit(conf_cleanup); + init_wsize(); + conf(&rootmenu); + + do { + cprint_init(); + cprint("--yesno"); + cprint(_("Do you wish to save your new busybox configuration?")); + cprint("5"); + cprint("60"); + stat = exec_conf(); + } while (stat < 0); + + if (stat == 0) { + if (conf_write(NULL)) { + fprintf(stderr, _("\n\n" + "Error during writing of the busybox configuration.\n" + "Your busybox configuration changes were NOT saved." + "\n\n")); + return 1; + } + printf(_("\n\n" + "*** End of busybox configuration.\n" + "*** Execute 'make' to build the kernel or try 'make help'." + "\n\n")); + } else { + fprintf(stderr, _("\n\n" + "Your busybox configuration changes were NOT saved." + "\n\n")); + } + + return 0; +} diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c new file mode 100644 index 000000000..0fce20cb7 --- /dev/null +++ b/scripts/kconfig/menu.c @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <stdlib.h> +#include <string.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +struct menu rootmenu; +static struct menu **last_entry_ptr; + +struct file *file_list; +struct file *current_file; + +static void menu_warn(struct menu *menu, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", menu->file->name, menu->lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +static void prop_warn(struct property *prop, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", prop->file->name, prop->lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +void menu_init(void) +{ + current_entry = current_menu = &rootmenu; + last_entry_ptr = &rootmenu.list; +} + +void menu_add_entry(struct symbol *sym) +{ + struct menu *menu; + + menu = malloc(sizeof(*menu)); + memset(menu, 0, sizeof(*menu)); + menu->sym = sym; + menu->parent = current_menu; + menu->file = current_file; + menu->lineno = zconf_lineno(); + + *last_entry_ptr = menu; + last_entry_ptr = &menu->next; + current_entry = menu; +} + +void menu_end_entry(void) +{ +} + +struct menu *menu_add_menu(void) +{ + menu_end_entry(); + last_entry_ptr = ¤t_entry->list; + return current_menu = current_entry; +} + +void menu_end_menu(void) +{ + last_entry_ptr = ¤t_menu->next; + current_menu = current_menu->parent; +} + +struct expr *menu_check_dep(struct expr *e) +{ + if (!e) + return e; + + switch (e->type) { + case E_NOT: + e->left.expr = menu_check_dep(e->left.expr); + break; + case E_OR: + case E_AND: + e->left.expr = menu_check_dep(e->left.expr); + e->right.expr = menu_check_dep(e->right.expr); + break; + case E_SYMBOL: + /* change 'm' into 'm' && MODULES */ + if (e->left.sym == &symbol_mod) + return expr_alloc_and(e, expr_alloc_symbol(modules_sym)); + break; + default: + break; + } + return e; +} + +void menu_add_dep(struct expr *dep) +{ + current_entry->dep = expr_alloc_and(current_entry->dep, menu_check_dep(dep)); +} + +void menu_set_type(int type) +{ + struct symbol *sym = current_entry->sym; + + if (sym->type == type) + return; + if (sym->type == S_UNKNOWN) { + sym->type = type; + return; + } + menu_warn(current_entry, "type of '%s' redefined from '%s' to '%s'\n", + sym->name ? sym->name : "<choice>", + sym_type_name(sym->type), sym_type_name(type)); +} + +struct property *menu_add_prop(enum prop_type type, char *prompt, struct expr *expr, struct expr *dep) +{ + struct property *prop = prop_alloc(type, current_entry->sym); + + prop->menu = current_entry; + prop->text = prompt; + prop->expr = expr; + prop->visible.expr = menu_check_dep(dep); + + if (prompt) { + if (current_entry->prompt) + menu_warn(current_entry, "prompt redefined\n"); + current_entry->prompt = prop; + } + + return prop; +} + +struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep) +{ + return menu_add_prop(type, prompt, NULL, dep); +} + +void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep) +{ + menu_add_prop(type, NULL, expr, dep); +} + +void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep) +{ + menu_add_prop(type, NULL, expr_alloc_symbol(sym), dep); +} + +static int menu_range_valid_sym(struct symbol *sym, struct symbol *sym2) +{ + return sym2->type == S_INT || sym2->type == S_HEX || + (sym2->type == S_UNKNOWN && sym_string_valid(sym, sym2->name)); +} + +void sym_check_prop(struct symbol *sym) +{ + struct property *prop; + struct symbol *sym2; + for (prop = sym->prop; prop; prop = prop->next) { + switch (prop->type) { + case P_DEFAULT: + if ((sym->type == S_STRING || sym->type == S_INT || sym->type == S_HEX) && + prop->expr->type != E_SYMBOL) + prop_warn(prop, + "default for config symbol '%'" + " must be a single symbol", sym->name); + break; + case P_SELECT: + sym2 = prop_get_symbol(prop); + if (sym->type != S_BOOLEAN && sym->type != S_TRISTATE) + prop_warn(prop, + "config symbol '%s' uses select, but is " + "not boolean or tristate", sym->name); + else if (sym2->type == S_UNKNOWN) + prop_warn(prop, + "'select' used by config symbol '%s' " + "refer to undefined symbol '%s'", + sym->name, sym2->name); + else if (sym2->type != S_BOOLEAN && sym2->type != S_TRISTATE) + prop_warn(prop, + "'%s' has wrong type. 'select' only " + "accept arguments of boolean and " + "tristate type", sym2->name); + break; + case P_RANGE: + if (sym->type != S_INT && sym->type != S_HEX) + prop_warn(prop, "range is only allowed " + "for int or hex symbols"); + if (!menu_range_valid_sym(sym, prop->expr->left.sym) || + !menu_range_valid_sym(sym, prop->expr->right.sym)) + prop_warn(prop, "range is invalid"); + break; + default: + ; + } + } +} + +void menu_finalize(struct menu *parent) +{ + struct menu *menu, *last_menu; + struct symbol *sym; + struct property *prop; + struct expr *parentdep, *basedep, *dep, *dep2, **ep; + + sym = parent->sym; + if (parent->list) { + if (sym && sym_is_choice(sym)) { + /* find the first choice value and find out choice type */ + for (menu = parent->list; menu; menu = menu->next) { + if (menu->sym) { + current_entry = parent; + menu_set_type(menu->sym->type); + current_entry = menu; + menu_set_type(sym->type); + break; + } + } + parentdep = expr_alloc_symbol(sym); + } else if (parent->prompt) + parentdep = parent->prompt->visible.expr; + else + parentdep = parent->dep; + + for (menu = parent->list; menu; menu = menu->next) { + basedep = expr_transform(menu->dep); + basedep = expr_alloc_and(expr_copy(parentdep), basedep); + basedep = expr_eliminate_dups(basedep); + menu->dep = basedep; + if (menu->sym) + prop = menu->sym->prop; + else + prop = menu->prompt; + for (; prop; prop = prop->next) { + if (prop->menu != menu) + continue; + dep = expr_transform(prop->visible.expr); + dep = expr_alloc_and(expr_copy(basedep), dep); + dep = expr_eliminate_dups(dep); + if (menu->sym && menu->sym->type != S_TRISTATE) + dep = expr_trans_bool(dep); + prop->visible.expr = dep; + if (prop->type == P_SELECT) { + struct symbol *es = prop_get_symbol(prop); + es->rev_dep.expr = expr_alloc_or(es->rev_dep.expr, + expr_alloc_and(expr_alloc_symbol(menu->sym), expr_copy(dep))); + } + } + } + for (menu = parent->list; menu; menu = menu->next) + menu_finalize(menu); + } else if (sym) { + basedep = parent->prompt ? parent->prompt->visible.expr : NULL; + basedep = expr_trans_compare(basedep, E_UNEQUAL, &symbol_no); + basedep = expr_eliminate_dups(expr_transform(basedep)); + last_menu = NULL; + for (menu = parent->next; menu; menu = menu->next) { + dep = menu->prompt ? menu->prompt->visible.expr : menu->dep; + if (!expr_contains_symbol(dep, sym)) + break; + if (expr_depends_symbol(dep, sym)) + goto next; + dep = expr_trans_compare(dep, E_UNEQUAL, &symbol_no); + dep = expr_eliminate_dups(expr_transform(dep)); + dep2 = expr_copy(basedep); + expr_eliminate_eq(&dep, &dep2); + expr_free(dep); + if (!expr_is_yes(dep2)) { + expr_free(dep2); + break; + } + expr_free(dep2); + next: + menu_finalize(menu); + menu->parent = parent; + last_menu = menu; + } + if (last_menu) { + parent->list = parent->next; + parent->next = last_menu->next; + last_menu->next = NULL; + } + } + for (menu = parent->list; menu; menu = menu->next) { + if (sym && sym_is_choice(sym) && menu->sym) { + menu->sym->flags |= SYMBOL_CHOICEVAL; + if (!menu->prompt) + menu_warn(menu, "choice value must have a prompt"); + for (prop = menu->sym->prop; prop; prop = prop->next) { + if (prop->type == P_PROMPT && prop->menu != menu) { + prop_warn(prop, "choice values " + "currently only support a " + "single prompt"); + } + if (prop->type == P_DEFAULT) + prop_warn(prop, "defaults for choice " + "values not supported"); + } + current_entry = menu; + menu_set_type(sym->type); + menu_add_symbol(P_CHOICE, sym, NULL); + prop = sym_get_choice_prop(sym); + for (ep = &prop->expr; *ep; ep = &(*ep)->left.expr) + ; + *ep = expr_alloc_one(E_CHOICE, NULL); + (*ep)->right.sym = menu->sym; + } + if (menu->list && (!menu->prompt || !menu->prompt->text)) { + for (last_menu = menu->list; ; last_menu = last_menu->next) { + last_menu->parent = parent; + if (!last_menu->next) + break; + } + last_menu->next = menu->next; + menu->next = menu->list; + menu->list = NULL; + } + } + + if (sym && !(sym->flags & SYMBOL_WARNED)) { + if (sym->type == S_UNKNOWN) + menu_warn(parent, "config symbol defined " + "without type\n"); + + if (sym_is_choice(sym) && !parent->prompt) + menu_warn(parent, "choice must have a prompt\n"); + + /* Check properties connected to this symbol */ + sym_check_prop(sym); + sym->flags |= SYMBOL_WARNED; + } + + if (sym && !sym_is_optional(sym) && parent->prompt) { + sym->rev_dep.expr = expr_alloc_or(sym->rev_dep.expr, + expr_alloc_and(parent->prompt->visible.expr, + expr_alloc_symbol(&symbol_mod))); + } +} + +bool menu_is_visible(struct menu *menu) +{ + struct menu *child; + struct symbol *sym; + tristate visible; + + if (!menu->prompt) + return false; + sym = menu->sym; + if (sym) { + sym_calc_value(sym); + visible = menu->prompt->visible.tri; + } else + visible = menu->prompt->visible.tri = expr_calc_value(menu->prompt->visible.expr); + + if (visible != no) + return true; + if (!sym || sym_get_tristate_value(menu->sym) == no) + return false; + + for (child = menu->list; child; child = child->next) + if (menu_is_visible(child)) + return true; + return false; +} + +const char *menu_get_prompt(struct menu *menu) +{ + if (menu->prompt) + return _(menu->prompt->text); + else if (menu->sym) + return _(menu->sym->name); + return NULL; +} + +struct menu *menu_get_root_menu(struct menu *menu) +{ + return &rootmenu; +} + +struct menu *menu_get_parent_menu(struct menu *menu) +{ + enum prop_type type; + + for (; menu != &rootmenu; menu = menu->parent) { + type = menu->prompt ? menu->prompt->type : 0; + if (type == P_MENU) + break; + } + return menu; +} + diff --git a/scripts/kconfig/qconf.cc b/scripts/kconfig/qconf.cc new file mode 100644 index 000000000..74b8470e8 --- /dev/null +++ b/scripts/kconfig/qconf.cc @@ -0,0 +1,1426 @@ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <qapplication.h> +#include <qmainwindow.h> +#include <qtoolbar.h> +#include <qvbox.h> +#include <qsplitter.h> +#include <qlistview.h> +#include <qtextview.h> +#include <qlineedit.h> +#include <qmenubar.h> +#include <qmessagebox.h> +#include <qaction.h> +#include <qheader.h> +#include <qfiledialog.h> +#include <qregexp.h> + +#include <stdlib.h> + +#include "lkc.h" +#include "qconf.h" + +#include "qconf.moc" +#include "images.c" + +#ifdef _ +# undef _ +# define _ qgettext +#endif + +static QApplication *configApp; + +static inline QString qgettext(const char* str) +{ + return QString::fromLocal8Bit(gettext(str)); +} + +static inline QString qgettext(const QString& str) +{ + return QString::fromLocal8Bit(gettext(str.latin1())); +} + +ConfigSettings::ConfigSettings() + : showAll(false), showName(false), showRange(false), showData(false) +{ +} + +#if QT_VERSION >= 300 +/** + * Reads the list column settings from the application settings. + */ +void ConfigSettings::readListSettings() +{ + showAll = readBoolEntry("/kconfig/qconf/showAll", false); + showName = readBoolEntry("/kconfig/qconf/showName", false); + showRange = readBoolEntry("/kconfig/qconf/showRange", false); + showData = readBoolEntry("/kconfig/qconf/showData", false); +} + +/** + * Reads a list of integer values from the application settings. + */ +QValueList<int> ConfigSettings::readSizes(const QString& key, bool *ok) +{ + QValueList<int> result; + QStringList entryList = readListEntry(key, ok); + if (ok) { + QStringList::Iterator it; + for (it = entryList.begin(); it != entryList.end(); ++it) + result.push_back((*it).toInt()); + } + + return result; +} + +/** + * Writes a list of integer values to the application settings. + */ +bool ConfigSettings::writeSizes(const QString& key, const QValueList<int>& value) +{ + QStringList stringList; + QValueList<int>::ConstIterator it; + + for (it = value.begin(); it != value.end(); ++it) + stringList.push_back(QString::number(*it)); + return writeEntry(key, stringList); +} +#endif + + +/* + * update all the children of a menu entry + * removes/adds the entries from the parent widget as necessary + * + * parent: either the menu list widget or a menu entry widget + * menu: entry to be updated + */ +template <class P> +void ConfigList::updateMenuList(P* parent, struct menu* menu) +{ + struct menu* child; + ConfigItem* item; + ConfigItem* last; + bool visible; + enum prop_type type; + + if (!menu) { + while ((item = parent->firstChild())) + delete item; + return; + } + + last = parent->firstChild(); + if (last && !last->goParent) + last = 0; + for (child = menu->list; child; child = child->next) { + item = last ? last->nextSibling() : parent->firstChild(); + type = child->prompt ? child->prompt->type : P_UNKNOWN; + + switch (mode) { + case menuMode: + if (!(child->flags & MENU_ROOT)) + goto hide; + break; + case symbolMode: + if (child->flags & MENU_ROOT) + goto hide; + break; + default: + break; + } + + visible = menu_is_visible(child); + if (showAll || visible) { + if (!item || item->menu != child) + item = new ConfigItem(parent, last, child, visible); + else + item->testUpdateMenu(visible); + + if (mode == fullMode || mode == menuMode || type != P_MENU) + updateMenuList(item, child); + else + updateMenuList(item, 0); + last = item; + continue; + } + hide: + if (item && item->menu == child) { + last = parent->firstChild(); + if (last == item) + last = 0; + else while (last->nextSibling() != item) + last = last->nextSibling(); + delete item; + } + } +} + +#if QT_VERSION >= 300 +/* + * set the new data + * TODO check the value + */ +void ConfigItem::okRename(int col) +{ + Parent::okRename(col); + sym_set_string_value(menu->sym, text(dataColIdx).latin1()); +} +#endif + +/* + * update the displayed of a menu entry + */ +void ConfigItem::updateMenu(void) +{ + ConfigList* list; + struct symbol* sym; + struct property *prop; + QString prompt; + int type; + tristate expr; + + list = listView(); + if (goParent) { + setPixmap(promptColIdx, list->menuBackPix); + prompt = ".."; + goto set_prompt; + } + + sym = menu->sym; + prop = menu->prompt; + prompt = QString::fromLocal8Bit(menu_get_prompt(menu)); + + if (prop) switch (prop->type) { + case P_MENU: + if (list->mode == singleMode || list->mode == symbolMode) { + /* a menuconfig entry is displayed differently + * depending whether it's at the view root or a child. + */ + if (sym && list->rootEntry == menu) + break; + setPixmap(promptColIdx, list->menuPix); + } else { + if (sym) + break; + setPixmap(promptColIdx, 0); + } + goto set_prompt; + case P_COMMENT: + setPixmap(promptColIdx, 0); + goto set_prompt; + default: + ; + } + if (!sym) + goto set_prompt; + + setText(nameColIdx, QString::fromLocal8Bit(sym->name)); + + type = sym_get_type(sym); + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + char ch; + + if (!sym_is_changable(sym) && !list->showAll) { + setPixmap(promptColIdx, 0); + setText(noColIdx, QString::null); + setText(modColIdx, QString::null); + setText(yesColIdx, QString::null); + break; + } + expr = sym_get_tristate_value(sym); + switch (expr) { + case yes: + if (sym_is_choice_value(sym) && type == S_BOOLEAN) + setPixmap(promptColIdx, list->choiceYesPix); + else + setPixmap(promptColIdx, list->symbolYesPix); + setText(yesColIdx, "Y"); + ch = 'Y'; + break; + case mod: + setPixmap(promptColIdx, list->symbolModPix); + setText(modColIdx, "M"); + ch = 'M'; + break; + default: + if (sym_is_choice_value(sym) && type == S_BOOLEAN) + setPixmap(promptColIdx, list->choiceNoPix); + else + setPixmap(promptColIdx, list->symbolNoPix); + setText(noColIdx, "N"); + ch = 'N'; + break; + } + if (expr != no) + setText(noColIdx, sym_tristate_within_range(sym, no) ? "_" : 0); + if (expr != mod) + setText(modColIdx, sym_tristate_within_range(sym, mod) ? "_" : 0); + if (expr != yes) + setText(yesColIdx, sym_tristate_within_range(sym, yes) ? "_" : 0); + + setText(dataColIdx, QChar(ch)); + break; + case S_INT: + case S_HEX: + case S_STRING: + const char* data; + + data = sym_get_string_value(sym); + +#if QT_VERSION >= 300 + int i = list->mapIdx(dataColIdx); + if (i >= 0) + setRenameEnabled(i, TRUE); +#endif + setText(dataColIdx, data); + if (type == S_STRING) + prompt = QString("%1: %2").arg(prompt).arg(data); + else + prompt = QString("(%2) %1").arg(prompt).arg(data); + break; + } + if (!sym_has_value(sym) && visible) + prompt += " (NEW)"; +set_prompt: + setText(promptColIdx, prompt); +} + +void ConfigItem::testUpdateMenu(bool v) +{ + ConfigItem* i; + + visible = v; + if (!menu) + return; + + sym_calc_value(menu->sym); + if (menu->flags & MENU_CHANGED) { + /* the menu entry changed, so update all list items */ + menu->flags &= ~MENU_CHANGED; + for (i = (ConfigItem*)menu->data; i; i = i->nextItem) + i->updateMenu(); + } else if (listView()->updateAll) + updateMenu(); +} + +void ConfigItem::paintCell(QPainter* p, const QColorGroup& cg, int column, int width, int align) +{ + ConfigList* list = listView(); + + if (visible) { + if (isSelected() && !list->hasFocus() && list->mode == menuMode) + Parent::paintCell(p, list->inactivedColorGroup, column, width, align); + else + Parent::paintCell(p, cg, column, width, align); + } else + Parent::paintCell(p, list->disabledColorGroup, column, width, align); +} + +/* + * construct a menu entry + */ +void ConfigItem::init(void) +{ + if (menu) { + ConfigList* list = listView(); + nextItem = (ConfigItem*)menu->data; + menu->data = this; + + if (list->mode != fullMode) + setOpen(TRUE); + sym_calc_value(menu->sym); + } + updateMenu(); +} + +/* + * destruct a menu entry + */ +ConfigItem::~ConfigItem(void) +{ + if (menu) { + ConfigItem** ip = (ConfigItem**)&menu->data; + for (; *ip; ip = &(*ip)->nextItem) { + if (*ip == this) { + *ip = nextItem; + break; + } + } + } +} + +void ConfigLineEdit::show(ConfigItem* i) +{ + item = i; + if (sym_get_string_value(item->menu->sym)) + setText(QString::fromLocal8Bit(sym_get_string_value(item->menu->sym))); + else + setText(QString::null); + Parent::show(); + setFocus(); +} + +void ConfigLineEdit::keyPressEvent(QKeyEvent* e) +{ + switch (e->key()) { + case Key_Escape: + break; + case Key_Return: + case Key_Enter: + sym_set_string_value(item->menu->sym, text().latin1()); + parent()->updateList(item); + break; + default: + Parent::keyPressEvent(e); + return; + } + e->accept(); + parent()->list->setFocus(); + hide(); +} + +ConfigList::ConfigList(ConfigView* p, ConfigMainWindow* cv, ConfigSettings* configSettings) + : Parent(p), cview(cv), + updateAll(false), + symbolYesPix(xpm_symbol_yes), symbolModPix(xpm_symbol_mod), symbolNoPix(xpm_symbol_no), + choiceYesPix(xpm_choice_yes), choiceNoPix(xpm_choice_no), + menuPix(xpm_menu), menuInvPix(xpm_menu_inv), menuBackPix(xpm_menuback), voidPix(xpm_void), + showAll(false), showName(false), showRange(false), showData(false), + rootEntry(0) +{ + int i; + + setSorting(-1); + setRootIsDecorated(TRUE); + disabledColorGroup = palette().active(); + disabledColorGroup.setColor(QColorGroup::Text, palette().disabled().text()); + inactivedColorGroup = palette().active(); + inactivedColorGroup.setColor(QColorGroup::Highlight, palette().disabled().highlight()); + + connect(this, SIGNAL(selectionChanged(void)), + SLOT(updateSelection(void))); + + if (configSettings) { + showAll = configSettings->showAll; + showName = configSettings->showName; + showRange = configSettings->showRange; + showData = configSettings->showData; + } + + for (i = 0; i < colNr; i++) + colMap[i] = colRevMap[i] = -1; + addColumn(promptColIdx, "Option"); + + reinit(); +} + +void ConfigList::reinit(void) +{ + removeColumn(dataColIdx); + removeColumn(yesColIdx); + removeColumn(modColIdx); + removeColumn(noColIdx); + removeColumn(nameColIdx); + + if (showName) + addColumn(nameColIdx, "Name"); + if (showRange) { + addColumn(noColIdx, "N"); + addColumn(modColIdx, "M"); + addColumn(yesColIdx, "Y"); + } + if (showData) + addColumn(dataColIdx, "Value"); + + updateListAll(); +} + +void ConfigList::updateSelection(void) +{ + struct menu *menu; + enum prop_type type; + + ConfigItem* item = (ConfigItem*)selectedItem(); + if (!item) + return; + + cview->setHelp(item); + + menu = item->menu; + if (!menu) + return; + type = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (mode == menuMode && type == P_MENU) + emit menuSelected(menu); +} + +void ConfigList::updateList(ConfigItem* item) +{ + ConfigItem* last = 0; + + if (!rootEntry) + goto update; + + if (rootEntry != &rootmenu && (mode == singleMode || + (mode == symbolMode && rootEntry->parent != &rootmenu))) { + item = firstChild(); + if (!item) + item = new ConfigItem(this, 0, true); + last = item; + } + if ((mode == singleMode || (mode == symbolMode && !(rootEntry->flags & MENU_ROOT))) && + rootEntry->sym && rootEntry->prompt) { + item = last ? last->nextSibling() : firstChild(); + if (!item) + item = new ConfigItem(this, last, rootEntry, true); + else + item->testUpdateMenu(true); + + updateMenuList(item, rootEntry); + triggerUpdate(); + return; + } +update: + updateMenuList(this, rootEntry); + triggerUpdate(); +} + +void ConfigList::setAllOpen(bool open) +{ + QListViewItemIterator it(this); + + for (; it.current(); it++) + it.current()->setOpen(open); +} + +void ConfigList::setValue(ConfigItem* item, tristate val) +{ + struct symbol* sym; + int type; + tristate oldval; + + sym = item->menu ? item->menu->sym : 0; + if (!sym) + return; + + type = sym_get_type(sym); + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + oldval = sym_get_tristate_value(sym); + + if (!sym_set_tristate_value(sym, val)) + return; + if (oldval == no && item->menu->list) + item->setOpen(TRUE); + parent()->updateList(item); + break; + } +} + +void ConfigList::changeValue(ConfigItem* item) +{ + struct symbol* sym; + struct menu* menu; + int type, oldexpr, newexpr; + + menu = item->menu; + if (!menu) + return; + sym = menu->sym; + if (!sym) { + if (item->menu->list) + item->setOpen(!item->isOpen()); + return; + } + + type = sym_get_type(sym); + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + oldexpr = sym_get_tristate_value(sym); + newexpr = sym_toggle_tristate_value(sym); + if (item->menu->list) { + if (oldexpr == newexpr) + item->setOpen(!item->isOpen()); + else if (oldexpr == no) + item->setOpen(TRUE); + } + if (oldexpr != newexpr) + parent()->updateList(item); + break; + case S_INT: + case S_HEX: + case S_STRING: +#if QT_VERSION >= 300 + if (colMap[dataColIdx] >= 0) + item->startRename(colMap[dataColIdx]); + else +#endif + parent()->lineEdit->show(item); + break; + } +} + +void ConfigList::setRootMenu(struct menu *menu) +{ + enum prop_type type; + + if (rootEntry == menu) + return; + type = menu && menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (type != P_MENU) + return; + updateMenuList(this, 0); + rootEntry = menu; + updateListAll(); + setSelected(currentItem(), hasFocus()); +} + +void ConfigList::setParentMenu(void) +{ + ConfigItem* item; + struct menu *oldroot; + + oldroot = rootEntry; + if (rootEntry == &rootmenu) + return; + setRootMenu(menu_get_parent_menu(rootEntry->parent)); + + QListViewItemIterator it(this); + for (; (item = (ConfigItem*)it.current()); it++) { + if (item->menu == oldroot) { + setCurrentItem(item); + ensureItemVisible(item); + break; + } + } +} + +void ConfigList::keyPressEvent(QKeyEvent* ev) +{ + QListViewItem* i = currentItem(); + ConfigItem* item; + struct menu *menu; + enum prop_type type; + + if (ev->key() == Key_Escape && mode != fullMode) { + emit parentSelected(); + ev->accept(); + return; + } + + if (!i) { + Parent::keyPressEvent(ev); + return; + } + item = (ConfigItem*)i; + + switch (ev->key()) { + case Key_Return: + case Key_Enter: + if (item->goParent) { + emit parentSelected(); + break; + } + menu = item->menu; + if (!menu) + break; + type = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (type == P_MENU && rootEntry != menu && + mode != fullMode && mode != menuMode) { + emit menuSelected(menu); + break; + } + case Key_Space: + changeValue(item); + break; + case Key_N: + setValue(item, no); + break; + case Key_M: + setValue(item, mod); + break; + case Key_Y: + setValue(item, yes); + break; + default: + Parent::keyPressEvent(ev); + return; + } + ev->accept(); +} + +void ConfigList::contentsMousePressEvent(QMouseEvent* e) +{ + //QPoint p(contentsToViewport(e->pos())); + //printf("contentsMousePressEvent: %d,%d\n", p.x(), p.y()); + Parent::contentsMousePressEvent(e); +} + +void ConfigList::contentsMouseReleaseEvent(QMouseEvent* e) +{ + QPoint p(contentsToViewport(e->pos())); + ConfigItem* item = (ConfigItem*)itemAt(p); + struct menu *menu; + enum prop_type ptype; + const QPixmap* pm; + int idx, x; + + if (!item) + goto skip; + + menu = item->menu; + x = header()->offset() + p.x(); + idx = colRevMap[header()->sectionAt(x)]; + switch (idx) { + case promptColIdx: + pm = item->pixmap(promptColIdx); + if (pm) { + int off = header()->sectionPos(0) + itemMargin() + + treeStepSize() * (item->depth() + (rootIsDecorated() ? 1 : 0)); + if (x >= off && x < off + pm->width()) { + if (item->goParent) { + emit parentSelected(); + break; + } else if (!menu) + break; + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (ptype == P_MENU && rootEntry != menu && + mode != fullMode && mode != menuMode) + emit menuSelected(menu); + else + changeValue(item); + } + } + break; + case noColIdx: + setValue(item, no); + break; + case modColIdx: + setValue(item, mod); + break; + case yesColIdx: + setValue(item, yes); + break; + case dataColIdx: + changeValue(item); + break; + } + +skip: + //printf("contentsMouseReleaseEvent: %d,%d\n", p.x(), p.y()); + Parent::contentsMouseReleaseEvent(e); +} + +void ConfigList::contentsMouseMoveEvent(QMouseEvent* e) +{ + //QPoint p(contentsToViewport(e->pos())); + //printf("contentsMouseMoveEvent: %d,%d\n", p.x(), p.y()); + Parent::contentsMouseMoveEvent(e); +} + +void ConfigList::contentsMouseDoubleClickEvent(QMouseEvent* e) +{ + QPoint p(contentsToViewport(e->pos())); + ConfigItem* item = (ConfigItem*)itemAt(p); + struct menu *menu; + enum prop_type ptype; + + if (!item) + goto skip; + if (item->goParent) { + emit parentSelected(); + goto skip; + } + menu = item->menu; + if (!menu) + goto skip; + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (ptype == P_MENU && (mode == singleMode || mode == symbolMode)) + emit menuSelected(menu); + else if (menu->sym) + changeValue(item); + +skip: + //printf("contentsMouseDoubleClickEvent: %d,%d\n", p.x(), p.y()); + Parent::contentsMouseDoubleClickEvent(e); +} + +void ConfigList::focusInEvent(QFocusEvent *e) +{ + Parent::focusInEvent(e); + + QListViewItem* item = currentItem(); + if (!item) + return; + + setSelected(item, TRUE); + emit gotFocus(); +} + +ConfigView* ConfigView::viewList; + +ConfigView::ConfigView(QWidget* parent, ConfigMainWindow* cview, + ConfigSettings *configSettings) + : Parent(parent) +{ + list = new ConfigList(this, cview, configSettings); + lineEdit = new ConfigLineEdit(this); + lineEdit->hide(); + + this->nextView = viewList; + viewList = this; +} + +ConfigView::~ConfigView(void) +{ + ConfigView** vp; + + for (vp = &viewList; *vp; vp = &(*vp)->nextView) { + if (*vp == this) { + *vp = nextView; + break; + } + } +} + +void ConfigView::updateList(ConfigItem* item) +{ + ConfigView* v; + + for (v = viewList; v; v = v->nextView) + v->list->updateList(item); +} + +void ConfigView::updateListAll(void) +{ + ConfigView* v; + + for (v = viewList; v; v = v->nextView) + v->list->updateListAll(); +} + +/* + * Construct the complete config widget + */ +ConfigMainWindow::ConfigMainWindow(void) +{ + QMenuBar* menu; + bool ok; + int x, y, width, height; + + QWidget *d = configApp->desktop(); + + ConfigSettings* configSettings = new ConfigSettings(); +#if QT_VERSION >= 300 + width = configSettings->readNumEntry("/kconfig/qconf/window width", d->width() - 64); + height = configSettings->readNumEntry("/kconfig/qconf/window height", d->height() - 64); + resize(width, height); + x = configSettings->readNumEntry("/kconfig/qconf/window x", 0, &ok); + if (ok) + y = configSettings->readNumEntry("/kconfig/qconf/window y", 0, &ok); + if (ok) + move(x, y); + showDebug = configSettings->readBoolEntry("/kconfig/qconf/showDebug", false); + + // read list settings into configSettings, will be used later for ConfigList setup + configSettings->readListSettings(); +#else + width = d->width() - 64; + height = d->height() - 64; + resize(width, height); + showDebug = false; +#endif + + split1 = new QSplitter(this); + split1->setOrientation(QSplitter::Horizontal); + setCentralWidget(split1); + + menuView = new ConfigView(split1, this, configSettings); + menuList = menuView->list; + + split2 = new QSplitter(split1); + split2->setOrientation(QSplitter::Vertical); + + // create config tree + configView = new ConfigView(split2, this, configSettings); + configList = configView->list; + + helpText = new QTextView(split2); + helpText->setTextFormat(Qt::RichText); + + setTabOrder(configList, helpText); + configList->setFocus(); + + menu = menuBar(); + toolBar = new QToolBar("Tools", this); + + backAction = new QAction("Back", QPixmap(xpm_back), "Back", 0, this); + connect(backAction, SIGNAL(activated()), SLOT(goBack())); + backAction->setEnabled(FALSE); + QAction *quitAction = new QAction("Quit", "&Quit", CTRL+Key_Q, this); + connect(quitAction, SIGNAL(activated()), SLOT(close())); + QAction *loadAction = new QAction("Load", QPixmap(xpm_load), "&Load", CTRL+Key_L, this); + connect(loadAction, SIGNAL(activated()), SLOT(loadConfig())); + QAction *saveAction = new QAction("Save", QPixmap(xpm_save), "&Save", CTRL+Key_S, this); + connect(saveAction, SIGNAL(activated()), SLOT(saveConfig())); + QAction *saveAsAction = new QAction("Save As...", "Save &As...", 0, this); + connect(saveAsAction, SIGNAL(activated()), SLOT(saveConfigAs())); + QAction *singleViewAction = new QAction("Single View", QPixmap(xpm_single_view), "Split View", 0, this); + connect(singleViewAction, SIGNAL(activated()), SLOT(showSingleView())); + QAction *splitViewAction = new QAction("Split View", QPixmap(xpm_split_view), "Split View", 0, this); + connect(splitViewAction, SIGNAL(activated()), SLOT(showSplitView())); + QAction *fullViewAction = new QAction("Full View", QPixmap(xpm_tree_view), "Full View", 0, this); + connect(fullViewAction, SIGNAL(activated()), SLOT(showFullView())); + + QAction *showNameAction = new QAction(NULL, "Show Name", 0, this); + showNameAction->setToggleAction(TRUE); + showNameAction->setOn(configList->showName); + connect(showNameAction, SIGNAL(toggled(bool)), SLOT(setShowName(bool))); + QAction *showRangeAction = new QAction(NULL, "Show Range", 0, this); + showRangeAction->setToggleAction(TRUE); + showRangeAction->setOn(configList->showRange); + connect(showRangeAction, SIGNAL(toggled(bool)), SLOT(setShowRange(bool))); + QAction *showDataAction = new QAction(NULL, "Show Data", 0, this); + showDataAction->setToggleAction(TRUE); + showDataAction->setOn(configList->showData); + connect(showDataAction, SIGNAL(toggled(bool)), SLOT(setShowData(bool))); + QAction *showAllAction = new QAction(NULL, "Show All Options", 0, this); + showAllAction->setToggleAction(TRUE); + showAllAction->setOn(configList->showAll); + connect(showAllAction, SIGNAL(toggled(bool)), SLOT(setShowAll(bool))); + QAction *showDebugAction = new QAction(NULL, "Show Debug Info", 0, this); + showDebugAction->setToggleAction(TRUE); + showDebugAction->setOn(showDebug); + connect(showDebugAction, SIGNAL(toggled(bool)), SLOT(setShowDebug(bool))); + + QAction *showIntroAction = new QAction(NULL, "Introduction", 0, this); + connect(showIntroAction, SIGNAL(activated()), SLOT(showIntro())); + QAction *showAboutAction = new QAction(NULL, "About", 0, this); + connect(showAboutAction, SIGNAL(activated()), SLOT(showAbout())); + + // init tool bar + backAction->addTo(toolBar); + toolBar->addSeparator(); + loadAction->addTo(toolBar); + saveAction->addTo(toolBar); + toolBar->addSeparator(); + singleViewAction->addTo(toolBar); + splitViewAction->addTo(toolBar); + fullViewAction->addTo(toolBar); + + // create config menu + QPopupMenu* config = new QPopupMenu(this); + menu->insertItem("&File", config); + loadAction->addTo(config); + saveAction->addTo(config); + saveAsAction->addTo(config); + config->insertSeparator(); + quitAction->addTo(config); + + // create options menu + QPopupMenu* optionMenu = new QPopupMenu(this); + menu->insertItem("&Option", optionMenu); + showNameAction->addTo(optionMenu); + showRangeAction->addTo(optionMenu); + showDataAction->addTo(optionMenu); + optionMenu->insertSeparator(); + showAllAction->addTo(optionMenu); + showDebugAction->addTo(optionMenu); + + // create help menu + QPopupMenu* helpMenu = new QPopupMenu(this); + menu->insertSeparator(); + menu->insertItem("&Help", helpMenu); + showIntroAction->addTo(helpMenu); + showAboutAction->addTo(helpMenu); + + connect(configList, SIGNAL(menuSelected(struct menu *)), + SLOT(changeMenu(struct menu *))); + connect(configList, SIGNAL(parentSelected()), + SLOT(goBack())); + connect(menuList, SIGNAL(menuSelected(struct menu *)), + SLOT(changeMenu(struct menu *))); + + connect(configList, SIGNAL(gotFocus(void)), + SLOT(listFocusChanged(void))); + connect(menuList, SIGNAL(gotFocus(void)), + SLOT(listFocusChanged(void))); + +#if QT_VERSION >= 300 + QString listMode = configSettings->readEntry("/kconfig/qconf/listMode", "symbol"); + if (listMode == "single") + showSingleView(); + else if (listMode == "full") + showFullView(); + else /*if (listMode == "split")*/ + showSplitView(); + + // UI setup done, restore splitter positions + QValueList<int> sizes = configSettings->readSizes("/kconfig/qconf/split1", &ok); + if (ok) + split1->setSizes(sizes); + + sizes = configSettings->readSizes("/kconfig/qconf/split2", &ok); + if (ok) + split2->setSizes(sizes); +#else + showSplitView(); +#endif + delete configSettings; +} + +static QString print_filter(const QString &str) +{ + QRegExp re("[<>&\"\\n]"); + QString res = str; + for (int i = 0; (i = res.find(re, i)) >= 0;) { + switch (res[i].latin1()) { + case '<': + res.replace(i, 1, "<"); + i += 4; + break; + case '>': + res.replace(i, 1, ">"); + i += 4; + break; + case '&': + res.replace(i, 1, "&"); + i += 5; + break; + case '"': + res.replace(i, 1, """); + i += 6; + break; + case '\n': + res.replace(i, 1, "<br>"); + i += 4; + break; + } + } + return res; +} + +static void expr_print_help(void *data, const char *str) +{ + reinterpret_cast<QString*>(data)->append(print_filter(str)); +} + +/* + * display a new help entry as soon as a new menu entry is selected + */ +void ConfigMainWindow::setHelp(QListViewItem* item) +{ + struct symbol* sym; + struct menu* menu = 0; + + configList->parent()->lineEdit->hide(); + if (item) + menu = ((ConfigItem*)item)->menu; + if (!menu) { + helpText->setText(QString::null); + return; + } + + QString head, debug, help; + menu = ((ConfigItem*)item)->menu; + sym = menu->sym; + if (sym) { + if (menu->prompt) { + head += "<big><b>"; + head += print_filter(_(menu->prompt->text)); + head += "</b></big>"; + if (sym->name) { + head += " ("; + head += print_filter(_(sym->name)); + head += ")"; + } + } else if (sym->name) { + head += "<big><b>"; + head += print_filter(_(sym->name)); + head += "</b></big>"; + } + head += "<br><br>"; + + if (showDebug) { + debug += "type: "; + debug += print_filter(sym_type_name(sym->type)); + if (sym_is_choice(sym)) + debug += " (choice)"; + debug += "<br>"; + if (sym->rev_dep.expr) { + debug += "reverse dep: "; + expr_print(sym->rev_dep.expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + } + for (struct property *prop = sym->prop; prop; prop = prop->next) { + switch (prop->type) { + case P_PROMPT: + case P_MENU: + debug += "prompt: "; + debug += print_filter(_(prop->text)); + debug += "<br>"; + break; + case P_DEFAULT: + debug += "default: "; + expr_print(prop->expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + break; + case P_CHOICE: + if (sym_is_choice(sym)) { + debug += "choice: "; + expr_print(prop->expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + } + break; + case P_SELECT: + debug += "select: "; + expr_print(prop->expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + break; + case P_RANGE: + debug += "range: "; + expr_print(prop->expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + break; + default: + debug += "unknown property: "; + debug += prop_get_type_name(prop->type); + debug += "<br>"; + } + if (prop->visible.expr) { + debug += "    dep: "; + expr_print(prop->visible.expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + } + } + debug += "<br>"; + } + + help = print_filter(_(sym->help)); + } else if (menu->prompt) { + head += "<big><b>"; + head += print_filter(_(menu->prompt->text)); + head += "</b></big><br><br>"; + if (showDebug) { + if (menu->prompt->visible.expr) { + debug += "  dep: "; + expr_print(menu->prompt->visible.expr, expr_print_help, &debug, E_NONE); + debug += "<br><br>"; + } + } + } + if (showDebug) + debug += QString().sprintf("defined at %s:%d<br><br>", menu->file->name, menu->lineno); + helpText->setText(head + debug + help); +} + +void ConfigMainWindow::loadConfig(void) +{ + QString s = QFileDialog::getOpenFileName(".config", NULL, this); + if (s.isNull()) + return; + if (conf_read(QFile::encodeName(s))) + QMessageBox::information(this, "qconf", "Unable to load configuration!"); + ConfigView::updateListAll(); +} + +void ConfigMainWindow::saveConfig(void) +{ + if (conf_write(NULL)) + QMessageBox::information(this, "qconf", "Unable to save configuration!"); +} + +void ConfigMainWindow::saveConfigAs(void) +{ + QString s = QFileDialog::getSaveFileName(".config", NULL, this); + if (s.isNull()) + return; + if (conf_write(QFile::encodeName(s))) + QMessageBox::information(this, "qconf", "Unable to save configuration!"); +} + +void ConfigMainWindow::changeMenu(struct menu *menu) +{ + configList->setRootMenu(menu); + backAction->setEnabled(TRUE); +} + +void ConfigMainWindow::listFocusChanged(void) +{ + if (menuList->hasFocus()) { + if (menuList->mode == menuMode) + configList->clearSelection(); + setHelp(menuList->selectedItem()); + } else if (configList->hasFocus()) { + setHelp(configList->selectedItem()); + } +} + +void ConfigMainWindow::goBack(void) +{ + ConfigItem* item; + + configList->setParentMenu(); + if (configList->rootEntry == &rootmenu) + backAction->setEnabled(FALSE); + item = (ConfigItem*)menuList->selectedItem(); + while (item) { + if (item->menu == configList->rootEntry) { + menuList->setSelected(item, TRUE); + break; + } + item = (ConfigItem*)item->parent(); + } +} + +void ConfigMainWindow::showSingleView(void) +{ + menuView->hide(); + menuList->setRootMenu(0); + configList->mode = singleMode; + if (configList->rootEntry == &rootmenu) + configList->updateListAll(); + else + configList->setRootMenu(&rootmenu); + configList->setAllOpen(TRUE); + configList->setFocus(); +} + +void ConfigMainWindow::showSplitView(void) +{ + configList->mode = symbolMode; + if (configList->rootEntry == &rootmenu) + configList->updateListAll(); + else + configList->setRootMenu(&rootmenu); + configList->setAllOpen(TRUE); + configApp->processEvents(); + menuList->mode = menuMode; + menuList->setRootMenu(&rootmenu); + menuList->setAllOpen(TRUE); + menuView->show(); + menuList->setFocus(); +} + +void ConfigMainWindow::showFullView(void) +{ + menuView->hide(); + menuList->setRootMenu(0); + configList->mode = fullMode; + if (configList->rootEntry == &rootmenu) + configList->updateListAll(); + else + configList->setRootMenu(&rootmenu); + configList->setAllOpen(FALSE); + configList->setFocus(); +} + +void ConfigMainWindow::setShowAll(bool b) +{ + if (configList->showAll == b) + return; + configList->showAll = b; + configList->updateListAll(); + menuList->showAll = b; + menuList->updateListAll(); +} + +void ConfigMainWindow::setShowDebug(bool b) +{ + if (showDebug == b) + return; + showDebug = b; +} + +void ConfigMainWindow::setShowName(bool b) +{ + if (configList->showName == b) + return; + configList->showName = b; + configList->reinit(); + menuList->showName = b; + menuList->reinit(); +} + +void ConfigMainWindow::setShowRange(bool b) +{ + if (configList->showRange == b) + return; + configList->showRange = b; + configList->reinit(); + menuList->showRange = b; + menuList->reinit(); +} + +void ConfigMainWindow::setShowData(bool b) +{ + if (configList->showData == b) + return; + configList->showData = b; + configList->reinit(); + menuList->showData = b; + menuList->reinit(); +} + +/* + * ask for saving configuration before quitting + * TODO ask only when something changed + */ +void ConfigMainWindow::closeEvent(QCloseEvent* e) +{ + if (!sym_change_count) { + e->accept(); + return; + } + QMessageBox mb("qconf", "Save configuration?", QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::Default, QMessageBox::No, QMessageBox::Cancel | QMessageBox::Escape); + mb.setButtonText(QMessageBox::Yes, "&Save Changes"); + mb.setButtonText(QMessageBox::No, "&Discard Changes"); + mb.setButtonText(QMessageBox::Cancel, "Cancel Exit"); + switch (mb.exec()) { + case QMessageBox::Yes: + conf_write(NULL); + case QMessageBox::No: + e->accept(); + break; + case QMessageBox::Cancel: + e->ignore(); + break; + } +} + +void ConfigMainWindow::showIntro(void) +{ + static char str[] = "Welcome to the qconf graphical busybox configuration tool for Linux.\n\n" + "For each option, a blank box indicates the feature is disabled, a check\n" + "indicates it is enabled, and a dot indicates that it is to be compiled\n" + "as a module. Clicking on the box will cycle through the three states.\n\n" + "If you do not see an option (e.g., a device driver) that you believe\n" + "should be present, try turning on Show All Options under the Options menu.\n" + "Although there is no cross reference yet to help you figure out what other\n" + "options must be enabled to support the option you are interested in, you can\n" + "still view the help of a grayed-out option.\n\n" + "Toggling Show Debug Info under the Options menu will show the dependencies,\n" + "which you can then match by examining other options.\n\n"; + + QMessageBox::information(this, "qconf", str); +} + +void ConfigMainWindow::showAbout(void) +{ + static char str[] = "qconf is Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>.\n\n" + "Bug reports and feature request can also be entered at http://bugzilla.kernel.org/\n"; + + QMessageBox::information(this, "qconf", str); +} + +void ConfigMainWindow::saveSettings(void) +{ +#if QT_VERSION >= 300 + ConfigSettings *configSettings = new ConfigSettings; + configSettings->writeEntry("/kconfig/qconf/window x", pos().x()); + configSettings->writeEntry("/kconfig/qconf/window y", pos().y()); + configSettings->writeEntry("/kconfig/qconf/window width", size().width()); + configSettings->writeEntry("/kconfig/qconf/window height", size().height()); + configSettings->writeEntry("/kconfig/qconf/showName", configList->showName); + configSettings->writeEntry("/kconfig/qconf/showRange", configList->showRange); + configSettings->writeEntry("/kconfig/qconf/showData", configList->showData); + configSettings->writeEntry("/kconfig/qconf/showAll", configList->showAll); + configSettings->writeEntry("/kconfig/qconf/showDebug", showDebug); + + QString entry; + switch(configList->mode) { + case singleMode : + entry = "single"; + break; + + case symbolMode : + entry = "split"; + break; + + case fullMode : + entry = "full"; + break; + } + configSettings->writeEntry("/kconfig/qconf/listMode", entry); + + configSettings->writeSizes("/kconfig/qconf/split1", split1->sizes()); + configSettings->writeSizes("/kconfig/qconf/split2", split2->sizes()); + + delete configSettings; +#endif +} + +void fixup_rootmenu(struct menu *menu) +{ + struct menu *child; + static int menu_cnt = 0; + + menu->flags |= MENU_ROOT; + for (child = menu->list; child; child = child->next) { + if (child->prompt && child->prompt->type == P_MENU) { + menu_cnt++; + fixup_rootmenu(child); + menu_cnt--; + } else if (!menu_cnt) + fixup_rootmenu(child); + } +} + +static const char *progname; + +static void usage(void) +{ + printf("%s <config>\n", progname); + exit(0); +} + +int main(int ac, char** av) +{ + ConfigMainWindow* v; + const char *name; + + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + +#ifndef LKC_DIRECT_LINK + kconfig_load(); +#endif + + progname = av[0]; + configApp = new QApplication(ac, av); + if (ac > 1 && av[1][0] == '-') { + switch (av[1][1]) { + case 'h': + case '?': + usage(); + } + name = av[2]; + } else + name = av[1]; + if (!name) + usage(); + + conf_parse(name); + fixup_rootmenu(&rootmenu); + conf_read(NULL); + //zconfdump(stdout); + + v = new ConfigMainWindow(); + + //zconfdump(stdout); + v->show(); + configApp->connect(configApp, SIGNAL(lastWindowClosed()), SLOT(quit())); + configApp->connect(configApp, SIGNAL(aboutToQuit()), v, SLOT(saveSettings())); + configApp->exec(); + + return 0; +} diff --git a/scripts/kconfig/qconf.h b/scripts/kconfig/qconf.h new file mode 100644 index 000000000..e52f3e90b --- /dev/null +++ b/scripts/kconfig/qconf.h @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <qlistview.h> +#if QT_VERSION >= 300 +#include <qsettings.h> +#else +class QSettings { }; +#endif + +class ConfigList; +class ConfigItem; +class ConfigLineEdit; +class ConfigMainWindow; + + +class ConfigSettings : public QSettings { +public: + ConfigSettings(); + +#if QT_VERSION >= 300 + void readListSettings(); + QValueList<int> readSizes(const QString& key, bool *ok); + bool writeSizes(const QString& key, const QValueList<int>& value); +#endif + + bool showAll; + bool showName; + bool showRange; + bool showData; +}; + +class ConfigView : public QVBox { + Q_OBJECT + typedef class QVBox Parent; +public: + ConfigView(QWidget* parent, ConfigMainWindow* cview, ConfigSettings* configSettings); + ~ConfigView(void); + static void updateList(ConfigItem* item); + static void updateListAll(void); + +public: + ConfigList* list; + ConfigLineEdit* lineEdit; + + static ConfigView* viewList; + ConfigView* nextView; +}; + +enum colIdx { + promptColIdx, nameColIdx, noColIdx, modColIdx, yesColIdx, dataColIdx, colNr +}; +enum listMode { + singleMode, menuMode, symbolMode, fullMode +}; + +class ConfigList : public QListView { + Q_OBJECT + typedef class QListView Parent; +public: + ConfigList(ConfigView* p, ConfigMainWindow* cview, ConfigSettings *configSettings); + void reinit(void); + ConfigView* parent(void) const + { + return (ConfigView*)Parent::parent(); + } + +protected: + ConfigMainWindow* cview; + + void keyPressEvent(QKeyEvent *e); + void contentsMousePressEvent(QMouseEvent *e); + void contentsMouseReleaseEvent(QMouseEvent *e); + void contentsMouseMoveEvent(QMouseEvent *e); + void contentsMouseDoubleClickEvent(QMouseEvent *e); + void focusInEvent(QFocusEvent *e); +public slots: + void setRootMenu(struct menu *menu); + + void updateList(ConfigItem *item); + void setValue(ConfigItem* item, tristate val); + void changeValue(ConfigItem* item); + void updateSelection(void); +signals: + void menuSelected(struct menu *menu); + void parentSelected(void); + void gotFocus(void); + +public: + void updateListAll(void) + { + updateAll = true; + updateList(NULL); + updateAll = false; + } + ConfigList* listView() + { + return this; + } + ConfigItem* firstChild() const + { + return (ConfigItem *)Parent::firstChild(); + } + int mapIdx(colIdx idx) + { + return colMap[idx]; + } + void addColumn(colIdx idx, const QString& label) + { + colMap[idx] = Parent::addColumn(label); + colRevMap[colMap[idx]] = idx; + } + void removeColumn(colIdx idx) + { + int col = colMap[idx]; + if (col >= 0) { + Parent::removeColumn(col); + colRevMap[col] = colMap[idx] = -1; + } + } + void setAllOpen(bool open); + void setParentMenu(void); + + template <class P> + void updateMenuList(P*, struct menu*); + + bool updateAll; + + QPixmap symbolYesPix, symbolModPix, symbolNoPix; + QPixmap choiceYesPix, choiceNoPix; + QPixmap menuPix, menuInvPix, menuBackPix, voidPix; + + bool showAll, showName, showRange, showData; + enum listMode mode; + struct menu *rootEntry; + QColorGroup disabledColorGroup; + QColorGroup inactivedColorGroup; + +private: + int colMap[colNr]; + int colRevMap[colNr]; +}; + +class ConfigItem : public QListViewItem { + typedef class QListViewItem Parent; +public: + ConfigItem(QListView *parent, ConfigItem *after, struct menu *m, bool v) + : Parent(parent, after), menu(m), visible(v), goParent(false) + { + init(); + } + ConfigItem(ConfigItem *parent, ConfigItem *after, struct menu *m, bool v) + : Parent(parent, after), menu(m), visible(v), goParent(false) + { + init(); + } + ConfigItem(QListView *parent, ConfigItem *after, bool v) + : Parent(parent, after), menu(0), visible(v), goParent(true) + { + init(); + } + ~ConfigItem(void); + void init(void); +#if QT_VERSION >= 300 + void okRename(int col); +#endif + void updateMenu(void); + void testUpdateMenu(bool v); + ConfigList* listView() const + { + return (ConfigList*)Parent::listView(); + } + ConfigItem* firstChild() const + { + return (ConfigItem *)Parent::firstChild(); + } + ConfigItem* nextSibling() const + { + return (ConfigItem *)Parent::nextSibling(); + } + void setText(colIdx idx, const QString& text) + { + Parent::setText(listView()->mapIdx(idx), text); + } + QString text(colIdx idx) const + { + return Parent::text(listView()->mapIdx(idx)); + } + void setPixmap(colIdx idx, const QPixmap& pm) + { + Parent::setPixmap(listView()->mapIdx(idx), pm); + } + const QPixmap* pixmap(colIdx idx) const + { + return Parent::pixmap(listView()->mapIdx(idx)); + } + void paintCell(QPainter* p, const QColorGroup& cg, int column, int width, int align); + + ConfigItem* nextItem; + struct menu *menu; + bool visible; + bool goParent; +}; + +class ConfigLineEdit : public QLineEdit { + Q_OBJECT + typedef class QLineEdit Parent; +public: + ConfigLineEdit(ConfigView* parent) + : Parent(parent) + { } + ConfigView* parent(void) const + { + return (ConfigView*)Parent::parent(); + } + void show(ConfigItem *i); + void keyPressEvent(QKeyEvent *e); + +public: + ConfigItem *item; +}; + +class ConfigMainWindow : public QMainWindow { + Q_OBJECT +public: + ConfigMainWindow(void); +public slots: + void setHelp(QListViewItem* item); + void changeMenu(struct menu *); + void listFocusChanged(void); + void goBack(void); + void loadConfig(void); + void saveConfig(void); + void saveConfigAs(void); + void showSingleView(void); + void showSplitView(void); + void showFullView(void); + void setShowAll(bool); + void setShowDebug(bool); + void setShowRange(bool); + void setShowName(bool); + void setShowData(bool); + void showIntro(void); + void showAbout(void); + void saveSettings(void); + +protected: + void closeEvent(QCloseEvent *e); + + ConfigView *menuView; + ConfigList *menuList; + ConfigView *configView; + ConfigList *configList; + QTextView *helpText; + QToolBar *toolBar; + QAction *backAction; + QSplitter* split1; + QSplitter* split2; + + bool showDebug; +}; diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c new file mode 100644 index 000000000..3d7877afc --- /dev/null +++ b/scripts/kconfig/symbol.c @@ -0,0 +1,882 @@ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <regex.h> +#include <sys/utsname.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +struct symbol symbol_yes = { + .name = "y", + .curr = { "y", yes }, + .flags = SYMBOL_YES|SYMBOL_VALID, +}, symbol_mod = { + .name = "m", + .curr = { "m", mod }, + .flags = SYMBOL_MOD|SYMBOL_VALID, +}, symbol_no = { + .name = "n", + .curr = { "n", no }, + .flags = SYMBOL_NO|SYMBOL_VALID, +}, symbol_empty = { + .name = "", + .curr = { "", no }, + .flags = SYMBOL_VALID, +}; + +int sym_change_count; +struct symbol *modules_sym; +tristate modules_val; + +void sym_add_default(struct symbol *sym, const char *def) +{ + struct property *prop = prop_alloc(P_DEFAULT, sym); + + prop->expr = expr_alloc_symbol(sym_lookup(def, 1)); +} + +void sym_init(void) +{ + struct symbol *sym; + struct utsname uts; + char *p; + static bool inited = false; + + if (inited) + return; + inited = true; + + uname(&uts); + + sym = sym_lookup("ARCH", 0); + sym->type = S_STRING; + sym->flags |= SYMBOL_AUTO; + p = getenv("ARCH"); + if (p) + sym_add_default(sym, p); + + sym = sym_lookup("KERNELVERSION", 0); + sym->type = S_STRING; + sym->flags |= SYMBOL_AUTO; + p = getenv("KERNELVERSION"); + if (p) + sym_add_default(sym, p); + + sym = sym_lookup("UNAME_RELEASE", 0); + sym->type = S_STRING; + sym->flags |= SYMBOL_AUTO; + sym_add_default(sym, uts.release); +} + +enum symbol_type sym_get_type(struct symbol *sym) +{ + enum symbol_type type = sym->type; + + if (type == S_TRISTATE) { + if (sym_is_choice_value(sym) && sym->visible == yes) + type = S_BOOLEAN; + else if (modules_val == no) + type = S_BOOLEAN; + } + return type; +} + +const char *sym_type_name(enum symbol_type type) +{ + switch (type) { + case S_BOOLEAN: + return "boolean"; + case S_TRISTATE: + return "tristate"; + case S_INT: + return "integer"; + case S_HEX: + return "hex"; + case S_STRING: + return "string"; + case S_UNKNOWN: + return "unknown"; + case S_OTHER: + break; + } + return "???"; +} + +struct property *sym_get_choice_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_choices(sym, prop) + return prop; + return NULL; +} + +struct property *sym_get_default_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_defaults(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri != no) + return prop; + } + return NULL; +} + +struct property *sym_get_range_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_properties(sym, prop, P_RANGE) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri != no) + return prop; + } + return NULL; +} + +static int sym_get_range_val(struct symbol *sym, int base) +{ + sym_calc_value(sym); + switch (sym->type) { + case S_INT: + base = 10; + break; + case S_HEX: + base = 16; + break; + default: + break; + } + return strtol(sym->curr.val, NULL, base); +} + +static void sym_validate_range(struct symbol *sym) +{ + struct property *prop; + int base, val, val2; + char str[64]; + + switch (sym->type) { + case S_INT: + base = 10; + break; + case S_HEX: + base = 16; + break; + default: + return; + } + prop = sym_get_range_prop(sym); + if (!prop) + return; + val = strtol(sym->curr.val, NULL, base); + val2 = sym_get_range_val(prop->expr->left.sym, base); + if (val >= val2) { + val2 = sym_get_range_val(prop->expr->right.sym, base); + if (val <= val2) + return; + } + if (sym->type == S_INT) + sprintf(str, "%d", val2); + else + sprintf(str, "0x%x", val2); + sym->curr.val = strdup(str); +} + +static void sym_calc_visibility(struct symbol *sym) +{ + struct property *prop; + tristate tri; + + /* any prompt visible? */ + tri = no; + for_all_prompts(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + tri = E_OR(tri, prop->visible.tri); + } + if (tri == mod && (sym->type != S_TRISTATE || modules_val == no)) + tri = yes; + if (sym->visible != tri) { + sym->visible = tri; + sym_set_changed(sym); + } + if (sym_is_choice_value(sym)) + return; + tri = no; + if (sym->rev_dep.expr) + tri = expr_calc_value(sym->rev_dep.expr); + if (tri == mod && sym_get_type(sym) == S_BOOLEAN) + tri = yes; + if (sym->rev_dep.tri != tri) { + sym->rev_dep.tri = tri; + sym_set_changed(sym); + } +} + +static struct symbol *sym_calc_choice(struct symbol *sym) +{ + struct symbol *def_sym; + struct property *prop; + struct expr *e; + + /* is the user choice visible? */ + def_sym = sym->user.val; + if (def_sym) { + sym_calc_visibility(def_sym); + if (def_sym->visible != no) + return def_sym; + } + + /* any of the defaults visible? */ + for_all_defaults(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri == no) + continue; + def_sym = prop_get_symbol(prop); + sym_calc_visibility(def_sym); + if (def_sym->visible != no) + return def_sym; + } + + /* just get the first visible value */ + prop = sym_get_choice_prop(sym); + for (e = prop->expr; e; e = e->left.expr) { + def_sym = e->right.sym; + sym_calc_visibility(def_sym); + if (def_sym->visible != no) + return def_sym; + } + + /* no choice? reset tristate value */ + sym->curr.tri = no; + return NULL; +} + +void sym_calc_value(struct symbol *sym) +{ + struct symbol_value newval, oldval; + struct property *prop; + struct expr *e; + + if (!sym) + return; + + if (sym->flags & SYMBOL_VALID) + return; + sym->flags |= SYMBOL_VALID; + + oldval = sym->curr; + + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + newval = symbol_empty.curr; + break; + case S_BOOLEAN: + case S_TRISTATE: + newval = symbol_no.curr; + break; + default: + sym->curr.val = sym->name; + sym->curr.tri = no; + return; + } + if (!sym_is_choice_value(sym)) + sym->flags &= ~SYMBOL_WRITE; + + sym_calc_visibility(sym); + + /* set default if recursively called */ + sym->curr = newval; + + switch (sym_get_type(sym)) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym_is_choice_value(sym) && sym->visible == yes) { + prop = sym_get_choice_prop(sym); + newval.tri = (prop_get_symbol(prop)->curr.val == sym) ? yes : no; + } else if (E_OR(sym->visible, sym->rev_dep.tri) != no) { + sym->flags |= SYMBOL_WRITE; + if (sym_has_value(sym)) + newval.tri = sym->user.tri; + else if (!sym_is_choice(sym)) { + prop = sym_get_default_prop(sym); + if (prop) + newval.tri = expr_calc_value(prop->expr); + } + newval.tri = E_OR(E_AND(newval.tri, sym->visible), sym->rev_dep.tri); + } else if (!sym_is_choice(sym)) { + prop = sym_get_default_prop(sym); + if (prop) { + sym->flags |= SYMBOL_WRITE; + newval.tri = expr_calc_value(prop->expr); + } + } + if (newval.tri == mod && sym_get_type(sym) == S_BOOLEAN) + newval.tri = yes; + break; + case S_STRING: + case S_HEX: + case S_INT: + if (sym->visible != no) { + sym->flags |= SYMBOL_WRITE; + if (sym_has_value(sym)) { + newval.val = sym->user.val; + break; + } + } + prop = sym_get_default_prop(sym); + if (prop) { + struct symbol *ds = prop_get_symbol(prop); + if (ds) { + sym->flags |= SYMBOL_WRITE; + sym_calc_value(ds); + newval.val = ds->curr.val; + } + } + break; + default: + ; + } + + sym->curr = newval; + if (sym_is_choice(sym) && newval.tri == yes) + sym->curr.val = sym_calc_choice(sym); + sym_validate_range(sym); + + if (memcmp(&oldval, &sym->curr, sizeof(oldval))) + sym_set_changed(sym); + if (modules_sym == sym) + modules_val = modules_sym->curr.tri; + + if (sym_is_choice(sym)) { + int flags = sym->flags & (SYMBOL_CHANGED | SYMBOL_WRITE); + prop = sym_get_choice_prop(sym); + for (e = prop->expr; e; e = e->left.expr) { + e->right.sym->flags |= flags; + if (flags & SYMBOL_CHANGED) + sym_set_changed(e->right.sym); + } + } +} + +void sym_clear_all_valid(void) +{ + struct symbol *sym; + int i; + + for_all_symbols(i, sym) + sym->flags &= ~SYMBOL_VALID; + sym_change_count++; + if (modules_sym) + sym_calc_value(modules_sym); +} + +void sym_set_changed(struct symbol *sym) +{ + struct property *prop; + + sym->flags |= SYMBOL_CHANGED; + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->menu) + prop->menu->flags |= MENU_CHANGED; + } +} + +void sym_set_all_changed(void) +{ + struct symbol *sym; + int i; + + for_all_symbols(i, sym) + sym_set_changed(sym); +} + +bool sym_tristate_within_range(struct symbol *sym, tristate val) +{ + int type = sym_get_type(sym); + + if (sym->visible == no) + return false; + + if (type != S_BOOLEAN && type != S_TRISTATE) + return false; + + if (type == S_BOOLEAN && val == mod) + return false; + if (sym->visible <= sym->rev_dep.tri) + return false; + if (sym_is_choice_value(sym) && sym->visible == yes) + return val == yes; + return val >= sym->rev_dep.tri && val <= sym->visible; +} + +bool sym_set_tristate_value(struct symbol *sym, tristate val) +{ + tristate oldval = sym_get_tristate_value(sym); + + if (oldval != val && !sym_tristate_within_range(sym, val)) + return false; + + if (sym->flags & SYMBOL_NEW) { + sym->flags &= ~SYMBOL_NEW; + sym_set_changed(sym); + } + /* + * setting a choice value also resets the new flag of the choice + * symbol and all other choice values. + */ + if (sym_is_choice_value(sym) && val == yes) { + struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym)); + struct property *prop; + struct expr *e; + + cs->user.val = sym; + cs->flags &= ~SYMBOL_NEW; + prop = sym_get_choice_prop(cs); + for (e = prop->expr; e; e = e->left.expr) { + if (e->right.sym->visible != no) + e->right.sym->flags &= ~SYMBOL_NEW; + } + } + + sym->user.tri = val; + if (oldval != val) { + sym_clear_all_valid(); + if (sym == modules_sym) + sym_set_all_changed(); + } + + return true; +} + +tristate sym_toggle_tristate_value(struct symbol *sym) +{ + tristate oldval, newval; + + oldval = newval = sym_get_tristate_value(sym); + do { + switch (newval) { + case no: + newval = mod; + break; + case mod: + newval = yes; + break; + case yes: + newval = no; + break; + } + if (sym_set_tristate_value(sym, newval)) + break; + } while (oldval != newval); + return newval; +} + +bool sym_string_valid(struct symbol *sym, const char *str) +{ + signed char ch; + + switch (sym->type) { + case S_STRING: + return true; + case S_INT: + ch = *str++; + if (ch == '-') + ch = *str++; + if (!isdigit(ch)) + return false; + if (ch == '0' && *str != 0) + return false; + while ((ch = *str++)) { + if (!isdigit(ch)) + return false; + } + return true; + case S_HEX: + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) + str += 2; + ch = *str++; + do { + if (!isxdigit(ch)) + return false; + } while ((ch = *str++)); + return true; + case S_BOOLEAN: + case S_TRISTATE: + switch (str[0]) { + case 'y': case 'Y': + case 'm': case 'M': + case 'n': case 'N': + return true; + } + return false; + default: + return false; + } +} + +bool sym_string_within_range(struct symbol *sym, const char *str) +{ + struct property *prop; + int val; + + switch (sym->type) { + case S_STRING: + return sym_string_valid(sym, str); + case S_INT: + if (!sym_string_valid(sym, str)) + return false; + prop = sym_get_range_prop(sym); + if (!prop) + return true; + val = strtol(str, NULL, 10); + return val >= sym_get_range_val(prop->expr->left.sym, 10) && + val <= sym_get_range_val(prop->expr->right.sym, 10); + case S_HEX: + if (!sym_string_valid(sym, str)) + return false; + prop = sym_get_range_prop(sym); + if (!prop) + return true; + val = strtol(str, NULL, 16); + return val >= sym_get_range_val(prop->expr->left.sym, 16) && + val <= sym_get_range_val(prop->expr->right.sym, 16); + case S_BOOLEAN: + case S_TRISTATE: + switch (str[0]) { + case 'y': case 'Y': + return sym_tristate_within_range(sym, yes); + case 'm': case 'M': + return sym_tristate_within_range(sym, mod); + case 'n': case 'N': + return sym_tristate_within_range(sym, no); + } + return false; + default: + return false; + } +} + +bool sym_set_string_value(struct symbol *sym, const char *newval) +{ + const char *oldval; + char *val; + int size; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + switch (newval[0]) { + case 'y': case 'Y': + return sym_set_tristate_value(sym, yes); + case 'm': case 'M': + return sym_set_tristate_value(sym, mod); + case 'n': case 'N': + return sym_set_tristate_value(sym, no); + } + return false; + default: + ; + } + + if (!sym_string_within_range(sym, newval)) + return false; + + if (sym->flags & SYMBOL_NEW) { + sym->flags &= ~SYMBOL_NEW; + sym_set_changed(sym); + } + + oldval = sym->user.val; + size = strlen(newval) + 1; + if (sym->type == S_HEX && (newval[0] != '0' || (newval[1] != 'x' && newval[1] != 'X'))) { + size += 2; + sym->user.val = val = malloc(size); + *val++ = '0'; + *val++ = 'x'; + } else if (!oldval || strcmp(oldval, newval)) + sym->user.val = val = malloc(size); + else + return true; + + strcpy(val, newval); + free((void *)oldval); + sym_clear_all_valid(); + + return true; +} + +const char *sym_get_string_value(struct symbol *sym) +{ + tristate val; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + val = sym_get_tristate_value(sym); + switch (val) { + case no: + return "n"; + case mod: + return "m"; + case yes: + return "y"; + } + break; + default: + ; + } + return (const char *)sym->curr.val; +} + +bool sym_is_changable(struct symbol *sym) +{ + return sym->visible > sym->rev_dep.tri; +} + +struct symbol *sym_lookup(const char *name, int isconst) +{ + struct symbol *symbol; + const char *ptr; + char *new_name; + int hash = 0; + + if (name) { + if (name[0] && !name[1]) { + switch (name[0]) { + case 'y': return &symbol_yes; + case 'm': return &symbol_mod; + case 'n': return &symbol_no; + } + } + for (ptr = name; *ptr; ptr++) + hash += *ptr; + hash &= 0xff; + + for (symbol = symbol_hash[hash]; symbol; symbol = symbol->next) { + if (!strcmp(symbol->name, name)) { + if ((isconst && symbol->flags & SYMBOL_CONST) || + (!isconst && !(symbol->flags & SYMBOL_CONST))) + return symbol; + } + } + new_name = strdup(name); + } else { + new_name = NULL; + hash = 256; + } + + symbol = malloc(sizeof(*symbol)); + memset(symbol, 0, sizeof(*symbol)); + symbol->name = new_name; + symbol->type = S_UNKNOWN; + symbol->flags = SYMBOL_NEW; + if (isconst) + symbol->flags |= SYMBOL_CONST; + + symbol->next = symbol_hash[hash]; + symbol_hash[hash] = symbol; + + return symbol; +} + +struct symbol *sym_find(const char *name) +{ + struct symbol *symbol = NULL; + const char *ptr; + int hash = 0; + + if (!name) + return NULL; + + if (name[0] && !name[1]) { + switch (name[0]) { + case 'y': return &symbol_yes; + case 'm': return &symbol_mod; + case 'n': return &symbol_no; + } + } + for (ptr = name; *ptr; ptr++) + hash += *ptr; + hash &= 0xff; + + for (symbol = symbol_hash[hash]; symbol; symbol = symbol->next) { + if (!strcmp(symbol->name, name) && + !(symbol->flags & SYMBOL_CONST)) + break; + } + + return symbol; +} + +struct symbol **sym_re_search(const char *pattern) +{ + struct symbol *sym, **sym_arr = NULL; + int i, cnt, size; + regex_t re; + + cnt = size = 0; + /* Skip if empty */ + if (strlen(pattern) == 0) + return NULL; + if (regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB|REG_ICASE)) + return NULL; + + for_all_symbols(i, sym) { + if (sym->flags & SYMBOL_CONST || !sym->name) + continue; + if (regexec(&re, sym->name, 0, NULL, 0)) + continue; + if (cnt + 1 >= size) { + void *tmp = sym_arr; + size += 16; + sym_arr = realloc(sym_arr, size * sizeof(struct symbol *)); + if (!sym_arr) { + free(tmp); + return NULL; + } + } + sym_arr[cnt++] = sym; + } + if (sym_arr) + sym_arr[cnt] = NULL; + regfree(&re); + + return sym_arr; +} + + +struct symbol *sym_check_deps(struct symbol *sym); + +static struct symbol *sym_check_expr_deps(struct expr *e) +{ + struct symbol *sym; + + if (!e) + return NULL; + switch (e->type) { + case E_OR: + case E_AND: + sym = sym_check_expr_deps(e->left.expr); + if (sym) + return sym; + return sym_check_expr_deps(e->right.expr); + case E_NOT: + return sym_check_expr_deps(e->left.expr); + case E_EQUAL: + case E_UNEQUAL: + sym = sym_check_deps(e->left.sym); + if (sym) + return sym; + return sym_check_deps(e->right.sym); + case E_SYMBOL: + return sym_check_deps(e->left.sym); + default: + break; + } + printf("Oops! How to check %d?\n", e->type); + return NULL; +} + +struct symbol *sym_check_deps(struct symbol *sym) +{ + struct symbol *sym2; + struct property *prop; + + if (sym->flags & SYMBOL_CHECK) { + printf("Warning! Found recursive dependency: %s", sym->name); + return sym; + } + if (sym->flags & SYMBOL_CHECKED) + return NULL; + + sym->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED); + sym2 = sym_check_expr_deps(sym->rev_dep.expr); + if (sym2) + goto out; + + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->type == P_CHOICE || prop->type == P_SELECT) + continue; + sym2 = sym_check_expr_deps(prop->visible.expr); + if (sym2) + goto out; + if (prop->type != P_DEFAULT || sym_is_choice(sym)) + continue; + sym2 = sym_check_expr_deps(prop->expr); + if (sym2) + goto out; + } +out: + if (sym2) { + printf(" %s", sym->name); + if (sym2 == sym) { + printf("\n"); + sym2 = NULL; + } + } + sym->flags &= ~SYMBOL_CHECK; + return sym2; +} + +struct property *prop_alloc(enum prop_type type, struct symbol *sym) +{ + struct property *prop; + struct property **propp; + + prop = malloc(sizeof(*prop)); + memset(prop, 0, sizeof(*prop)); + prop->type = type; + prop->sym = sym; + prop->file = current_file; + prop->lineno = zconf_lineno(); + + /* append property to the prop list of symbol */ + if (sym) { + for (propp = &sym->prop; *propp; propp = &(*propp)->next) + ; + *propp = prop; + } + + return prop; +} + +struct symbol *prop_get_symbol(struct property *prop) +{ + if (prop->expr && (prop->expr->type == E_SYMBOL || + prop->expr->type == E_CHOICE)) + return prop->expr->left.sym; + return NULL; +} + +const char *prop_get_type_name(enum prop_type type) +{ + switch (type) { + case P_PROMPT: + return "prompt"; + case P_COMMENT: + return "comment"; + case P_MENU: + return "menu"; + case P_DEFAULT: + return "default"; + case P_CHOICE: + return "choice"; + case P_SELECT: + return "select"; + case P_RANGE: + return "range"; + case P_UNKNOWN: + break; + } + return "unknown"; +} diff --git a/scripts/kconfig/util.c b/scripts/kconfig/util.c new file mode 100644 index 000000000..aea8d56ce --- /dev/null +++ b/scripts/kconfig/util.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2002-2005 Roman Zippel <zippel@linux-m68k.org> + * Copyright (C) 2002-2005 Sam Ravnborg <sam@ravnborg.org> + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include <string.h> +#include "lkc.h" + +/* file already present in list? If not add it */ +struct file *file_lookup(const char *name) +{ + struct file *file; + + for (file = file_list; file; file = file->next) { + if (!strcmp(name, file->name)) + return file; + } + + file = malloc(sizeof(*file)); + memset(file, 0, sizeof(*file)); + file->name = strdup(name); + file->next = file_list; + file_list = file; + return file; +} + +/* write a dependency file as used by kbuild to track dependencies */ +int file_write_dep(const char *name) +{ + struct file *file; + FILE *out; + + if (!name) + name = ".kconfig.d"; + out = fopen("..config.tmp", "w"); + if (!out) + return 1; + fprintf(out, "deps_config := \\\n"); + for (file = file_list; file; file = file->next) { + if (file->next) + fprintf(out, "\t%s \\\n", file->name); + else + fprintf(out, "\t%s\n", file->name); + } + fprintf(out, "\n.config include/autoconf.h: $(deps_config)\n\n$(deps_config):\n"); + fclose(out); + rename("..config.tmp", name); + return 0; +} + + +/* Allocate initial growable sting */ +struct gstr str_new(void) +{ + struct gstr gs; + gs.s = malloc(sizeof(char) * 64); + gs.len = 16; + strcpy(gs.s, "\0"); + return gs; +} + +/* Allocate and assign growable string */ +struct gstr str_assign(const char *s) +{ + struct gstr gs; + gs.s = strdup(s); + gs.len = strlen(s) + 1; + return gs; +} + +/* Free storage for growable string */ +void str_free(struct gstr *gs) +{ + if (gs->s) + free(gs->s); + gs->s = NULL; + gs->len = 0; +} + +/* Append to growable string */ +void str_append(struct gstr *gs, const char *s) +{ + size_t l = strlen(gs->s) + strlen(s) + 1; + if (l > gs->len) { + gs->s = realloc(gs->s, l); + gs->len = l; + } + strcat(gs->s, s); +} + +/* Append printf formatted string to growable string */ +void str_printf(struct gstr *gs, const char *fmt, ...) +{ + va_list ap; + char s[10000]; /* big enough... */ + va_start(ap, fmt); + vsnprintf(s, sizeof(s), fmt, ap); + str_append(gs, s); + va_end(ap); +} + +/* Retrieve value of growable string */ +const char *str_get(struct gstr *gs) +{ + return gs->s; +} + diff --git a/scripts/kconfig/zconf.gperf b/scripts/kconfig/zconf.gperf new file mode 100644 index 000000000..b03220600 --- /dev/null +++ b/scripts/kconfig/zconf.gperf @@ -0,0 +1,43 @@ +%language=ANSI-C +%define hash-function-name kconf_id_hash +%define lookup-function-name kconf_id_lookup +%define string-pool-name kconf_id_strings +%compare-strncmp +%enum +%pic +%struct-type + +struct kconf_id; + +%% +mainmenu, T_MAINMENU, TF_COMMAND +menu, T_MENU, TF_COMMAND +endmenu, T_ENDMENU, TF_COMMAND +source, T_SOURCE, TF_COMMAND +choice, T_CHOICE, TF_COMMAND +endchoice, T_ENDCHOICE, TF_COMMAND +comment, T_COMMENT, TF_COMMAND +config, T_CONFIG, TF_COMMAND +menuconfig, T_MENUCONFIG, TF_COMMAND +help, T_HELP, TF_COMMAND +if, T_IF, TF_COMMAND|TF_PARAM +endif, T_ENDIF, TF_COMMAND +depends, T_DEPENDS, TF_COMMAND +requires, T_REQUIRES, TF_COMMAND +optional, T_OPTIONAL, TF_COMMAND +default, T_DEFAULT, TF_COMMAND, S_UNKNOWN +prompt, T_PROMPT, TF_COMMAND +tristate, T_TYPE, TF_COMMAND, S_TRISTATE +def_tristate, T_DEFAULT, TF_COMMAND, S_TRISTATE +bool, T_TYPE, TF_COMMAND, S_BOOLEAN +boolean, T_TYPE, TF_COMMAND, S_BOOLEAN +def_bool, T_DEFAULT, TF_COMMAND, S_BOOLEAN +def_boolean, T_DEFAULT, TF_COMMAND, S_BOOLEAN +int, T_TYPE, TF_COMMAND, S_INT +hex, T_TYPE, TF_COMMAND, S_HEX +string, T_TYPE, TF_COMMAND, S_STRING +select, T_SELECT, TF_COMMAND +enable, T_SELECT, TF_COMMAND +range, T_RANGE, TF_COMMAND +on, T_ON, TF_PARAM +%% diff --git a/scripts/kconfig/zconf.hash.c_shipped b/scripts/kconfig/zconf.hash.c_shipped new file mode 100644 index 000000000..345f0fc07 --- /dev/null +++ b/scripts/kconfig/zconf.hash.c_shipped @@ -0,0 +1,231 @@ +/* ANSI-C code produced by gperf version 3.0.1 */ +/* Command-line: gperf */ +/* Computed positions: -k'1,3' */ + +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) +/* The character set is not based on ISO-646. */ +#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>." +#endif + +struct kconf_id; +/* maximum key range = 45, duplicates = 0 */ + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static unsigned int +kconf_id_hash (register const char *str, register unsigned int len) +{ + static unsigned char asso_values[] = + { + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 25, 10, 15, + 0, 0, 5, 47, 0, 0, 47, 47, 0, 10, + 0, 20, 20, 20, 5, 0, 0, 20, 47, 47, + 20, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47 + }; + register int hval = len; + + switch (hval) + { + default: + hval += asso_values[(unsigned char)str[2]]; + /*FALLTHROUGH*/ + case 2: + case 1: + hval += asso_values[(unsigned char)str[0]]; + break; + } + return hval; +} + +struct kconf_id_strings_t + { + char kconf_id_strings_str2[sizeof("if")]; + char kconf_id_strings_str3[sizeof("int")]; + char kconf_id_strings_str4[sizeof("help")]; + char kconf_id_strings_str5[sizeof("endif")]; + char kconf_id_strings_str6[sizeof("select")]; + char kconf_id_strings_str7[sizeof("endmenu")]; + char kconf_id_strings_str8[sizeof("tristate")]; + char kconf_id_strings_str9[sizeof("endchoice")]; + char kconf_id_strings_str10[sizeof("range")]; + char kconf_id_strings_str11[sizeof("string")]; + char kconf_id_strings_str12[sizeof("default")]; + char kconf_id_strings_str13[sizeof("def_bool")]; + char kconf_id_strings_str14[sizeof("menu")]; + char kconf_id_strings_str16[sizeof("def_boolean")]; + char kconf_id_strings_str17[sizeof("def_tristate")]; + char kconf_id_strings_str18[sizeof("mainmenu")]; + char kconf_id_strings_str20[sizeof("menuconfig")]; + char kconf_id_strings_str21[sizeof("config")]; + char kconf_id_strings_str22[sizeof("on")]; + char kconf_id_strings_str23[sizeof("hex")]; + char kconf_id_strings_str26[sizeof("source")]; + char kconf_id_strings_str27[sizeof("depends")]; + char kconf_id_strings_str28[sizeof("optional")]; + char kconf_id_strings_str31[sizeof("enable")]; + char kconf_id_strings_str32[sizeof("comment")]; + char kconf_id_strings_str33[sizeof("requires")]; + char kconf_id_strings_str34[sizeof("bool")]; + char kconf_id_strings_str37[sizeof("boolean")]; + char kconf_id_strings_str41[sizeof("choice")]; + char kconf_id_strings_str46[sizeof("prompt")]; + }; +static struct kconf_id_strings_t kconf_id_strings_contents = + { + "if", + "int", + "help", + "endif", + "select", + "endmenu", + "tristate", + "endchoice", + "range", + "string", + "default", + "def_bool", + "menu", + "def_boolean", + "def_tristate", + "mainmenu", + "menuconfig", + "config", + "on", + "hex", + "source", + "depends", + "optional", + "enable", + "comment", + "requires", + "bool", + "boolean", + "choice", + "prompt" + }; +#define kconf_id_strings ((const char *) &kconf_id_strings_contents) +#ifdef __GNUC__ +__inline +#endif +struct kconf_id * +kconf_id_lookup (register const char *str, register unsigned int len) +{ + enum + { + TOTAL_KEYWORDS = 30, + MIN_WORD_LENGTH = 2, + MAX_WORD_LENGTH = 12, + MIN_HASH_VALUE = 2, + MAX_HASH_VALUE = 46 + }; + + static struct kconf_id wordlist[] = + { + {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str2, T_IF, TF_COMMAND|TF_PARAM}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str3, T_TYPE, TF_COMMAND, S_INT}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str4, T_HELP, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str5, T_ENDIF, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str6, T_SELECT, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str7, T_ENDMENU, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str8, T_TYPE, TF_COMMAND, S_TRISTATE}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str9, T_ENDCHOICE, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str10, T_RANGE, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str11, T_TYPE, TF_COMMAND, S_STRING}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str12, T_DEFAULT, TF_COMMAND, S_UNKNOWN}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str13, T_DEFAULT, TF_COMMAND, S_BOOLEAN}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str14, T_MENU, TF_COMMAND}, + {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str16, T_DEFAULT, TF_COMMAND, S_BOOLEAN}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str17, T_DEFAULT, TF_COMMAND, S_TRISTATE}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str18, T_MAINMENU, TF_COMMAND}, + {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str20, T_MENUCONFIG, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str21, T_CONFIG, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str22, T_ON, TF_PARAM}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str23, T_TYPE, TF_COMMAND, S_HEX}, + {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str26, T_SOURCE, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str27, T_DEPENDS, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str28, T_OPTIONAL, TF_COMMAND}, + {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str31, T_SELECT, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str32, T_COMMENT, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str33, T_REQUIRES, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str34, T_TYPE, TF_COMMAND, S_BOOLEAN}, + {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str37, T_TYPE, TF_COMMAND, S_BOOLEAN}, + {-1}, {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str41, T_CHOICE, TF_COMMAND}, + {-1}, {-1}, {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str46, T_PROMPT, TF_COMMAND} + }; + + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + register int key = kconf_id_hash (str, len); + + if (key <= MAX_HASH_VALUE && key >= 0) + { + register int o = wordlist[key].name; + if (o >= 0) + { + register const char *s = o + kconf_id_strings; + + if (*str == *s && !strncmp (str + 1, s + 1, len - 1) && s[len] == '\0') + return &wordlist[key]; + } + } + } + return 0; +} + diff --git a/scripts/kconfig/zconf.l b/scripts/kconfig/zconf.l new file mode 100644 index 000000000..cfa46077c --- /dev/null +++ b/scripts/kconfig/zconf.l @@ -0,0 +1,350 @@ +%option backup nostdinit noyywrap never-interactive full ecs +%option 8bit backup nodefault perf-report perf-report +%x COMMAND HELP STRING PARAM +%{ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +#define START_STRSIZE 16 + +static struct { + struct file *file; + int lineno; +} current_pos; + +static char *text; +static int text_size, text_asize; + +struct buffer { + struct buffer *parent; + YY_BUFFER_STATE state; +}; + +struct buffer *current_buf; + +static int last_ts, first_ts; + +static void zconf_endhelp(void); +static void zconf_endfile(void); + +void new_string(void) +{ + text = malloc(START_STRSIZE); + text_asize = START_STRSIZE; + text_size = 0; + *text = 0; +} + +void append_string(const char *str, int size) +{ + int new_size = text_size + size + 1; + if (new_size > text_asize) { + new_size += START_STRSIZE - 1; + new_size &= -START_STRSIZE; + text = realloc(text, new_size); + text_asize = new_size; + } + memcpy(text + text_size, str, size); + text_size += size; + text[text_size] = 0; +} + +void alloc_string(const char *str, int size) +{ + text = malloc(size + 1); + memcpy(text, str, size); + text[size] = 0; +} +%} + +ws [ \n\t] +n [A-Za-z0-9_] + +%% + int str = 0; + int ts, i; + +[ \t]*#.*\n | +[ \t]*\n { + current_file->lineno++; + return T_EOL; +} +[ \t]*#.* + + +[ \t]+ { + BEGIN(COMMAND); +} + +. { + unput(yytext[0]); + BEGIN(COMMAND); +} + + +<COMMAND>{ + {n}+ { + struct kconf_id *id = kconf_id_lookup(yytext, yyleng); + BEGIN(PARAM); + current_pos.file = current_file; + current_pos.lineno = current_file->lineno; + if (id && id->flags & TF_COMMAND) { + zconflval.id = id; + return id->token; + } + alloc_string(yytext, yyleng); + zconflval.string = text; + return T_WORD; + } + . + \n { + BEGIN(INITIAL); + current_file->lineno++; + return T_EOL; + } +} + +<PARAM>{ + "&&" return T_AND; + "||" return T_OR; + "(" return T_OPEN_PAREN; + ")" return T_CLOSE_PAREN; + "!" return T_NOT; + "=" return T_EQUAL; + "!=" return T_UNEQUAL; + \"|\' { + str = yytext[0]; + new_string(); + BEGIN(STRING); + } + \n BEGIN(INITIAL); current_file->lineno++; return T_EOL; + --- /* ignore */ + ({n}|[-/.])+ { + struct kconf_id *id = kconf_id_lookup(yytext, yyleng); + if (id && id->flags & TF_PARAM) { + zconflval.id = id; + return id->token; + } + alloc_string(yytext, yyleng); + zconflval.string = text; + return T_WORD; + } + #.* /* comment */ + \\\n current_file->lineno++; + . + <<EOF>> { + BEGIN(INITIAL); + } +} + +<STRING>{ + [^'"\\\n]+/\n { + append_string(yytext, yyleng); + zconflval.string = text; + return T_WORD_QUOTE; + } + [^'"\\\n]+ { + append_string(yytext, yyleng); + } + \\.?/\n { + append_string(yytext + 1, yyleng - 1); + zconflval.string = text; + return T_WORD_QUOTE; + } + \\.? { + append_string(yytext + 1, yyleng - 1); + } + \'|\" { + if (str == yytext[0]) { + BEGIN(PARAM); + zconflval.string = text; + return T_WORD_QUOTE; + } else + append_string(yytext, 1); + } + \n { + printf("%s:%d:warning: multi-line strings not supported\n", zconf_curname(), zconf_lineno()); + current_file->lineno++; + BEGIN(INITIAL); + return T_EOL; + } + <<EOF>> { + BEGIN(INITIAL); + } +} + +<HELP>{ + [ \t]+ { + ts = 0; + for (i = 0; i < yyleng; i++) { + if (yytext[i] == '\t') + ts = (ts & ~7) + 8; + else + ts++; + } + last_ts = ts; + if (first_ts) { + if (ts < first_ts) { + zconf_endhelp(); + return T_HELPTEXT; + } + ts -= first_ts; + while (ts > 8) { + append_string(" ", 8); + ts -= 8; + } + append_string(" ", ts); + } + } + [ \t]*\n/[^ \t\n] { + current_file->lineno++; + zconf_endhelp(); + return T_HELPTEXT; + } + [ \t]*\n { + current_file->lineno++; + append_string("\n", 1); + } + [^ \t\n].* { + append_string(yytext, yyleng); + if (!first_ts) + first_ts = last_ts; + } + <<EOF>> { + zconf_endhelp(); + return T_HELPTEXT; + } +} + +<<EOF>> { + if (current_file) { + zconf_endfile(); + return T_EOL; + } + fclose(yyin); + yyterminate(); +} + +%% +void zconf_starthelp(void) +{ + new_string(); + last_ts = first_ts = 0; + BEGIN(HELP); +} + +static void zconf_endhelp(void) +{ + zconflval.string = text; + BEGIN(INITIAL); +} + + +/* + * Try to open specified file with following names: + * ./name + * $(srctree)/name + * The latter is used when srctree is separate from objtree + * when compiling the kernel. + * Return NULL if file is not found. + */ +FILE *zconf_fopen(const char *name) +{ + char *env, fullname[PATH_MAX+1]; + FILE *f; + + f = fopen(name, "r"); + if (!f && name[0] != '/') { + env = getenv(SRCTREE); + if (env) { + sprintf(fullname, "%s/%s", env, name); + f = fopen(fullname, "r"); + } + } + return f; +} + +void zconf_initscan(const char *name) +{ + yyin = zconf_fopen(name); + if (!yyin) { + printf("can't find file %s\n", name); + exit(1); + } + + current_buf = malloc(sizeof(*current_buf)); + memset(current_buf, 0, sizeof(*current_buf)); + + current_file = file_lookup(name); + current_file->lineno = 1; + current_file->flags = FILE_BUSY; +} + +void zconf_nextfile(const char *name) +{ + struct file *file = file_lookup(name); + struct buffer *buf = malloc(sizeof(*buf)); + memset(buf, 0, sizeof(*buf)); + + current_buf->state = YY_CURRENT_BUFFER; + yyin = zconf_fopen(name); + if (!yyin) { + printf("%s:%d: can't open file \"%s\"\n", zconf_curname(), zconf_lineno(), name); + exit(1); + } + yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE)); + buf->parent = current_buf; + current_buf = buf; + + if (file->flags & FILE_BUSY) { + printf("recursive scan (%s)?\n", name); + exit(1); + } + if (file->flags & FILE_SCANNED) { + printf("file %s already scanned?\n", name); + exit(1); + } + file->flags |= FILE_BUSY; + file->lineno = 1; + file->parent = current_file; + current_file = file; +} + +static void zconf_endfile(void) +{ + struct buffer *parent; + + current_file->flags |= FILE_SCANNED; + current_file->flags &= ~FILE_BUSY; + current_file = current_file->parent; + + parent = current_buf->parent; + if (parent) { + fclose(yyin); + yy_delete_buffer(YY_CURRENT_BUFFER); + yy_switch_to_buffer(parent->state); + } + free(current_buf); + current_buf = parent; +} + +int zconf_lineno(void) +{ + return current_pos.lineno; +} + +char *zconf_curname(void) +{ + return current_pos.file ? current_pos.file->name : "<none>"; +} diff --git a/scripts/kconfig/zconf.tab.c_shipped b/scripts/kconfig/zconf.tab.c_shipped new file mode 100644 index 000000000..e14eafa6c --- /dev/null +++ b/scripts/kconfig/zconf.tab.c_shipped @@ -0,0 +1,2173 @@ +/* A Bison parser, made by GNU Bison 2.0. */ + +/* Skeleton parser for Yacc-like parsing with Bison, + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* As a special exception, when this file is copied by Bison into a + Bison output file, you may use that output file without restriction. + This special exception was added by the Free Software Foundation + in version 1.24 of Bison. */ + +/* Written by Richard Stallman by simplifying the original so called + ``semantic'' parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Using locations. */ +#define YYLSP_NEEDED 0 + +/* Substitute the variable and function names. */ +#define yyparse zconfparse +#define yylex zconflex +#define yyerror zconferror +#define yylval zconflval +#define yychar zconfchar +#define yydebug zconfdebug +#define yynerrs zconfnerrs + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + T_MAINMENU = 258, + T_MENU = 259, + T_ENDMENU = 260, + T_SOURCE = 261, + T_CHOICE = 262, + T_ENDCHOICE = 263, + T_COMMENT = 264, + T_CONFIG = 265, + T_MENUCONFIG = 266, + T_HELP = 267, + T_HELPTEXT = 268, + T_IF = 269, + T_ENDIF = 270, + T_DEPENDS = 271, + T_REQUIRES = 272, + T_OPTIONAL = 273, + T_PROMPT = 274, + T_TYPE = 275, + T_DEFAULT = 276, + T_SELECT = 277, + T_RANGE = 278, + T_ON = 279, + T_WORD = 280, + T_WORD_QUOTE = 281, + T_UNEQUAL = 282, + T_CLOSE_PAREN = 283, + T_OPEN_PAREN = 284, + T_EOL = 285, + T_OR = 286, + T_AND = 287, + T_EQUAL = 288, + T_NOT = 289 + }; +#endif +#define T_MAINMENU 258 +#define T_MENU 259 +#define T_ENDMENU 260 +#define T_SOURCE 261 +#define T_CHOICE 262 +#define T_ENDCHOICE 263 +#define T_COMMENT 264 +#define T_CONFIG 265 +#define T_MENUCONFIG 266 +#define T_HELP 267 +#define T_HELPTEXT 268 +#define T_IF 269 +#define T_ENDIF 270 +#define T_DEPENDS 271 +#define T_REQUIRES 272 +#define T_OPTIONAL 273 +#define T_PROMPT 274 +#define T_TYPE 275 +#define T_DEFAULT 276 +#define T_SELECT 277 +#define T_RANGE 278 +#define T_ON 279 +#define T_WORD 280 +#define T_WORD_QUOTE 281 +#define T_UNEQUAL 282 +#define T_CLOSE_PAREN 283 +#define T_OPEN_PAREN 284 +#define T_EOL 285 +#define T_OR 286 +#define T_AND 287 +#define T_EQUAL 288 +#define T_NOT 289 + + + + +/* Copy the first part of user declarations. */ + + +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +#include "zconf.hash.c" + +#define printd(mask, fmt...) if (cdebug & (mask)) printf(fmt) + +#define PRINTD 0x0001 +#define DEBUG_PARSE 0x0002 + +int cdebug = PRINTD; + +extern int zconflex(void); +static void zconfprint(const char *err, ...); +static void zconf_error(const char *err, ...); +static void zconferror(const char *err); +static bool zconf_endtoken(struct kconf_id *id, int starttoken, int endtoken); + +struct symbol *symbol_hash[257]; + +static struct menu *current_menu, *current_entry; + +#define YYDEBUG 0 +#if YYDEBUG +#define YYERROR_VERBOSE +#endif + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED) + +typedef union YYSTYPE { + char *string; + struct file *file; + struct symbol *symbol; + struct expr *expr; + struct menu *menu; + struct kconf_id *id; +} YYSTYPE; +/* Line 190 of yacc.c. */ + +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +/* Copy the second part of user declarations. */ + + +/* Line 213 of yacc.c. */ + + +#if ! defined (yyoverflow) || YYERROR_VERBOSE + +# ifndef YYFREE +# define YYFREE free +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# endif + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# else +# define YYSTACK_ALLOC alloca +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# else +# if defined (__STDC__) || defined (__cplusplus) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# endif +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# endif +#endif /* ! defined (yyoverflow) || YYERROR_VERBOSE */ + + +#if (! defined (yyoverflow) \ + && (! defined (__cplusplus) \ + || (defined (YYSTYPE_IS_TRIVIAL) && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + short int yyss; + YYSTYPE yyvs; + }; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (short int) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined (__GNUC__) && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + register YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (0) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (0) + +#endif + +#if defined (__STDC__) || defined (__cplusplus) + typedef signed char yysigned_char; +#else + typedef short int yysigned_char; +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 3 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 264 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 35 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 42 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 104 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 175 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 289 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const unsigned char yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const unsigned short int yyprhs[] = +{ + 0, 0, 3, 5, 6, 9, 12, 15, 20, 23, + 28, 33, 37, 39, 41, 43, 45, 47, 49, 51, + 53, 55, 57, 59, 61, 63, 67, 70, 74, 77, + 81, 84, 85, 88, 91, 94, 97, 100, 104, 109, + 114, 119, 125, 128, 131, 133, 137, 138, 141, 144, + 147, 150, 153, 158, 162, 165, 170, 171, 174, 178, + 180, 184, 185, 188, 191, 194, 198, 201, 203, 207, + 208, 211, 214, 217, 221, 225, 228, 231, 234, 235, + 238, 241, 244, 249, 253, 257, 258, 261, 263, 265, + 268, 271, 274, 276, 279, 280, 283, 285, 289, 293, + 297, 300, 304, 308, 310 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yysigned_char yyrhs[] = +{ + 36, 0, -1, 37, -1, -1, 37, 39, -1, 37, + 50, -1, 37, 61, -1, 37, 3, 71, 73, -1, + 37, 72, -1, 37, 25, 1, 30, -1, 37, 38, + 1, 30, -1, 37, 1, 30, -1, 16, -1, 19, + -1, 20, -1, 22, -1, 18, -1, 23, -1, 21, + -1, 30, -1, 56, -1, 65, -1, 42, -1, 44, + -1, 63, -1, 25, 1, 30, -1, 1, 30, -1, + 10, 25, 30, -1, 41, 45, -1, 11, 25, 30, + -1, 43, 45, -1, -1, 45, 46, -1, 45, 69, + -1, 45, 67, -1, 45, 40, -1, 45, 30, -1, + 20, 70, 30, -1, 19, 71, 74, 30, -1, 21, + 75, 74, 30, -1, 22, 25, 74, 30, -1, 23, + 76, 76, 74, 30, -1, 7, 30, -1, 47, 51, + -1, 72, -1, 48, 53, 49, -1, -1, 51, 52, + -1, 51, 69, -1, 51, 67, -1, 51, 30, -1, + 51, 40, -1, 19, 71, 74, 30, -1, 20, 70, + 30, -1, 18, 30, -1, 21, 25, 74, 30, -1, + -1, 53, 39, -1, 14, 75, 73, -1, 72, -1, + 54, 57, 55, -1, -1, 57, 39, -1, 57, 61, + -1, 57, 50, -1, 4, 71, 30, -1, 58, 68, + -1, 72, -1, 59, 62, 60, -1, -1, 62, 39, + -1, 62, 61, -1, 62, 50, -1, 6, 71, 30, + -1, 9, 71, 30, -1, 64, 68, -1, 12, 30, + -1, 66, 13, -1, -1, 68, 69, -1, 68, 30, + -1, 68, 40, -1, 16, 24, 75, 30, -1, 16, + 75, 30, -1, 17, 75, 30, -1, -1, 71, 74, + -1, 25, -1, 26, -1, 5, 30, -1, 8, 30, + -1, 15, 30, -1, 30, -1, 73, 30, -1, -1, + 14, 75, -1, 76, -1, 76, 33, 76, -1, 76, + 27, 76, -1, 29, 75, 28, -1, 34, 75, -1, + 75, 31, 75, -1, 75, 32, 75, -1, 25, -1, + 26, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const unsigned short int yyrline[] = +{ + 0, 103, 103, 105, 107, 108, 109, 110, 111, 112, + 113, 117, 121, 121, 121, 121, 121, 121, 121, 125, + 126, 127, 128, 129, 130, 134, 135, 141, 149, 155, + 163, 173, 175, 176, 177, 178, 179, 182, 190, 196, + 206, 212, 220, 229, 234, 242, 245, 247, 248, 249, + 250, 251, 254, 260, 271, 277, 287, 289, 294, 302, + 310, 313, 315, 316, 317, 322, 329, 334, 342, 345, + 347, 348, 349, 352, 360, 367, 374, 380, 387, 389, + 390, 391, 394, 399, 404, 412, 414, 419, 420, 423, + 424, 425, 429, 430, 433, 434, 437, 438, 439, 440, + 441, 442, 443, 446, 447 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE +/* YYTNME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "T_MAINMENU", "T_MENU", "T_ENDMENU", + "T_SOURCE", "T_CHOICE", "T_ENDCHOICE", "T_COMMENT", "T_CONFIG", + "T_MENUCONFIG", "T_HELP", "T_HELPTEXT", "T_IF", "T_ENDIF", "T_DEPENDS", + "T_REQUIRES", "T_OPTIONAL", "T_PROMPT", "T_TYPE", "T_DEFAULT", + "T_SELECT", "T_RANGE", "T_ON", "T_WORD", "T_WORD_QUOTE", "T_UNEQUAL", + "T_CLOSE_PAREN", "T_OPEN_PAREN", "T_EOL", "T_OR", "T_AND", "T_EQUAL", + "T_NOT", "$accept", "input", "stmt_list", "option_name", "common_stmt", + "option_error", "config_entry_start", "config_stmt", + "menuconfig_entry_start", "menuconfig_stmt", "config_option_list", + "config_option", "choice", "choice_entry", "choice_end", "choice_stmt", + "choice_option_list", "choice_option", "choice_block", "if_entry", + "if_end", "if_stmt", "if_block", "menu", "menu_entry", "menu_end", + "menu_stmt", "menu_block", "source_stmt", "comment", "comment_stmt", + "help_start", "help", "depends_list", "depends", "prompt_stmt_opt", + "prompt", "end", "nl", "if_expr", "expr", "symbol", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const unsigned short int yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const unsigned char yyr1[] = +{ + 0, 35, 36, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 38, 38, 38, 38, 38, 38, 38, 39, + 39, 39, 39, 39, 39, 40, 40, 41, 42, 43, + 44, 45, 45, 45, 45, 45, 45, 46, 46, 46, + 46, 46, 47, 48, 49, 50, 51, 51, 51, 51, + 51, 51, 52, 52, 52, 52, 53, 53, 54, 55, + 56, 57, 57, 57, 57, 58, 59, 60, 61, 62, + 62, 62, 62, 63, 64, 65, 66, 67, 68, 68, + 68, 68, 69, 69, 69, 70, 70, 71, 71, 72, + 72, 72, 73, 73, 74, 74, 75, 75, 75, 75, + 75, 75, 75, 76, 76 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const unsigned char yyr2[] = +{ + 0, 2, 1, 0, 2, 2, 2, 4, 2, 4, + 4, 3, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 3, 2, 3, 2, 3, + 2, 0, 2, 2, 2, 2, 2, 3, 4, 4, + 4, 5, 2, 2, 1, 3, 0, 2, 2, 2, + 2, 2, 4, 3, 2, 4, 0, 2, 3, 1, + 3, 0, 2, 2, 2, 3, 2, 1, 3, 0, + 2, 2, 2, 3, 3, 2, 2, 2, 0, 2, + 2, 2, 4, 3, 3, 0, 2, 1, 1, 2, + 2, 2, 1, 2, 0, 2, 1, 3, 3, 3, + 2, 3, 3, 1, 1 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const unsigned char yydefact[] = +{ + 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 12, 16, 13, 14, + 18, 15, 17, 0, 19, 0, 4, 31, 22, 31, + 23, 46, 56, 5, 61, 20, 78, 69, 6, 24, + 78, 21, 8, 11, 87, 88, 0, 0, 89, 0, + 42, 90, 0, 0, 0, 103, 104, 0, 0, 0, + 96, 91, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 92, 7, 65, 73, 74, 27, 29, 0, + 100, 0, 0, 58, 0, 0, 9, 10, 0, 0, + 0, 0, 0, 85, 0, 0, 0, 0, 36, 35, + 32, 0, 34, 33, 0, 0, 85, 0, 50, 51, + 47, 49, 48, 57, 45, 44, 62, 64, 60, 63, + 59, 80, 81, 79, 70, 72, 68, 71, 67, 93, + 99, 101, 102, 98, 97, 26, 76, 0, 0, 0, + 94, 0, 94, 94, 94, 0, 0, 77, 54, 94, + 0, 94, 0, 83, 84, 0, 0, 37, 86, 0, + 0, 94, 25, 0, 53, 0, 82, 95, 38, 39, + 40, 0, 52, 55, 41 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const short int yydefgoto[] = +{ + -1, 1, 2, 25, 26, 99, 27, 28, 29, 30, + 64, 100, 31, 32, 114, 33, 66, 110, 67, 34, + 118, 35, 68, 36, 37, 126, 38, 70, 39, 40, + 41, 101, 102, 69, 103, 141, 142, 42, 73, 156, + 59, 60 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -78 +static const short int yypact[] = +{ + -78, 2, 159, -78, -21, 0, 0, -12, 0, 1, + 4, 0, 27, 38, 60, 58, -78, -78, -78, -78, + -78, -78, -78, 100, -78, 104, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, 86, 113, -78, 114, + -78, -78, 125, 127, 128, -78, -78, 60, 60, 210, + 65, -78, 141, 142, 39, 103, 182, 200, 6, 66, + 6, 131, -78, 146, -78, -78, -78, -78, -78, 196, + -78, 60, 60, 146, 40, 40, -78, -78, 155, 156, + -2, 60, 0, 0, 60, 105, 40, 194, -78, -78, + -78, 206, -78, -78, 183, 0, 0, 195, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, 197, -78, -78, -78, -78, -78, 60, 213, 216, + 212, 203, 212, 190, 212, 40, 208, -78, -78, 212, + 222, 212, 219, -78, -78, 60, 223, -78, -78, 224, + 225, 212, -78, 226, -78, 227, -78, 47, -78, -78, + -78, 228, -78, -78, -78 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const short int yypgoto[] = +{ + -78, -78, -78, -78, 164, -36, -78, -78, -78, -78, + 230, -78, -78, -78, -78, 29, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, 59, -78, -78, -78, + -78, -78, 198, 220, 24, 157, -5, 169, 202, 74, + -53, -77 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -76 +static const short int yytable[] = +{ + 46, 47, 3, 49, 79, 80, 52, 133, 134, 43, + 6, 7, 8, 9, 10, 11, 12, 13, 48, 145, + 14, 15, 137, 55, 56, 44, 45, 57, 131, 132, + 109, 50, 58, 122, 51, 122, 24, 138, 139, -28, + 88, 143, -28, -28, -28, -28, -28, -28, -28, -28, + -28, 89, 53, -28, -28, 90, 91, -28, 92, 93, + 94, 95, 96, 54, 97, 55, 56, 88, 161, 98, + -66, -66, -66, -66, -66, -66, -66, -66, 81, 82, + -66, -66, 90, 91, 152, 55, 56, 140, 61, 57, + 112, 97, 84, 123, 58, 123, 121, 117, 85, 125, + 149, 62, 167, -30, 88, 63, -30, -30, -30, -30, + -30, -30, -30, -30, -30, 89, 72, -30, -30, 90, + 91, -30, 92, 93, 94, 95, 96, 119, 97, 127, + 144, -75, 88, 98, -75, -75, -75, -75, -75, -75, + -75, -75, -75, 74, 75, -75, -75, 90, 91, -75, + -75, -75, -75, -75, -75, 76, 97, 77, 78, -2, + 4, 121, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 86, 87, 14, 15, 16, 129, 17, 18, 19, + 20, 21, 22, 88, 23, 135, 136, -43, -43, 24, + -43, -43, -43, -43, 89, 146, -43, -43, 90, 91, + 104, 105, 106, 107, 155, 7, 8, 97, 10, 11, + 12, 13, 108, 148, 14, 15, 158, 159, 160, 147, + 151, 81, 82, 163, 130, 165, 155, 81, 82, 82, + 24, 113, 116, 157, 124, 171, 115, 120, 162, 128, + 72, 81, 82, 153, 81, 82, 154, 81, 82, 166, + 81, 82, 164, 168, 169, 170, 172, 173, 174, 65, + 71, 83, 0, 150, 111 +}; + +static const short int yycheck[] = +{ + 5, 6, 0, 8, 57, 58, 11, 84, 85, 30, + 4, 5, 6, 7, 8, 9, 10, 11, 30, 96, + 14, 15, 24, 25, 26, 25, 26, 29, 81, 82, + 66, 30, 34, 69, 30, 71, 30, 90, 91, 0, + 1, 94, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 25, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 25, 25, 25, 26, 1, 145, 30, + 4, 5, 6, 7, 8, 9, 10, 11, 31, 32, + 14, 15, 16, 17, 137, 25, 26, 92, 30, 29, + 66, 25, 27, 69, 34, 71, 30, 68, 33, 70, + 105, 1, 155, 0, 1, 1, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 30, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 68, 25, 70, + 25, 0, 1, 30, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 30, 30, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 30, 25, 30, 30, 0, + 1, 30, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 30, 30, 14, 15, 16, 30, 18, 19, 20, + 21, 22, 23, 1, 25, 30, 30, 5, 6, 30, + 8, 9, 10, 11, 12, 1, 14, 15, 16, 17, + 18, 19, 20, 21, 14, 5, 6, 25, 8, 9, + 10, 11, 30, 30, 14, 15, 142, 143, 144, 13, + 25, 31, 32, 149, 28, 151, 14, 31, 32, 32, + 30, 67, 68, 30, 70, 161, 67, 68, 30, 70, + 30, 31, 32, 30, 31, 32, 30, 31, 32, 30, + 31, 32, 30, 30, 30, 30, 30, 30, 30, 29, + 40, 59, -1, 106, 66 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const unsigned char yystos[] = +{ + 0, 36, 37, 0, 1, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, + 21, 22, 23, 25, 30, 38, 39, 41, 42, 43, + 44, 47, 48, 50, 54, 56, 58, 59, 61, 63, + 64, 65, 72, 30, 25, 26, 71, 71, 30, 71, + 30, 30, 71, 25, 25, 25, 26, 29, 34, 75, + 76, 30, 1, 1, 45, 45, 51, 53, 57, 68, + 62, 68, 30, 73, 30, 30, 30, 30, 30, 75, + 75, 31, 32, 73, 27, 33, 30, 30, 1, 12, + 16, 17, 19, 20, 21, 22, 23, 25, 30, 40, + 46, 66, 67, 69, 18, 19, 20, 21, 30, 40, + 52, 67, 69, 39, 49, 72, 39, 50, 55, 61, + 72, 30, 40, 69, 39, 50, 60, 61, 72, 30, + 28, 75, 75, 76, 76, 30, 30, 24, 75, 75, + 71, 70, 71, 75, 25, 76, 1, 13, 30, 71, + 70, 25, 75, 30, 30, 14, 74, 30, 74, 74, + 74, 76, 30, 74, 30, 74, 30, 75, 30, 30, + 30, 74, 30, 30, 30 +}; + +#if ! defined (YYSIZE_T) && defined (__SIZE_TYPE__) +# define YYSIZE_T __SIZE_TYPE__ +#endif +#if ! defined (YYSIZE_T) && defined (size_t) +# define YYSIZE_T size_t +#endif +#if ! defined (YYSIZE_T) +# if defined (__STDC__) || defined (__cplusplus) +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# endif +#endif +#if ! defined (YYSIZE_T) +# define YYSIZE_T unsigned int +#endif + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror ("syntax error: cannot back up");\ + YYERROR; \ + } \ +while (0) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (N) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (0) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (YYLEX_PARAM) +#else +# define YYLEX yylex () +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include <stdio.h> /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yysymprint (stderr, \ + Type, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yy_stack_print (short int *bottom, short int *top) +#else +static void +yy_stack_print (bottom, top) + short int *bottom; + short int *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (/* Nothing. */; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yy_reduce_print (int yyrule) +#else +static void +yy_reduce_print (yyrule) + int yyrule; +#endif +{ + int yyi; + unsigned int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %u), ", + yyrule - 1, yylno); + /* Print the symbols being reduced, and their result. */ + for (yyi = yyprhs[yyrule]; 0 <= yyrhs[yyi]; yyi++) + YYFPRINTF (stderr, "%s ", yytname [yyrhs[yyi]]); + YYFPRINTF (stderr, "-> %s\n", yytname [yyr1[yyrule]]); +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (Rule); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + SIZE_MAX < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined (__GLIBC__) && defined (_STRING_H) +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +static YYSIZE_T +# if defined (__STDC__) || defined (__cplusplus) +yystrlen (const char *yystr) +# else +yystrlen (yystr) + const char *yystr; +# endif +{ + register const char *yys = yystr; + + while (*yys++ != '\0') + continue; + + return yys - yystr - 1; +} +# endif +# endif + +# ifndef yystpcpy +# if defined (__GLIBC__) && defined (_STRING_H) && defined (_GNU_SOURCE) +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +static char * +# if defined (__STDC__) || defined (__cplusplus) +yystpcpy (char *yydest, const char *yysrc) +# else +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +# endif +{ + register char *yyd = yydest; + register const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +#endif /* !YYERROR_VERBOSE */ + + + +#if YYDEBUG +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yysymprint (FILE *yyoutput, int yytype, YYSTYPE *yyvaluep) +#else +static void +yysymprint (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + YYSTYPE *yyvaluep; +#endif +{ + /* Pacify ``unused variable'' warnings. */ + (void) yyvaluep; + + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# endif + switch (yytype) + { + default: + break; + } + YYFPRINTF (yyoutput, ")"); +} + +#endif /* ! YYDEBUG */ +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) +#else +static void +yydestruct (yymsg, yytype, yyvaluep) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; +#endif +{ + /* Pacify ``unused variable'' warnings. */ + (void) yyvaluep; + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + case 48: /* choice_entry */ + + { + fprintf(stderr, "%s:%d: missing end statement for this entry\n", + (yyvaluep->menu)->file->name, (yyvaluep->menu)->lineno); + if (current_menu == (yyvaluep->menu)) + menu_end_menu(); +}; + + break; + case 54: /* if_entry */ + + { + fprintf(stderr, "%s:%d: missing end statement for this entry\n", + (yyvaluep->menu)->file->name, (yyvaluep->menu)->lineno); + if (current_menu == (yyvaluep->menu)) + menu_end_menu(); +}; + + break; + case 59: /* menu_entry */ + + { + fprintf(stderr, "%s:%d: missing end statement for this entry\n", + (yyvaluep->menu)->file->name, (yyvaluep->menu)->lineno); + if (current_menu == (yyvaluep->menu)) + menu_end_menu(); +}; + + break; + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +# if defined (__STDC__) || defined (__cplusplus) +int yyparse (void *YYPARSE_PARAM); +# else +int yyparse (); +# endif +#else /* ! YYPARSE_PARAM */ +#if defined (__STDC__) || defined (__cplusplus) +int yyparse (void); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + +/* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +# if defined (__STDC__) || defined (__cplusplus) +int yyparse (void *YYPARSE_PARAM) +# else +int yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +# endif +#else /* ! YYPARSE_PARAM */ +#if defined (__STDC__) || defined (__cplusplus) +int +yyparse (void) +#else +int +yyparse () + +#endif +#endif +{ + + register int yystate; + register int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + short int yyssa[YYINITDEPTH]; + short int *yyss = yyssa; + register short int *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + register YYSTYPE *yyvsp; + + + +#define YYPOPSTACK (yyvsp--, yyssp--) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + + /* When reducing, the number of symbols on the RHS of the reduced + rule. */ + int yylen; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + + + yyvsp[0] = yylval; + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. so pushing a state here evens the stacks. + */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + short int *yyss1 = yyss; + + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow ("parser stack overflow", + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyoverflowlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyoverflowlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + short int *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyoverflowlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + +/* Do appropriate processing given the current state. */ +/* Read a look-ahead token if we need one and don't already have one. */ +/* yyresume: */ + + /* First try to decide what to do without reference to look-ahead token. */ + + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the token being shifted unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + *++yyvsp = yylval; + + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + yystate = yyn; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 8: + + { zconf_error("unexpected end statement"); ;} + break; + + case 9: + + { zconf_error("unknown statement \"%s\"", (yyvsp[-2].string)); ;} + break; + + case 10: + + { + zconf_error("unexpected option \"%s\"", kconf_id_strings + (yyvsp[-2].id)->name); +;} + break; + + case 11: + + { zconf_error("invalid statement"); ;} + break; + + case 25: + + { zconf_error("unknown option \"%s\"", (yyvsp[-2].string)); ;} + break; + + case 26: + + { zconf_error("invalid option"); ;} + break; + + case 27: + + { + struct symbol *sym = sym_lookup((yyvsp[-1].string), 0); + sym->flags |= SYMBOL_OPTIONAL; + menu_add_entry(sym); + printd(DEBUG_PARSE, "%s:%d:config %s\n", zconf_curname(), zconf_lineno(), (yyvsp[-1].string)); +;} + break; + + case 28: + + { + menu_end_entry(); + printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 29: + + { + struct symbol *sym = sym_lookup((yyvsp[-1].string), 0); + sym->flags |= SYMBOL_OPTIONAL; + menu_add_entry(sym); + printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", zconf_curname(), zconf_lineno(), (yyvsp[-1].string)); +;} + break; + + case 30: + + { + if (current_entry->prompt) + current_entry->prompt->type = P_MENU; + else + zconfprint("warning: menuconfig statement without prompt"); + menu_end_entry(); + printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 37: + + { + menu_set_type((yyvsp[-2].id)->stype); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", + zconf_curname(), zconf_lineno(), + (yyvsp[-2].id)->stype); +;} + break; + + case 38: + + { + menu_add_prompt(P_PROMPT, (yyvsp[-2].string), (yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 39: + + { + menu_add_expr(P_DEFAULT, (yyvsp[-2].expr), (yyvsp[-1].expr)); + if ((yyvsp[-3].id)->stype != S_UNKNOWN) + menu_set_type((yyvsp[-3].id)->stype); + printd(DEBUG_PARSE, "%s:%d:default(%u)\n", + zconf_curname(), zconf_lineno(), + (yyvsp[-3].id)->stype); +;} + break; + + case 40: + + { + menu_add_symbol(P_SELECT, sym_lookup((yyvsp[-2].string), 0), (yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:select\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 41: + + { + menu_add_expr(P_RANGE, expr_alloc_comp(E_RANGE,(yyvsp[-3].symbol), (yyvsp[-2].symbol)), (yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:range\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 42: + + { + struct symbol *sym = sym_lookup(NULL, 0); + sym->flags |= SYMBOL_CHOICE; + menu_add_entry(sym); + menu_add_expr(P_CHOICE, NULL, NULL); + printd(DEBUG_PARSE, "%s:%d:choice\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 43: + + { + (yyval.menu) = menu_add_menu(); +;} + break; + + case 44: + + { + if (zconf_endtoken((yyvsp[0].id), T_CHOICE, T_ENDCHOICE)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endchoice\n", zconf_curname(), zconf_lineno()); + } +;} + break; + + case 52: + + { + menu_add_prompt(P_PROMPT, (yyvsp[-2].string), (yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 53: + + { + if ((yyvsp[-2].id)->stype == S_BOOLEAN || (yyvsp[-2].id)->stype == S_TRISTATE) { + menu_set_type((yyvsp[-2].id)->stype); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", + zconf_curname(), zconf_lineno(), + (yyvsp[-2].id)->stype); + } else + YYERROR; +;} + break; + + case 54: + + { + current_entry->sym->flags |= SYMBOL_OPTIONAL; + printd(DEBUG_PARSE, "%s:%d:optional\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 55: + + { + if ((yyvsp[-3].id)->stype == S_UNKNOWN) { + menu_add_symbol(P_DEFAULT, sym_lookup((yyvsp[-2].string), 0), (yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:default\n", + zconf_curname(), zconf_lineno()); + } else + YYERROR; +;} + break; + + case 58: + + { + printd(DEBUG_PARSE, "%s:%d:if\n", zconf_curname(), zconf_lineno()); + menu_add_entry(NULL); + menu_add_dep((yyvsp[-1].expr)); + (yyval.menu) = menu_add_menu(); +;} + break; + + case 59: + + { + if (zconf_endtoken((yyvsp[0].id), T_IF, T_ENDIF)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endif\n", zconf_curname(), zconf_lineno()); + } +;} + break; + + case 65: + + { + menu_add_entry(NULL); + menu_add_prompt(P_MENU, (yyvsp[-1].string), NULL); + printd(DEBUG_PARSE, "%s:%d:menu\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 66: + + { + (yyval.menu) = menu_add_menu(); +;} + break; + + case 67: + + { + if (zconf_endtoken((yyvsp[0].id), T_MENU, T_ENDMENU)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endmenu\n", zconf_curname(), zconf_lineno()); + } +;} + break; + + case 73: + + { + printd(DEBUG_PARSE, "%s:%d:source %s\n", zconf_curname(), zconf_lineno(), (yyvsp[-1].string)); + zconf_nextfile((yyvsp[-1].string)); +;} + break; + + case 74: + + { + menu_add_entry(NULL); + menu_add_prompt(P_COMMENT, (yyvsp[-1].string), NULL); + printd(DEBUG_PARSE, "%s:%d:comment\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 75: + + { + menu_end_entry(); +;} + break; + + case 76: + + { + printd(DEBUG_PARSE, "%s:%d:help\n", zconf_curname(), zconf_lineno()); + zconf_starthelp(); +;} + break; + + case 77: + + { + current_entry->sym->help = (yyvsp[0].string); +;} + break; + + case 82: + + { + menu_add_dep((yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:depends on\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 83: + + { + menu_add_dep((yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:depends\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 84: + + { + menu_add_dep((yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:requires\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 86: + + { + menu_add_prompt(P_PROMPT, (yyvsp[-1].string), (yyvsp[0].expr)); +;} + break; + + case 89: + + { (yyval.id) = (yyvsp[-1].id); ;} + break; + + case 90: + + { (yyval.id) = (yyvsp[-1].id); ;} + break; + + case 91: + + { (yyval.id) = (yyvsp[-1].id); ;} + break; + + case 94: + + { (yyval.expr) = NULL; ;} + break; + + case 95: + + { (yyval.expr) = (yyvsp[0].expr); ;} + break; + + case 96: + + { (yyval.expr) = expr_alloc_symbol((yyvsp[0].symbol)); ;} + break; + + case 97: + + { (yyval.expr) = expr_alloc_comp(E_EQUAL, (yyvsp[-2].symbol), (yyvsp[0].symbol)); ;} + break; + + case 98: + + { (yyval.expr) = expr_alloc_comp(E_UNEQUAL, (yyvsp[-2].symbol), (yyvsp[0].symbol)); ;} + break; + + case 99: + + { (yyval.expr) = (yyvsp[-1].expr); ;} + break; + + case 100: + + { (yyval.expr) = expr_alloc_one(E_NOT, (yyvsp[0].expr)); ;} + break; + + case 101: + + { (yyval.expr) = expr_alloc_two(E_OR, (yyvsp[-2].expr), (yyvsp[0].expr)); ;} + break; + + case 102: + + { (yyval.expr) = expr_alloc_two(E_AND, (yyvsp[-2].expr), (yyvsp[0].expr)); ;} + break; + + case 103: + + { (yyval.symbol) = sym_lookup((yyvsp[0].string), 0); free((yyvsp[0].string)); ;} + break; + + case 104: + + { (yyval.symbol) = sym_lookup((yyvsp[0].string), 1); free((yyvsp[0].string)); ;} + break; + + + } + +/* Line 1037 of yacc.c. */ + + + yyvsp -= yylen; + yyssp -= yylen; + + + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if YYERROR_VERBOSE + yyn = yypact[yystate]; + + if (YYPACT_NINF < yyn && yyn < YYLAST) + { + YYSIZE_T yysize = 0; + int yytype = YYTRANSLATE (yychar); + const char* yyprefix; + char *yymsg; + int yyx; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 0; + + yyprefix = ", expecting "; + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + yysize += yystrlen (yyprefix) + yystrlen (yytname [yyx]); + yycount += 1; + if (yycount == 5) + { + yysize = 0; + break; + } + } + yysize += (sizeof ("syntax error, unexpected ") + + yystrlen (yytname[yytype])); + yymsg = (char *) YYSTACK_ALLOC (yysize); + if (yymsg != 0) + { + char *yyp = yystpcpy (yymsg, "syntax error, unexpected "); + yyp = yystpcpy (yyp, yytname[yytype]); + + if (yycount < 5) + { + yyprefix = ", expecting "; + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + yyp = yystpcpy (yyp, yyprefix); + yyp = yystpcpy (yyp, yytname[yyx]); + yyprefix = " or "; + } + } + yyerror (yymsg); + YYSTACK_FREE (yymsg); + } + else + yyerror ("syntax error; also virtual memory exhausted"); + } + else +#endif /* YYERROR_VERBOSE */ + yyerror ("syntax error"); + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* If at end of input, pop the error token, + then the rest of the stack, then return failure. */ + if (yychar == YYEOF) + for (;;) + { + + YYPOPSTACK; + if (yyssp == yyss) + YYABORT; + yydestruct ("Error: popping", + yystos[*yyssp], yyvsp); + } + } + else + { + yydestruct ("Error: discarding", yytoken, &yylval); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + +#ifdef __GNUC__ + /* Pacify GCC when the user code never invokes YYERROR and the label + yyerrorlab therefore never appears in user code. */ + if (0) + goto yyerrorlab; +#endif + +yyvsp -= yylen; + yyssp -= yylen; + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", yystos[yystate], yyvsp); + YYPOPSTACK; + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yydestruct ("Error: discarding lookahead", + yytoken, &yylval); + yychar = YYEMPTY; + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*----------------------------------------------. +| yyoverflowlab -- parser overflow comes here. | +`----------------------------------------------*/ +yyoverflowlab: + yyerror ("parser stack overflow"); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + return yyresult; +} + + + + + +void conf_parse(const char *name) +{ + struct symbol *sym; + int i; + + zconf_initscan(name); + + sym_init(); + menu_init(); + modules_sym = sym_lookup("MODULES", 0); + rootmenu.prompt = menu_add_prompt(P_MENU, "Busybox Configuration", NULL); + +#if YYDEBUG + if (getenv("ZCONF_DEBUG")) + zconfdebug = 1; +#endif + zconfparse(); + if (zconfnerrs) + exit(1); + menu_finalize(&rootmenu); + for_all_symbols(i, sym) { + sym_check_deps(sym); + } + + sym_change_count = 1; +} + +const char *zconf_tokenname(int token) +{ + switch (token) { + case T_MENU: return "menu"; + case T_ENDMENU: return "endmenu"; + case T_CHOICE: return "choice"; + case T_ENDCHOICE: return "endchoice"; + case T_IF: return "if"; + case T_ENDIF: return "endif"; + case T_DEPENDS: return "depends"; + } + return "<token>"; +} + +static bool zconf_endtoken(struct kconf_id *id, int starttoken, int endtoken) +{ + if (id->token != endtoken) { + zconf_error("unexpected '%s' within %s block", + kconf_id_strings + id->name, zconf_tokenname(starttoken)); + zconfnerrs++; + return false; + } + if (current_menu->file != current_file) { + zconf_error("'%s' in different file than '%s'", + kconf_id_strings + id->name, zconf_tokenname(starttoken)); + fprintf(stderr, "%s:%d: location of the '%s'\n", + current_menu->file->name, current_menu->lineno, + zconf_tokenname(starttoken)); + zconfnerrs++; + return false; + } + return true; +} + +static void zconfprint(const char *err, ...) +{ + va_list ap; + + fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void zconf_error(const char *err, ...) +{ + va_list ap; + + zconfnerrs++; + fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void zconferror(const char *err) +{ +#if YYDEBUG + fprintf(stderr, "%s:%d: %s\n", zconf_curname(), zconf_lineno() + 1, err); +#endif +} + +void print_quoted_string(FILE *out, const char *str) +{ + const char *p; + int len; + + putc('"', out); + while ((p = strchr(str, '"'))) { + len = p - str; + if (len) + fprintf(out, "%.*s", len, str); + fputs("\\\"", out); + str = p + 1; + } + fputs(str, out); + putc('"', out); +} + +void print_symbol(FILE *out, struct menu *menu) +{ + struct symbol *sym = menu->sym; + struct property *prop; + + if (sym_is_choice(sym)) + fprintf(out, "choice\n"); + else + fprintf(out, "config %s\n", sym->name); + switch (sym->type) { + case S_BOOLEAN: + fputs(" boolean\n", out); + break; + case S_TRISTATE: + fputs(" tristate\n", out); + break; + case S_STRING: + fputs(" string\n", out); + break; + case S_INT: + fputs(" integer\n", out); + break; + case S_HEX: + fputs(" hex\n", out); + break; + default: + fputs(" ???\n", out); + break; + } + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->menu != menu) + continue; + switch (prop->type) { + case P_PROMPT: + fputs(" prompt ", out); + print_quoted_string(out, prop->text); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_DEFAULT: + fputs( " default ", out); + expr_fprint(prop->expr, out); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_CHOICE: + fputs(" #choice value\n", out); + break; + default: + fprintf(out, " unknown prop %d!\n", prop->type); + break; + } + } + if (sym->help) { + int len = strlen(sym->help); + while (sym->help[--len] == '\n') + sym->help[len] = 0; + fprintf(out, " help\n%s\n", sym->help); + } + fputc('\n', out); +} + +void zconfdump(FILE *out) +{ + struct property *prop; + struct symbol *sym; + struct menu *menu; + + menu = rootmenu.list; + while (menu) { + if ((sym = menu->sym)) + print_symbol(out, menu); + else if ((prop = menu->prompt)) { + switch (prop->type) { + case P_COMMENT: + fputs("\ncomment ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + case P_MENU: + fputs("\nmenu ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + default: + ; + } + if (!expr_is_yes(prop->visible.expr)) { + fputs(" depends ", out); + expr_fprint(prop->visible.expr, out); + fputc('\n', out); + } + fputs("\n", out); + } + + if (menu->list) + menu = menu->list; + else if (menu->next) + menu = menu->next; + else while ((menu = menu->parent)) { + if (menu->prompt && menu->prompt->type == P_MENU) + fputs("\nendmenu\n", out); + if (menu->next) { + menu = menu->next; + break; + } + } + } +} + +#include "lex.zconf.c" +#include "util.c" +#include "confdata.c" +#include "expr.c" +#include "symbol.c" +#include "menu.c" + + diff --git a/scripts/kconfig/zconf.y b/scripts/kconfig/zconf.y new file mode 100644 index 000000000..0a7a79664 --- /dev/null +++ b/scripts/kconfig/zconf.y @@ -0,0 +1,681 @@ +%{ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +#include "zconf.hash.c" + +#define printd(mask, fmt...) if (cdebug & (mask)) printf(fmt) + +#define PRINTD 0x0001 +#define DEBUG_PARSE 0x0002 + +int cdebug = PRINTD; + +extern int zconflex(void); +static void zconfprint(const char *err, ...); +static void zconf_error(const char *err, ...); +static void zconferror(const char *err); +static bool zconf_endtoken(struct kconf_id *id, int starttoken, int endtoken); + +struct symbol *symbol_hash[257]; + +static struct menu *current_menu, *current_entry; + +#define YYDEBUG 0 +#if YYDEBUG +#define YYERROR_VERBOSE +#endif +%} +%expect 26 + +%union +{ + char *string; + struct file *file; + struct symbol *symbol; + struct expr *expr; + struct menu *menu; + struct kconf_id *id; +} + +%token <id>T_MAINMENU +%token <id>T_MENU +%token <id>T_ENDMENU +%token <id>T_SOURCE +%token <id>T_CHOICE +%token <id>T_ENDCHOICE +%token <id>T_COMMENT +%token <id>T_CONFIG +%token <id>T_MENUCONFIG +%token <id>T_HELP +%token <string> T_HELPTEXT +%token <id>T_IF +%token <id>T_ENDIF +%token <id>T_DEPENDS +%token <id>T_REQUIRES +%token <id>T_OPTIONAL +%token <id>T_PROMPT +%token <id>T_TYPE +%token <id>T_DEFAULT +%token <id>T_SELECT +%token <id>T_RANGE +%token <id>T_ON +%token <string> T_WORD +%token <string> T_WORD_QUOTE +%token T_UNEQUAL +%token T_CLOSE_PAREN +%token T_OPEN_PAREN +%token T_EOL + +%left T_OR +%left T_AND +%left T_EQUAL T_UNEQUAL +%nonassoc T_NOT + +%type <string> prompt +%type <symbol> symbol +%type <expr> expr +%type <expr> if_expr +%type <id> end +%type <id> option_name +%type <menu> if_entry menu_entry choice_entry + +%destructor { + fprintf(stderr, "%s:%d: missing end statement for this entry\n", + $$->file->name, $$->lineno); + if (current_menu == $$) + menu_end_menu(); +} if_entry menu_entry choice_entry + +%% +input: stmt_list; + +stmt_list: + /* empty */ + | stmt_list common_stmt + | stmt_list choice_stmt + | stmt_list menu_stmt + | stmt_list T_MAINMENU prompt nl + | stmt_list end { zconf_error("unexpected end statement"); } + | stmt_list T_WORD error T_EOL { zconf_error("unknown statement \"%s\"", $2); } + | stmt_list option_name error T_EOL +{ + zconf_error("unexpected option \"%s\"", kconf_id_strings + $2->name); +} + | stmt_list error T_EOL { zconf_error("invalid statement"); } +; + +option_name: + T_DEPENDS | T_PROMPT | T_TYPE | T_SELECT | T_OPTIONAL | T_RANGE | T_DEFAULT +; + +common_stmt: + T_EOL + | if_stmt + | comment_stmt + | config_stmt + | menuconfig_stmt + | source_stmt +; + +option_error: + T_WORD error T_EOL { zconf_error("unknown option \"%s\"", $1); } + | error T_EOL { zconf_error("invalid option"); } +; + + +/* config/menuconfig entry */ + +config_entry_start: T_CONFIG T_WORD T_EOL +{ + struct symbol *sym = sym_lookup($2, 0); + sym->flags |= SYMBOL_OPTIONAL; + menu_add_entry(sym); + printd(DEBUG_PARSE, "%s:%d:config %s\n", zconf_curname(), zconf_lineno(), $2); +}; + +config_stmt: config_entry_start config_option_list +{ + menu_end_entry(); + printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); +}; + +menuconfig_entry_start: T_MENUCONFIG T_WORD T_EOL +{ + struct symbol *sym = sym_lookup($2, 0); + sym->flags |= SYMBOL_OPTIONAL; + menu_add_entry(sym); + printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", zconf_curname(), zconf_lineno(), $2); +}; + +menuconfig_stmt: menuconfig_entry_start config_option_list +{ + if (current_entry->prompt) + current_entry->prompt->type = P_MENU; + else + zconfprint("warning: menuconfig statement without prompt"); + menu_end_entry(); + printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); +}; + +config_option_list: + /* empty */ + | config_option_list config_option + | config_option_list depends + | config_option_list help + | config_option_list option_error + | config_option_list T_EOL +; + +config_option: T_TYPE prompt_stmt_opt T_EOL +{ + menu_set_type($1->stype); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", + zconf_curname(), zconf_lineno(), + $1->stype); +}; + +config_option: T_PROMPT prompt if_expr T_EOL +{ + menu_add_prompt(P_PROMPT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno()); +}; + +config_option: T_DEFAULT expr if_expr T_EOL +{ + menu_add_expr(P_DEFAULT, $2, $3); + if ($1->stype != S_UNKNOWN) + menu_set_type($1->stype); + printd(DEBUG_PARSE, "%s:%d:default(%u)\n", + zconf_curname(), zconf_lineno(), + $1->stype); +}; + +config_option: T_SELECT T_WORD if_expr T_EOL +{ + menu_add_symbol(P_SELECT, sym_lookup($2, 0), $3); + printd(DEBUG_PARSE, "%s:%d:select\n", zconf_curname(), zconf_lineno()); +}; + +config_option: T_RANGE symbol symbol if_expr T_EOL +{ + menu_add_expr(P_RANGE, expr_alloc_comp(E_RANGE,$2, $3), $4); + printd(DEBUG_PARSE, "%s:%d:range\n", zconf_curname(), zconf_lineno()); +}; + +/* choice entry */ + +choice: T_CHOICE T_EOL +{ + struct symbol *sym = sym_lookup(NULL, 0); + sym->flags |= SYMBOL_CHOICE; + menu_add_entry(sym); + menu_add_expr(P_CHOICE, NULL, NULL); + printd(DEBUG_PARSE, "%s:%d:choice\n", zconf_curname(), zconf_lineno()); +}; + +choice_entry: choice choice_option_list +{ + $$ = menu_add_menu(); +}; + +choice_end: end +{ + if (zconf_endtoken($1, T_CHOICE, T_ENDCHOICE)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endchoice\n", zconf_curname(), zconf_lineno()); + } +}; + +choice_stmt: choice_entry choice_block choice_end +; + +choice_option_list: + /* empty */ + | choice_option_list choice_option + | choice_option_list depends + | choice_option_list help + | choice_option_list T_EOL + | choice_option_list option_error +; + +choice_option: T_PROMPT prompt if_expr T_EOL +{ + menu_add_prompt(P_PROMPT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno()); +}; + +choice_option: T_TYPE prompt_stmt_opt T_EOL +{ + if ($1->stype == S_BOOLEAN || $1->stype == S_TRISTATE) { + menu_set_type($1->stype); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", + zconf_curname(), zconf_lineno(), + $1->stype); + } else + YYERROR; +}; + +choice_option: T_OPTIONAL T_EOL +{ + current_entry->sym->flags |= SYMBOL_OPTIONAL; + printd(DEBUG_PARSE, "%s:%d:optional\n", zconf_curname(), zconf_lineno()); +}; + +choice_option: T_DEFAULT T_WORD if_expr T_EOL +{ + if ($1->stype == S_UNKNOWN) { + menu_add_symbol(P_DEFAULT, sym_lookup($2, 0), $3); + printd(DEBUG_PARSE, "%s:%d:default\n", + zconf_curname(), zconf_lineno()); + } else + YYERROR; +}; + +choice_block: + /* empty */ + | choice_block common_stmt +; + +/* if entry */ + +if_entry: T_IF expr nl +{ + printd(DEBUG_PARSE, "%s:%d:if\n", zconf_curname(), zconf_lineno()); + menu_add_entry(NULL); + menu_add_dep($2); + $$ = menu_add_menu(); +}; + +if_end: end +{ + if (zconf_endtoken($1, T_IF, T_ENDIF)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endif\n", zconf_curname(), zconf_lineno()); + } +}; + +if_stmt: if_entry if_block if_end +; + +if_block: + /* empty */ + | if_block common_stmt + | if_block menu_stmt + | if_block choice_stmt +; + +/* menu entry */ + +menu: T_MENU prompt T_EOL +{ + menu_add_entry(NULL); + menu_add_prompt(P_MENU, $2, NULL); + printd(DEBUG_PARSE, "%s:%d:menu\n", zconf_curname(), zconf_lineno()); +}; + +menu_entry: menu depends_list +{ + $$ = menu_add_menu(); +}; + +menu_end: end +{ + if (zconf_endtoken($1, T_MENU, T_ENDMENU)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endmenu\n", zconf_curname(), zconf_lineno()); + } +}; + +menu_stmt: menu_entry menu_block menu_end +; + +menu_block: + /* empty */ + | menu_block common_stmt + | menu_block menu_stmt + | menu_block choice_stmt +; + +source_stmt: T_SOURCE prompt T_EOL +{ + printd(DEBUG_PARSE, "%s:%d:source %s\n", zconf_curname(), zconf_lineno(), $2); + zconf_nextfile($2); +}; + +/* comment entry */ + +comment: T_COMMENT prompt T_EOL +{ + menu_add_entry(NULL); + menu_add_prompt(P_COMMENT, $2, NULL); + printd(DEBUG_PARSE, "%s:%d:comment\n", zconf_curname(), zconf_lineno()); +}; + +comment_stmt: comment depends_list +{ + menu_end_entry(); +}; + +/* help option */ + +help_start: T_HELP T_EOL +{ + printd(DEBUG_PARSE, "%s:%d:help\n", zconf_curname(), zconf_lineno()); + zconf_starthelp(); +}; + +help: help_start T_HELPTEXT +{ + current_entry->sym->help = $2; +}; + +/* depends option */ + +depends_list: + /* empty */ + | depends_list depends + | depends_list T_EOL + | depends_list option_error +; + +depends: T_DEPENDS T_ON expr T_EOL +{ + menu_add_dep($3); + printd(DEBUG_PARSE, "%s:%d:depends on\n", zconf_curname(), zconf_lineno()); +} + | T_DEPENDS expr T_EOL +{ + menu_add_dep($2); + printd(DEBUG_PARSE, "%s:%d:depends\n", zconf_curname(), zconf_lineno()); +} + | T_REQUIRES expr T_EOL +{ + menu_add_dep($2); + printd(DEBUG_PARSE, "%s:%d:requires\n", zconf_curname(), zconf_lineno()); +}; + +/* prompt statement */ + +prompt_stmt_opt: + /* empty */ + | prompt if_expr +{ + menu_add_prompt(P_PROMPT, $1, $2); +}; + +prompt: T_WORD + | T_WORD_QUOTE +; + +end: T_ENDMENU T_EOL { $$ = $1; } + | T_ENDCHOICE T_EOL { $$ = $1; } + | T_ENDIF T_EOL { $$ = $1; } +; + +nl: + T_EOL + | nl T_EOL +; + +if_expr: /* empty */ { $$ = NULL; } + | T_IF expr { $$ = $2; } +; + +expr: symbol { $$ = expr_alloc_symbol($1); } + | symbol T_EQUAL symbol { $$ = expr_alloc_comp(E_EQUAL, $1, $3); } + | symbol T_UNEQUAL symbol { $$ = expr_alloc_comp(E_UNEQUAL, $1, $3); } + | T_OPEN_PAREN expr T_CLOSE_PAREN { $$ = $2; } + | T_NOT expr { $$ = expr_alloc_one(E_NOT, $2); } + | expr T_OR expr { $$ = expr_alloc_two(E_OR, $1, $3); } + | expr T_AND expr { $$ = expr_alloc_two(E_AND, $1, $3); } +; + +symbol: T_WORD { $$ = sym_lookup($1, 0); free($1); } + | T_WORD_QUOTE { $$ = sym_lookup($1, 1); free($1); } +; + +%% + +void conf_parse(const char *name) +{ + struct symbol *sym; + int i; + + zconf_initscan(name); + + sym_init(); + menu_init(); + modules_sym = sym_lookup("MODULES", 0); + rootmenu.prompt = menu_add_prompt(P_MENU, "Busybox Configuration", NULL); + +#if YYDEBUG + if (getenv("ZCONF_DEBUG")) + zconfdebug = 1; +#endif + zconfparse(); + if (zconfnerrs) + exit(1); + menu_finalize(&rootmenu); + for_all_symbols(i, sym) { + sym_check_deps(sym); + } + + sym_change_count = 1; +} + +const char *zconf_tokenname(int token) +{ + switch (token) { + case T_MENU: return "menu"; + case T_ENDMENU: return "endmenu"; + case T_CHOICE: return "choice"; + case T_ENDCHOICE: return "endchoice"; + case T_IF: return "if"; + case T_ENDIF: return "endif"; + case T_DEPENDS: return "depends"; + } + return "<token>"; +} + +static bool zconf_endtoken(struct kconf_id *id, int starttoken, int endtoken) +{ + if (id->token != endtoken) { + zconf_error("unexpected '%s' within %s block", + kconf_id_strings + id->name, zconf_tokenname(starttoken)); + zconfnerrs++; + return false; + } + if (current_menu->file != current_file) { + zconf_error("'%s' in different file than '%s'", + kconf_id_strings + id->name, zconf_tokenname(starttoken)); + fprintf(stderr, "%s:%d: location of the '%s'\n", + current_menu->file->name, current_menu->lineno, + zconf_tokenname(starttoken)); + zconfnerrs++; + return false; + } + return true; +} + +static void zconfprint(const char *err, ...) +{ + va_list ap; + + fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void zconf_error(const char *err, ...) +{ + va_list ap; + + zconfnerrs++; + fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void zconferror(const char *err) +{ +#if YYDEBUG + fprintf(stderr, "%s:%d: %s\n", zconf_curname(), zconf_lineno() + 1, err); +#endif +} + +void print_quoted_string(FILE *out, const char *str) +{ + const char *p; + int len; + + putc('"', out); + while ((p = strchr(str, '"'))) { + len = p - str; + if (len) + fprintf(out, "%.*s", len, str); + fputs("\\\"", out); + str = p + 1; + } + fputs(str, out); + putc('"', out); +} + +void print_symbol(FILE *out, struct menu *menu) +{ + struct symbol *sym = menu->sym; + struct property *prop; + + if (sym_is_choice(sym)) + fprintf(out, "choice\n"); + else + fprintf(out, "config %s\n", sym->name); + switch (sym->type) { + case S_BOOLEAN: + fputs(" boolean\n", out); + break; + case S_TRISTATE: + fputs(" tristate\n", out); + break; + case S_STRING: + fputs(" string\n", out); + break; + case S_INT: + fputs(" integer\n", out); + break; + case S_HEX: + fputs(" hex\n", out); + break; + default: + fputs(" ???\n", out); + break; + } + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->menu != menu) + continue; + switch (prop->type) { + case P_PROMPT: + fputs(" prompt ", out); + print_quoted_string(out, prop->text); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_DEFAULT: + fputs( " default ", out); + expr_fprint(prop->expr, out); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_CHOICE: + fputs(" #choice value\n", out); + break; + default: + fprintf(out, " unknown prop %d!\n", prop->type); + break; + } + } + if (sym->help) { + int len = strlen(sym->help); + while (sym->help[--len] == '\n') + sym->help[len] = 0; + fprintf(out, " help\n%s\n", sym->help); + } + fputc('\n', out); +} + +void zconfdump(FILE *out) +{ + struct property *prop; + struct symbol *sym; + struct menu *menu; + + menu = rootmenu.list; + while (menu) { + if ((sym = menu->sym)) + print_symbol(out, menu); + else if ((prop = menu->prompt)) { + switch (prop->type) { + case P_COMMENT: + fputs("\ncomment ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + case P_MENU: + fputs("\nmenu ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + default: + ; + } + if (!expr_is_yes(prop->visible.expr)) { + fputs(" depends ", out); + expr_fprint(prop->visible.expr, out); + fputc('\n', out); + } + fputs("\n", out); + } + + if (menu->list) + menu = menu->list; + else if (menu->next) + menu = menu->next; + else while ((menu = menu->parent)) { + if (menu->prompt && menu->prompt->type == P_MENU) + fputs("\nendmenu\n", out); + if (menu->next) { + menu = menu->next; + break; + } + } + } +} + +#include "lex.zconf.c" +#include "util.c" +#include "confdata.c" +#include "expr.c" +#include "symbol.c" +#include "menu.c" diff --git a/scripts/mkconfigs b/scripts/mkconfigs new file mode 100755 index 000000000..fda9de72f --- /dev/null +++ b/scripts/mkconfigs @@ -0,0 +1,51 @@ +#!/bin/sh +# +# Copyright (C) 2002 Khalid Aziz <khalid_aziz at hp.com> +# Copyright (C) 2002 Randy Dunlap <rddunlap at osdl.org> +# Copyright (C) 2002 Al Stone <ahs3 at fc.hp.com> +# Copyright (C) 2002 Hewlett-Packard Company +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# Busybox version by Matteo Croce <3297627799 at wind.it> +# +# Rules to generate bbconfig.h from .config: +# - Retain lines that begin with "CONFIG_" +# - Retain lines that begin with "# CONFIG_" +# - lines that use double-quotes must \\-escape-quote them + +if [ $# -lt 1 ] +then + config=.config +else config=$1 +fi + +echo "#ifndef _BBCONFIGOPTS_H" +echo "#define _BBCONFIGOPTS_H" +echo \ +"/* + * busybox configuration settings. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * This file is generated automatically by scripts/config/mkconfigs. + * Do not edit. + * + */" + +echo "static const char * const bbconfig_config =" +echo "`sed 's/\"/\\\\\"/g' $config | grep "^#\? \?CONFIG_" | awk '{print "\\"" $0 "\\\\n\\"";}'`" +echo ";" +echo "#endif /* _BBCONFIGOPTS_H */" diff --git a/scripts/mkmakefile b/scripts/mkmakefile new file mode 100755 index 000000000..7f9d544f9 --- /dev/null +++ b/scripts/mkmakefile @@ -0,0 +1,36 @@ +#!/bin/sh +# Generates a small Makefile used in the root of the output +# directory, to allow make to be started from there. +# The Makefile also allow for more convinient build of external modules + +# Usage +# $1 - Kernel src directory +# $2 - Output directory +# $3 - version +# $4 - patchlevel + + +test ! -r $2/Makefile -o -O $2/Makefile || exit 0 +echo " GEN $2/Makefile" + +cat << EOF > $2/Makefile +# Automatically generated by $0: don't edit + +VERSION = $3 +PATCHLEVEL = $4 + +KERNELSRC := $1 +KERNELOUTPUT := $2 + +MAKEFLAGS += --no-print-directory + +.PHONY: all \$(MAKECMDGOALS) + +all: + \$(MAKE) -C \$(KERNELSRC) O=\$(KERNELOUTPUT) + +Makefile:; + +\$(filter-out all Makefile,\$(MAKECMDGOALS)) %/: + \$(MAKE) -C \$(KERNELSRC) O=\$(KERNELOUTPUT) \$@ +EOF diff --git a/scripts/objsizes b/scripts/objsizes new file mode 100755 index 000000000..4f6576d9a --- /dev/null +++ b/scripts/objsizes @@ -0,0 +1,7 @@ +#!/bin/sh + +printf "%9s %11s %9s %9s %s\n" "text+data" text+rodata rwdata bss filename +find -name '*.o' | sed 's:^\./::' | xargs size | grep '^ *[0-9]' \ +| while read text data bss dec hex filename; do + printf "%9d %11d %9d %9d %s\n" $((text+data)) $text $data $bss "$filename" +done | sort -r diff --git a/scripts/showasm b/scripts/showasm new file mode 100755 index 000000000..046442653 --- /dev/null +++ b/scripts/showasm @@ -0,0 +1,21 @@ +#!/bin/sh + +# Copyright 2006 Rob Landley <rob@landley.net> +# Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + +# Dumb little utility function to print out the assembly dump of a single +# function, or list the functions so dumpable in an executable. You'd think +# there would be a way to get objdump to do this, but I can't find it. + +[ $# -lt 1 ] || [ $# -gt 2 ] && { echo "usage: showasm file function"; exit 1; } + +[ ! -f $1 ] && { echo "File $1 not found"; exit 1; } + +if [ $# -eq 1 ] +then + objdump -d $1 | sed -n -e 's/^[0-9a-fA-F]* <\(.*\)>:$/\1/p' + exit 0 +fi + +objdump -d $1 | sed -n -e '/./{H;$!d}' -e "x;/^.[0-9a-fA-F]* <$2>:/p" + diff --git a/scripts/trylink b/scripts/trylink new file mode 100755 index 000000000..dfe282db5 --- /dev/null +++ b/scripts/trylink @@ -0,0 +1,18 @@ +#!/bin/sh + +debug=false + +function try { + added="$1" + shift + $debug && echo "Trying: $* $added" + "$@" $added >/dev/null 2>&1 \ + && exit 0 +} + +try "" "$@" +try "-lm" "$@" +try "-lcrypt" "$@" +try "-Wl,--start-group -lcrypt -lm -Wl,--end-group" "$@" +# It failed. Rerun & let people see the error messages +"$@" $added diff --git a/shell/Config.in b/shell/Config.in new file mode 100644 index 000000000..9ac233155 --- /dev/null +++ b/shell/Config.in @@ -0,0 +1,295 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Shells" + +choice + prompt "Choose your default shell" + default FEATURE_SH_IS_NONE + help + Choose a shell. The ash shell is the most bash compatible + and full featured one. + +config FEATURE_SH_IS_ASH + select ASH + bool "ash" + +config FEATURE_SH_IS_HUSH + select HUSH + bool "hush" + +config FEATURE_SH_IS_LASH + select LASH + bool "lash" + +config FEATURE_SH_IS_MSH + select MSH + bool "msh" + +config FEATURE_SH_IS_NONE + bool "none" + +endchoice + +config ASH + bool "ash" + default n + select TEST + help + Tha 'ash' shell adds about 60k in the default configuration and is + the most complete and most pedantically correct shell included with + busybox. This shell is actually a derivative of the Debian 'dash' + shell (by Herbert Xu), which was created by porting the 'ash' shell + (written by Kenneth Almquist) from NetBSD. + +comment "Ash Shell Options" + depends on ASH + +config ASH_JOB_CONTROL + bool "Job control" + default y + depends on ASH + help + Enable job control in the ash shell. + +config ASH_READ_NCHARS + bool "'read -n N' and 'read -s' support" + default n + depends on ASH + help + 'read -n N' will return a value after N characters have been read. + 'read -s' will read without echoing the user's input. + +config ASH_READ_TIMEOUT + bool "'read -t S' support." + default n + depends on ASH + help + 'read -t S' will return a value after S seconds have passed. + This implementation will allow fractional seconds, expressed + as a decimal fraction, e.g. 'read -t 2.5 foo'. + +config ASH_ALIAS + bool "alias support" + default y + depends on ASH + help + Enable alias support in the ash shell. + +config ASH_MATH_SUPPORT + bool "Posix math support" + default y + depends on ASH + help + Enable math support in the ash shell. + +config ASH_MATH_SUPPORT_64 + bool "Extend Posix math support to 64 bit" + default n + depends on ASH_MATH_SUPPORT + help + Enable 64-bit math support in the ash shell. This will make + the shell slightly larger, but will allow computation with very + large numbers. + +config ASH_GETOPTS + bool "Builtin getopt to parse positional parameters" + default n + depends on ASH + help + Enable getopts builtin in the ash shell. + +config ASH_BUILTIN_ECHO + bool "Builtin version of 'echo'" + default y + select ECHO + depends on ASH + help + Enable support for echo, built in to ash. + +config ASH_BUILTIN_TEST + bool "Builtin version of 'test'" + default y + select TEST + depends on ASH + help + Enable support for test, built in to ash. + +config ASH_CMDCMD + bool "'command' command to override shell builtins" + default n + depends on ASH + help + Enable support for the ash 'command' builtin, which allows + you to run the specified command with the specified arguments, + even when there is an ash builtin command with the same name. + +config ASH_MAIL + bool "Check for new mail on interactive shells" + default y + depends on ASH + help + Enable "check for new mail" in the ash shell. + +config ASH_OPTIMIZE_FOR_SIZE + bool "Optimize for size instead of speed" + default y + depends on ASH + help + Compile ash for reduced size at the price of speed. + +config ASH_RANDOM_SUPPORT + bool "Pseudorandom generator and variable $RANDOM" + default n + depends on ASH + help + Enable pseudorandom generator and dynamic variable "$RANDOM". + Each read of "$RANDOM" will generate a new pseudorandom value. + You can reset the generator by using a specified start value. + After "unset RANDOM" then generator will switch off and this + variable will no longer have special treatment. + +config ASH_EXPAND_PRMT + bool "Expand prompt string" + default n + depends on ASH + help + "PS#" may be contain volatile content, such as backquote commands. + This option recreates the prompt string from the environment + variable each time it is displayed. + +config HUSH + bool "hush" + default n + select TRUE + select FALSE + select TEST + help + hush is a very small shell (just 18k) and it has fairly complete + Bourne shell grammar. It even handles all the normal flow control + options such as if/then/elif/else/fi, for/in/do/done, while loops, + etc. + + It does not handle case/esac, select, function, here documents ( << + word ), arithmetic expansion, aliases, brace expansion, tilde + expansion, &> and >& redirection of stdout+stderr, etc. + + +config LASH + bool "lash" + default n + select TRUE + select FALSE + select TEST + help + lash is the very smallest shell (adds just 10k) and it is quite + usable as a command prompt, but it is not suitable for any but the + most trivial scripting (such as an initrd that calls insmod a few + times) since it does not understand any Bourne shell grammar. It + does handle pipes, redirects, and job control though. Adding in + command editing makes it a very nice lightweight command prompt. + + +config MSH + bool "msh" + default n + select TRUE + select FALSE + select TEST + help + The minix shell (adds just 30k) is quite complete and handles things + like for/do/done, case/esac and all the things you expect a Bourne + shell to do. It is not always pedantically correct about Bourne + shell grammar (try running the shell testscript "tests/sh.testcases" + on it and compare vs bash) but for most things it works quite well. + It also uses only vfork, so it can be used on uClinux systems. + +comment "Bourne Shell Options" + depends on MSH || LASH || HUSH || ASH + +config FEATURE_SH_EXTRA_QUIET + bool "Hide message on interactive shell startup" + default n + depends on MSH || LASH || HUSH || ASH + help + Remove the busybox introduction when starting a shell. + +config FEATURE_SH_STANDALONE_SHELL + bool "Standalone shell" + default n + depends on MSH || LASH || HUSH || ASH + help + This option causes the selected busybox shell to use busybox applets + in preference to executables in the PATH whenever possible. For + example, entering the command 'ifconfig' into the shell would cause + busybox to use the ifconfig busybox applet. Specifying the fully + qualified executable name, such as '/sbin/ifconfig' will still + execute the /sbin/ifconfig executable on the filesystem. This option + is generally used when creating a statically linked version of busybox + for use as a rescue shell, in the event that you screw up your system. + + Note that this will *also* cause applets to take precedence + over shell builtins of the same name. So turning this on will + eliminate any performance gained by turning on the builtin "echo" + and "test" commands in ash. + + Note that when using this option, the shell will attempt to directly + run '/bin/busybox'. If you do not have the busybox binary sitting in + that exact location with that exact name, this option will not work at + all. + +config FEATURE_COMMAND_EDITING + bool "Command line editing" + default n + depends on MSH || LASH || HUSH || ASH + help + Enable command editing in shell. + +config FEATURE_COMMAND_EDITING_VI + bool "vi-style line editing commands" + default n + depends on FEATURE_COMMAND_EDITING + help + Enable vi-style line editing in the shell. This mode can be + turned on and off with "set -o vi" and "set +o vi". + +config FEATURE_COMMAND_HISTORY + int "History size" + range 0 99999 + default 15 + depends on FEATURE_COMMAND_EDITING + help + Specify command history size in shell. + +config FEATURE_COMMAND_SAVEHISTORY + bool "History saving" + default n + depends on ASH && FEATURE_COMMAND_EDITING + help + Enable history saving in ash shell. + +config FEATURE_COMMAND_TAB_COMPLETION + bool "Tab completion" + default n + depends on FEATURE_COMMAND_EDITING + help + Enable tab completion in shell. + +config FEATURE_COMMAND_USERNAME_COMPLETION + bool "Username completion" + default n + depends on FEATURE_COMMAND_TAB_COMPLETION + help + Enable username completion in shell. + +config FEATURE_SH_FANCY_PROMPT + bool "Fancy shell prompts" + default n + depends on FEATURE_COMMAND_EDITING + help + Setting this option allows for prompts to use things like \w and + \$ and also using escape codes. + +endmenu diff --git a/shell/Kbuild b/shell/Kbuild new file mode 100644 index 000000000..eb0199ee2 --- /dev/null +++ b/shell/Kbuild @@ -0,0 +1,12 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org> +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_ASH) += ash.o +lib-$(CONFIG_HUSH) += hush.o +lib-$(CONFIG_LASH) += lash.o +lib-$(CONFIG_MSH) += msh.o +lib-$(CONFIG_FEATURE_COMMAND_EDITING) += cmdedit.o diff --git a/shell/ash.c b/shell/ash.c new file mode 100644 index 000000000..3a9998fc0 --- /dev/null +++ b/shell/ash.c @@ -0,0 +1,13701 @@ +/* vi: set sw=4 ts=4: */ +/* + * ash shell port for busybox + * + * Copyright (c) 1989, 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Copyright (c) 1997-2005 Herbert Xu <herbert@gondor.apana.org.au> + * was re-ported from NetBSD and debianized. + * + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Original BSD copyright notice is retained at the end of this file. + */ + +/* + * rewrite arith.y to micro stack based cryptic algorithm by + * Copyright (c) 2001 Aaron Lehmann <aaronl@vitelus.com> + * + * Modified by Paul Mundt <lethal@linux-sh.org> (c) 2004 to support + * dynamic variables. + * + * Modified by Vladimir Oleynik <dzo@simtreas.ru> (c) 2001-2005 to be + * used in busybox and size optimizations, + * rewrote arith (see notes to this), added locale support, + * rewrote dynamic variables. + * + */ + + +/* + * The follow should be set to reflect the type of system you have: + * JOBS -> 1 if you have Berkeley job control, 0 otherwise. + * define SYSV if you are running under System V. + * define DEBUG=1 to compile in debugging ('set -o debug' to turn on) + * define DEBUG=2 to compile in and turn on debugging. + * + * When debugging is on, debugging info will be written to ./trace and + * a quit signal will generate a core dump. + */ +#define DEBUG 0 + + +#define IFS_BROKEN + +#define PROFILE 0 + +#include "busybox.h" + +#if DEBUG +#define _GNU_SOURCE +#endif + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <stdarg.h> +#include <stddef.h> +#include <assert.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <paths.h> +#include <setjmp.h> +#include <signal.h> +/*#include <stdint.h>*/ +#include <time.h> +#include <fnmatch.h> + +#ifdef CONFIG_ASH_JOB_CONTROL +#define JOBS 1 +#else +#undef JOBS +#endif + +#if JOBS || defined(CONFIG_ASH_READ_NCHARS) +#include <termios.h> +#endif + +#include "cmdedit.h" + +#ifdef __GLIBC__ +/* glibc sucks */ +static int *dash_errno; +#undef errno +#define errno (*dash_errno) +#endif + +#if defined(__uClinux__) +#error "Do not even bother, ash will not run on uClinux" +#endif + +#if DEBUG +#define _DIAGASSERT(assert_expr) assert(assert_expr) +#else +#define _DIAGASSERT(assert_expr) +#endif + + +#ifdef CONFIG_ASH_ALIAS +/* alias.h */ + +#define ALIASINUSE 1 +#define ALIASDEAD 2 + +struct alias { + struct alias *next; + char *name; + char *val; + int flag; +}; + +static struct alias *lookupalias(const char *, int); +static int aliascmd(int, char **); +static int unaliascmd(int, char **); +static void rmaliases(void); +static int unalias(const char *); +static void printalias(const struct alias *); +#endif + +/* cd.h */ + + +static void setpwd(const char *, int); + +/* error.h */ + + +/* + * Types of operations (passed to the errmsg routine). + */ + + +static const char not_found_msg[] = "%s: not found"; + + +#define E_OPEN "No such file" /* opening a file */ +#define E_CREAT "Directory nonexistent" /* creating a file */ +#define E_EXEC not_found_msg+4 /* executing a program */ + +/* + * We enclose jmp_buf in a structure so that we can declare pointers to + * jump locations. The global variable handler contains the location to + * jump to when an exception occurs, and the global variable exception + * contains a code identifying the exception. To implement nested + * exception handlers, the user should save the value of handler on entry + * to an inner scope, set handler to point to a jmploc structure for the + * inner scope, and restore handler on exit from the scope. + */ + +struct jmploc { + jmp_buf loc; +}; + +static struct jmploc *handler; +static int exception; +static volatile int suppressint; +static volatile sig_atomic_t intpending; + +/* exceptions */ +#define EXINT 0 /* SIGINT received */ +#define EXERROR 1 /* a generic error */ +#define EXSHELLPROC 2 /* execute a shell procedure */ +#define EXEXEC 3 /* command execution failed */ +#define EXEXIT 4 /* exit the shell */ +#define EXSIG 5 /* trapped signal in wait(1) */ + + +/* do we generate EXSIG events */ +static int exsig; +/* last pending signal */ +static volatile sig_atomic_t pendingsigs; + +/* + * These macros allow the user to suspend the handling of interrupt signals + * over a period of time. This is similar to SIGHOLD to or sigblock, but + * much more efficient and portable. (But hacking the kernel is so much + * more fun than worrying about efficiency and portability. :-)) + */ + +#define xbarrier() ({ __asm__ __volatile__ ("": : :"memory"); }) +#define INTOFF \ + ({ \ + suppressint++; \ + xbarrier(); \ + 0; \ + }) +#define SAVEINT(v) ((v) = suppressint) +#define RESTOREINT(v) \ + ({ \ + xbarrier(); \ + if ((suppressint = (v)) == 0 && intpending) onint(); \ + 0; \ + }) +#define EXSIGON() \ + ({ \ + exsig++; \ + xbarrier(); \ + if (pendingsigs) \ + exraise(EXSIG); \ + 0; \ + }) +/* EXSIG is turned off by evalbltin(). */ + + +static void exraise(int) ATTRIBUTE_NORETURN; +static void onint(void) ATTRIBUTE_NORETURN; + +static void sh_error(const char *, ...) ATTRIBUTE_NORETURN; +static void exerror(int, const char *, ...) ATTRIBUTE_NORETURN; + +static void sh_warnx(const char *, ...); + +#ifdef CONFIG_ASH_OPTIMIZE_FOR_SIZE +static void +inton(void) { + if (--suppressint == 0 && intpending) { + onint(); + } +} +#define INTON inton() +static void forceinton(void) +{ + suppressint = 0; + if (intpending) + onint(); +} +#define FORCEINTON forceinton() +#else +#define INTON \ + ({ \ + xbarrier(); \ + if (--suppressint == 0 && intpending) onint(); \ + 0; \ + }) +#define FORCEINTON \ + ({ \ + xbarrier(); \ + suppressint = 0; \ + if (intpending) onint(); \ + 0; \ + }) +#endif /* CONFIG_ASH_OPTIMIZE_FOR_SIZE */ + +/* expand.h */ + +struct strlist { + struct strlist *next; + char *text; +}; + + +struct arglist { + struct strlist *list; + struct strlist **lastp; +}; + +/* + * expandarg() flags + */ +#define EXP_FULL 0x1 /* perform word splitting & file globbing */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ +#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ +#define EXP_WORD 0x80 /* expand word in parameter expansion */ +#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ + + +union node; +static void expandarg(union node *, struct arglist *, int); +#define rmescapes(p) _rmescapes((p), 0) +static char *_rmescapes(char *, int); +static int casematch(union node *, char *); + +#ifdef CONFIG_ASH_MATH_SUPPORT +static void expari(int); +#endif + +/* eval.h */ + +static char *commandname; /* currently executing command */ +static struct strlist *cmdenviron; /* environment for builtin command */ +static int exitstatus; /* exit status of last command */ +static int back_exitstatus; /* exit status of backquoted command */ + + +struct backcmd { /* result of evalbackcmd */ + int fd; /* file descriptor to read from */ + char *buf; /* buffer */ + int nleft; /* number of chars in buffer */ + struct job *jp; /* job structure for command */ +}; + +/* + * This file was generated by the mknodes program. + */ + +#define NCMD 0 +#define NPIPE 1 +#define NREDIR 2 +#define NBACKGND 3 +#define NSUBSHELL 4 +#define NAND 5 +#define NOR 6 +#define NSEMI 7 +#define NIF 8 +#define NWHILE 9 +#define NUNTIL 10 +#define NFOR 11 +#define NCASE 12 +#define NCLIST 13 +#define NDEFUN 14 +#define NARG 15 +#define NTO 16 +#define NCLOBBER 17 +#define NFROM 18 +#define NFROMTO 19 +#define NAPPEND 20 +#define NTOFD 21 +#define NFROMFD 22 +#define NHERE 23 +#define NXHERE 24 +#define NNOT 25 + + + +struct ncmd { + int type; + union node *assign; + union node *args; + union node *redirect; +}; + + +struct npipe { + int type; + int backgnd; + struct nodelist *cmdlist; +}; + + +struct nredir { + int type; + union node *n; + union node *redirect; +}; + + +struct nbinary { + int type; + union node *ch1; + union node *ch2; +}; + + +struct nif { + int type; + union node *test; + union node *ifpart; + union node *elsepart; +}; + + +struct nfor { + int type; + union node *args; + union node *body; + char *var; +}; + + +struct ncase { + int type; + union node *expr; + union node *cases; +}; + + +struct nclist { + int type; + union node *next; + union node *pattern; + union node *body; +}; + + +struct narg { + int type; + union node *next; + char *text; + struct nodelist *backquote; +}; + + +struct nfile { + int type; + union node *next; + int fd; + union node *fname; + char *expfname; +}; + + +struct ndup { + int type; + union node *next; + int fd; + int dupfd; + union node *vname; +}; + + +struct nhere { + int type; + union node *next; + int fd; + union node *doc; +}; + + +struct nnot { + int type; + union node *com; +}; + + +union node { + int type; + struct ncmd ncmd; + struct npipe npipe; + struct nredir nredir; + struct nbinary nbinary; + struct nif nif; + struct nfor nfor; + struct ncase ncase; + struct nclist nclist; + struct narg narg; + struct nfile nfile; + struct ndup ndup; + struct nhere nhere; + struct nnot nnot; +}; + + +struct nodelist { + struct nodelist *next; + union node *n; +}; + + +struct funcnode { + int count; + union node n; +}; + + +static void freefunc(struct funcnode *); +/* parser.h */ + +/* control characters in argument strings */ +#define CTL_FIRST '\201' /* first 'special' character */ +#define CTLESC '\201' /* escape next character */ +#define CTLVAR '\202' /* variable defn */ +#define CTLENDVAR '\203' +#define CTLBACKQ '\204' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\205' */ +#define CTLARI '\206' /* arithmetic expression */ +#define CTLENDARI '\207' +#define CTLQUOTEMARK '\210' +#define CTL_LAST '\210' /* last 'special' character */ + +/* variable substitution byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon--treat the empty string as unset */ +#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ + +/* values of VSTYPE field */ +#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ +#define VSMINUS 0x2 /* ${var-text} */ +#define VSPLUS 0x3 /* ${var+text} */ +#define VSQUESTION 0x4 /* ${var?message} */ +#define VSASSIGN 0x5 /* ${var=text} */ +#define VSTRIMRIGHT 0x6 /* ${var%pattern} */ +#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ +#define VSTRIMLEFT 0x8 /* ${var#pattern} */ +#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ +#define VSLENGTH 0xa /* ${#var} */ + +/* values of checkkwd variable */ +#define CHKALIAS 0x1 +#define CHKKWD 0x2 +#define CHKNL 0x4 + +#define IBUFSIZ (BUFSIZ + 1) + +/* + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL, so we use the address of a variable that + * happens to be handy. + */ +static int plinno = 1; /* input line number */ + +/* number of characters left in input buffer */ +static int parsenleft; /* copy of parsefile->nleft */ +static int parselleft; /* copy of parsefile->lleft */ + +/* next character in input buffer */ +static char *parsenextc; /* copy of parsefile->nextc */ + +struct strpush { + struct strpush *prev; /* preceding string on stack */ + char *prevstring; + int prevnleft; +#ifdef CONFIG_ASH_ALIAS + struct alias *ap; /* if push was associated with an alias */ +#endif + char *string; /* remember the string since it may change */ +}; + +struct parsefile { + struct parsefile *prev; /* preceding file on stack */ + int linno; /* current line */ + int fd; /* file descriptor (or -1 if string) */ + int nleft; /* number of chars left in this line */ + int lleft; /* number of chars left in this buffer */ + char *nextc; /* next char in buffer */ + char *buf; /* input buffer */ + struct strpush *strpush; /* for pushing strings at this level */ + struct strpush basestrpush; /* so pushing one is fast */ +}; + +static struct parsefile basepf; /* top level input file */ +#define basebuf bb_common_bufsiz1 /* buffer for top level input file */ +static struct parsefile *parsefile = &basepf; /* current input file */ + + +static int tokpushback; /* last token pushed back */ +#define NEOF ((union node *)&tokpushback) +static int parsebackquote; /* nonzero if we are inside backquotes */ +static int doprompt; /* if set, prompt the user */ +static int needprompt; /* true if interactive and at start of line */ +static int lasttoken; /* last token read */ +static char *wordtext; /* text of last word returned by readtoken */ +static int checkkwd; +static struct nodelist *backquotelist; +static union node *redirnode; +static struct heredoc *heredoc; +static int quoteflag; /* set if (part of) last token was quoted */ +static int startlinno; /* line # where last token started */ + +static union node *parsecmd(int); +static void fixredir(union node *, const char *, int); +static const char *const *findkwd(const char *); +static char *endofname(const char *); + +/* shell.h */ + +typedef void *pointer; + +static char nullstr[1]; /* zero length string */ +static const char spcstr[] = " "; +static const char snlfmt[] = "%s\n"; +static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' }; +static const char illnum[] = "Illegal number: %s"; +static const char homestr[] = "HOME"; + +#if DEBUG +#define TRACE(param) trace param +#define TRACEV(param) tracev param +#else +#define TRACE(param) +#define TRACEV(param) +#endif + +#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) +#define __builtin_expect(x, expected_value) (x) +#endif + +#define xlikely(x) __builtin_expect((x),1) + + +#define TEOF 0 +#define TNL 1 +#define TREDIR 2 +#define TWORD 3 +#define TSEMI 4 +#define TBACKGND 5 +#define TAND 6 +#define TOR 7 +#define TPIPE 8 +#define TLP 9 +#define TRP 10 +#define TENDCASE 11 +#define TENDBQUOTE 12 +#define TNOT 13 +#define TCASE 14 +#define TDO 15 +#define TDONE 16 +#define TELIF 17 +#define TELSE 18 +#define TESAC 19 +#define TFI 20 +#define TFOR 21 +#define TIF 22 +#define TIN 23 +#define TTHEN 24 +#define TUNTIL 25 +#define TWHILE 26 +#define TBEGIN 27 +#define TEND 28 + +/* first char is indicating which tokens mark the end of a list */ +static const char *const tokname_array[] = { + "\1end of file", + "\0newline", + "\0redirection", + "\0word", + "\0;", + "\0&", + "\0&&", + "\0||", + "\0|", + "\0(", + "\1)", + "\1;;", + "\1`", +#define KWDOFFSET 13 + /* the following are keywords */ + "\0!", + "\0case", + "\1do", + "\1done", + "\1elif", + "\1else", + "\1esac", + "\1fi", + "\0for", + "\0if", + "\0in", + "\1then", + "\0until", + "\0while", + "\0{", + "\1}", +}; + +static const char *tokname(int tok) +{ + static char buf[16]; + + if (tok >= TSEMI) + buf[0] = '"'; + sprintf(buf + (tok >= TSEMI), "%s%c", + tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); + return buf; +} + +/* machdep.h */ + +/* + * Most machines require the value returned from malloc to be aligned + * in some way. The following macro will get this right on many machines. + */ + +#define SHELL_SIZE (sizeof(union {int i; char *cp; double d; }) - 1) +/* + * It appears that grabstackstr() will barf with such alignments + * because stalloc() will return a string allocated in a new stackblock. + */ +#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE) + +/* + * This file was generated by the mksyntax program. + */ + + +/* Syntax classes */ +#define CWORD 0 /* character is nothing special */ +#define CNL 1 /* newline character */ +#define CBACK 2 /* a backslash character */ +#define CSQUOTE 3 /* single quote */ +#define CDQUOTE 4 /* double quote */ +#define CENDQUOTE 5 /* a terminating quote */ +#define CBQUOTE 6 /* backwards single quote */ +#define CVAR 7 /* a dollar sign */ +#define CENDVAR 8 /* a '}' character */ +#define CLP 9 /* a left paren in arithmetic */ +#define CRP 10 /* a right paren in arithmetic */ +#define CENDFILE 11 /* end of file */ +#define CCTL 12 /* like CWORD, except it must be escaped */ +#define CSPCL 13 /* these terminate a word */ +#define CIGN 14 /* character should be ignored */ + +#ifdef CONFIG_ASH_ALIAS +#define SYNBASE 130 +#define PEOF -130 +#define PEOA -129 +#define PEOA_OR_PEOF PEOA +#else +#define SYNBASE 129 +#define PEOF -129 +#define PEOA_OR_PEOF PEOF +#endif + +#define is_digit(c) ((unsigned)((c) - '0') <= 9) +#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) +#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) + +/* C99 say: "char" declaration may be signed or unsigned default */ +#define SC2INT(chr2may_be_negative_int) (int)((signed char)chr2may_be_negative_int) + +/* + * is_special(c) evaluates to 1 for c in "!#$*-0123456789?@"; 0 otherwise + * (assuming ascii char codes, as the original implementation did) + */ +#define is_special(c) \ + ( (((unsigned int)c) - 33 < 32) \ + && ((0xc1ff920dUL >> (((unsigned int)c) - 33)) & 1)) + +#define digit_val(c) ((c) - '0') + +/* + * This file was generated by the mksyntax program. + */ + +#ifdef CONFIG_ASH_OPTIMIZE_FOR_SIZE +#define USE_SIT_FUNCTION +#endif + +/* number syntax index */ +#define BASESYNTAX 0 /* not in quotes */ +#define DQSYNTAX 1 /* in double quotes */ +#define SQSYNTAX 2 /* in single quotes */ +#define ARISYNTAX 3 /* in arithmetic */ + +#ifdef CONFIG_ASH_MATH_SUPPORT +static const char S_I_T[][4] = { +#ifdef CONFIG_ASH_ALIAS + {CSPCL, CIGN, CIGN, CIGN}, /* 0, PEOA */ +#endif + {CSPCL, CWORD, CWORD, CWORD}, /* 1, ' ' */ + {CNL, CNL, CNL, CNL}, /* 2, \n */ + {CWORD, CCTL, CCTL, CWORD}, /* 3, !*-/:=?[]~ */ + {CDQUOTE, CENDQUOTE, CWORD, CWORD}, /* 4, '"' */ + {CVAR, CVAR, CWORD, CVAR}, /* 5, $ */ + {CSQUOTE, CWORD, CENDQUOTE, CWORD}, /* 6, "'" */ + {CSPCL, CWORD, CWORD, CLP}, /* 7, ( */ + {CSPCL, CWORD, CWORD, CRP}, /* 8, ) */ + {CBACK, CBACK, CCTL, CBACK}, /* 9, \ */ + {CBQUOTE, CBQUOTE, CWORD, CBQUOTE}, /* 10, ` */ + {CENDVAR, CENDVAR, CWORD, CENDVAR}, /* 11, } */ +#ifndef USE_SIT_FUNCTION + {CENDFILE, CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ + {CWORD, CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ + {CCTL, CCTL, CCTL, CCTL} /* 14, CTLESC ... */ +#endif +}; +#else +static const char S_I_T[][3] = { +#ifdef CONFIG_ASH_ALIAS + {CSPCL, CIGN, CIGN}, /* 0, PEOA */ +#endif + {CSPCL, CWORD, CWORD}, /* 1, ' ' */ + {CNL, CNL, CNL}, /* 2, \n */ + {CWORD, CCTL, CCTL}, /* 3, !*-/:=?[]~ */ + {CDQUOTE, CENDQUOTE, CWORD}, /* 4, '"' */ + {CVAR, CVAR, CWORD}, /* 5, $ */ + {CSQUOTE, CWORD, CENDQUOTE}, /* 6, "'" */ + {CSPCL, CWORD, CWORD}, /* 7, ( */ + {CSPCL, CWORD, CWORD}, /* 8, ) */ + {CBACK, CBACK, CCTL}, /* 9, \ */ + {CBQUOTE, CBQUOTE, CWORD}, /* 10, ` */ + {CENDVAR, CENDVAR, CWORD}, /* 11, } */ +#ifndef USE_SIT_FUNCTION + {CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ + {CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ + {CCTL, CCTL, CCTL} /* 14, CTLESC ... */ +#endif +}; +#endif /* CONFIG_ASH_MATH_SUPPORT */ + +#ifdef USE_SIT_FUNCTION + +#define U_C(c) ((unsigned char)(c)) + +static int SIT(int c, int syntax) +{ + static const char spec_symbls[] = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~"; +#ifdef CONFIG_ASH_ALIAS + static const char syntax_index_table[] = { + 1, 2, 1, 3, 4, 5, 1, 6, /* "\t\n !\"$&'" */ + 7, 8, 3, 3, 3, 3, 1, 1, /* "()*-/:;<" */ + 3, 1, 3, 3, 9, 3, 10, 1, /* "=>?[\\]`|" */ + 11, 3 /* "}~" */ + }; +#else + static const char syntax_index_table[] = { + 0, 1, 0, 2, 3, 4, 0, 5, /* "\t\n !\"$&'" */ + 6, 7, 2, 2, 2, 2, 0, 0, /* "()*-/:;<" */ + 2, 0, 2, 2, 8, 2, 9, 0, /* "=>?[\\]`|" */ + 10, 2 /* "}~" */ + }; +#endif + const char *s; + int indx; + + if (c == PEOF) /* 2^8+2 */ + return CENDFILE; +#ifdef CONFIG_ASH_ALIAS + if (c == PEOA) /* 2^8+1 */ + indx = 0; + else +#endif + if (U_C(c) >= U_C(CTLESC) && U_C(c) <= U_C(CTLQUOTEMARK)) + return CCTL; + else { + s = strchr(spec_symbls, c); + if (s == 0 || *s == 0) + return CWORD; + indx = syntax_index_table[(s - spec_symbls)]; + } + return S_I_T[indx][syntax]; +} + +#else /* USE_SIT_FUNCTION */ + +#define SIT(c, syntax) S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax] + +#ifdef CONFIG_ASH_ALIAS +#define CSPCL_CIGN_CIGN_CIGN 0 +#define CSPCL_CWORD_CWORD_CWORD 1 +#define CNL_CNL_CNL_CNL 2 +#define CWORD_CCTL_CCTL_CWORD 3 +#define CDQUOTE_CENDQUOTE_CWORD_CWORD 4 +#define CVAR_CVAR_CWORD_CVAR 5 +#define CSQUOTE_CWORD_CENDQUOTE_CWORD 6 +#define CSPCL_CWORD_CWORD_CLP 7 +#define CSPCL_CWORD_CWORD_CRP 8 +#define CBACK_CBACK_CCTL_CBACK 9 +#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 10 +#define CENDVAR_CENDVAR_CWORD_CENDVAR 11 +#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 12 +#define CWORD_CWORD_CWORD_CWORD 13 +#define CCTL_CCTL_CCTL_CCTL 14 +#else +#define CSPCL_CWORD_CWORD_CWORD 0 +#define CNL_CNL_CNL_CNL 1 +#define CWORD_CCTL_CCTL_CWORD 2 +#define CDQUOTE_CENDQUOTE_CWORD_CWORD 3 +#define CVAR_CVAR_CWORD_CVAR 4 +#define CSQUOTE_CWORD_CENDQUOTE_CWORD 5 +#define CSPCL_CWORD_CWORD_CLP 6 +#define CSPCL_CWORD_CWORD_CRP 7 +#define CBACK_CBACK_CCTL_CBACK 8 +#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 9 +#define CENDVAR_CENDVAR_CWORD_CENDVAR 10 +#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 11 +#define CWORD_CWORD_CWORD_CWORD 12 +#define CCTL_CCTL_CCTL_CCTL 13 +#endif + +static const char syntax_index_table[258] = { + /* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */ + /* 0 PEOF */ CENDFILE_CENDFILE_CENDFILE_CENDFILE, +#ifdef CONFIG_ASH_ALIAS + /* 1 PEOA */ CSPCL_CIGN_CIGN_CIGN, +#endif + /* 2 -128 0x80 */ CWORD_CWORD_CWORD_CWORD, + /* 3 -127 CTLESC */ CCTL_CCTL_CCTL_CCTL, + /* 4 -126 CTLVAR */ CCTL_CCTL_CCTL_CCTL, + /* 5 -125 CTLENDVAR */ CCTL_CCTL_CCTL_CCTL, + /* 6 -124 CTLBACKQ */ CCTL_CCTL_CCTL_CCTL, + /* 7 -123 CTLQUOTE */ CCTL_CCTL_CCTL_CCTL, + /* 8 -122 CTLARI */ CCTL_CCTL_CCTL_CCTL, + /* 9 -121 CTLENDARI */ CCTL_CCTL_CCTL_CCTL, + /* 10 -120 CTLQUOTEMARK */ CCTL_CCTL_CCTL_CCTL, + /* 11 -119 */ CWORD_CWORD_CWORD_CWORD, + /* 12 -118 */ CWORD_CWORD_CWORD_CWORD, + /* 13 -117 */ CWORD_CWORD_CWORD_CWORD, + /* 14 -116 */ CWORD_CWORD_CWORD_CWORD, + /* 15 -115 */ CWORD_CWORD_CWORD_CWORD, + /* 16 -114 */ CWORD_CWORD_CWORD_CWORD, + /* 17 -113 */ CWORD_CWORD_CWORD_CWORD, + /* 18 -112 */ CWORD_CWORD_CWORD_CWORD, + /* 19 -111 */ CWORD_CWORD_CWORD_CWORD, + /* 20 -110 */ CWORD_CWORD_CWORD_CWORD, + /* 21 -109 */ CWORD_CWORD_CWORD_CWORD, + /* 22 -108 */ CWORD_CWORD_CWORD_CWORD, + /* 23 -107 */ CWORD_CWORD_CWORD_CWORD, + /* 24 -106 */ CWORD_CWORD_CWORD_CWORD, + /* 25 -105 */ CWORD_CWORD_CWORD_CWORD, + /* 26 -104 */ CWORD_CWORD_CWORD_CWORD, + /* 27 -103 */ CWORD_CWORD_CWORD_CWORD, + /* 28 -102 */ CWORD_CWORD_CWORD_CWORD, + /* 29 -101 */ CWORD_CWORD_CWORD_CWORD, + /* 30 -100 */ CWORD_CWORD_CWORD_CWORD, + /* 31 -99 */ CWORD_CWORD_CWORD_CWORD, + /* 32 -98 */ CWORD_CWORD_CWORD_CWORD, + /* 33 -97 */ CWORD_CWORD_CWORD_CWORD, + /* 34 -96 */ CWORD_CWORD_CWORD_CWORD, + /* 35 -95 */ CWORD_CWORD_CWORD_CWORD, + /* 36 -94 */ CWORD_CWORD_CWORD_CWORD, + /* 37 -93 */ CWORD_CWORD_CWORD_CWORD, + /* 38 -92 */ CWORD_CWORD_CWORD_CWORD, + /* 39 -91 */ CWORD_CWORD_CWORD_CWORD, + /* 40 -90 */ CWORD_CWORD_CWORD_CWORD, + /* 41 -89 */ CWORD_CWORD_CWORD_CWORD, + /* 42 -88 */ CWORD_CWORD_CWORD_CWORD, + /* 43 -87 */ CWORD_CWORD_CWORD_CWORD, + /* 44 -86 */ CWORD_CWORD_CWORD_CWORD, + /* 45 -85 */ CWORD_CWORD_CWORD_CWORD, + /* 46 -84 */ CWORD_CWORD_CWORD_CWORD, + /* 47 -83 */ CWORD_CWORD_CWORD_CWORD, + /* 48 -82 */ CWORD_CWORD_CWORD_CWORD, + /* 49 -81 */ CWORD_CWORD_CWORD_CWORD, + /* 50 -80 */ CWORD_CWORD_CWORD_CWORD, + /* 51 -79 */ CWORD_CWORD_CWORD_CWORD, + /* 52 -78 */ CWORD_CWORD_CWORD_CWORD, + /* 53 -77 */ CWORD_CWORD_CWORD_CWORD, + /* 54 -76 */ CWORD_CWORD_CWORD_CWORD, + /* 55 -75 */ CWORD_CWORD_CWORD_CWORD, + /* 56 -74 */ CWORD_CWORD_CWORD_CWORD, + /* 57 -73 */ CWORD_CWORD_CWORD_CWORD, + /* 58 -72 */ CWORD_CWORD_CWORD_CWORD, + /* 59 -71 */ CWORD_CWORD_CWORD_CWORD, + /* 60 -70 */ CWORD_CWORD_CWORD_CWORD, + /* 61 -69 */ CWORD_CWORD_CWORD_CWORD, + /* 62 -68 */ CWORD_CWORD_CWORD_CWORD, + /* 63 -67 */ CWORD_CWORD_CWORD_CWORD, + /* 64 -66 */ CWORD_CWORD_CWORD_CWORD, + /* 65 -65 */ CWORD_CWORD_CWORD_CWORD, + /* 66 -64 */ CWORD_CWORD_CWORD_CWORD, + /* 67 -63 */ CWORD_CWORD_CWORD_CWORD, + /* 68 -62 */ CWORD_CWORD_CWORD_CWORD, + /* 69 -61 */ CWORD_CWORD_CWORD_CWORD, + /* 70 -60 */ CWORD_CWORD_CWORD_CWORD, + /* 71 -59 */ CWORD_CWORD_CWORD_CWORD, + /* 72 -58 */ CWORD_CWORD_CWORD_CWORD, + /* 73 -57 */ CWORD_CWORD_CWORD_CWORD, + /* 74 -56 */ CWORD_CWORD_CWORD_CWORD, + /* 75 -55 */ CWORD_CWORD_CWORD_CWORD, + /* 76 -54 */ CWORD_CWORD_CWORD_CWORD, + /* 77 -53 */ CWORD_CWORD_CWORD_CWORD, + /* 78 -52 */ CWORD_CWORD_CWORD_CWORD, + /* 79 -51 */ CWORD_CWORD_CWORD_CWORD, + /* 80 -50 */ CWORD_CWORD_CWORD_CWORD, + /* 81 -49 */ CWORD_CWORD_CWORD_CWORD, + /* 82 -48 */ CWORD_CWORD_CWORD_CWORD, + /* 83 -47 */ CWORD_CWORD_CWORD_CWORD, + /* 84 -46 */ CWORD_CWORD_CWORD_CWORD, + /* 85 -45 */ CWORD_CWORD_CWORD_CWORD, + /* 86 -44 */ CWORD_CWORD_CWORD_CWORD, + /* 87 -43 */ CWORD_CWORD_CWORD_CWORD, + /* 88 -42 */ CWORD_CWORD_CWORD_CWORD, + /* 89 -41 */ CWORD_CWORD_CWORD_CWORD, + /* 90 -40 */ CWORD_CWORD_CWORD_CWORD, + /* 91 -39 */ CWORD_CWORD_CWORD_CWORD, + /* 92 -38 */ CWORD_CWORD_CWORD_CWORD, + /* 93 -37 */ CWORD_CWORD_CWORD_CWORD, + /* 94 -36 */ CWORD_CWORD_CWORD_CWORD, + /* 95 -35 */ CWORD_CWORD_CWORD_CWORD, + /* 96 -34 */ CWORD_CWORD_CWORD_CWORD, + /* 97 -33 */ CWORD_CWORD_CWORD_CWORD, + /* 98 -32 */ CWORD_CWORD_CWORD_CWORD, + /* 99 -31 */ CWORD_CWORD_CWORD_CWORD, + /* 100 -30 */ CWORD_CWORD_CWORD_CWORD, + /* 101 -29 */ CWORD_CWORD_CWORD_CWORD, + /* 102 -28 */ CWORD_CWORD_CWORD_CWORD, + /* 103 -27 */ CWORD_CWORD_CWORD_CWORD, + /* 104 -26 */ CWORD_CWORD_CWORD_CWORD, + /* 105 -25 */ CWORD_CWORD_CWORD_CWORD, + /* 106 -24 */ CWORD_CWORD_CWORD_CWORD, + /* 107 -23 */ CWORD_CWORD_CWORD_CWORD, + /* 108 -22 */ CWORD_CWORD_CWORD_CWORD, + /* 109 -21 */ CWORD_CWORD_CWORD_CWORD, + /* 110 -20 */ CWORD_CWORD_CWORD_CWORD, + /* 111 -19 */ CWORD_CWORD_CWORD_CWORD, + /* 112 -18 */ CWORD_CWORD_CWORD_CWORD, + /* 113 -17 */ CWORD_CWORD_CWORD_CWORD, + /* 114 -16 */ CWORD_CWORD_CWORD_CWORD, + /* 115 -15 */ CWORD_CWORD_CWORD_CWORD, + /* 116 -14 */ CWORD_CWORD_CWORD_CWORD, + /* 117 -13 */ CWORD_CWORD_CWORD_CWORD, + /* 118 -12 */ CWORD_CWORD_CWORD_CWORD, + /* 119 -11 */ CWORD_CWORD_CWORD_CWORD, + /* 120 -10 */ CWORD_CWORD_CWORD_CWORD, + /* 121 -9 */ CWORD_CWORD_CWORD_CWORD, + /* 122 -8 */ CWORD_CWORD_CWORD_CWORD, + /* 123 -7 */ CWORD_CWORD_CWORD_CWORD, + /* 124 -6 */ CWORD_CWORD_CWORD_CWORD, + /* 125 -5 */ CWORD_CWORD_CWORD_CWORD, + /* 126 -4 */ CWORD_CWORD_CWORD_CWORD, + /* 127 -3 */ CWORD_CWORD_CWORD_CWORD, + /* 128 -2 */ CWORD_CWORD_CWORD_CWORD, + /* 129 -1 */ CWORD_CWORD_CWORD_CWORD, + /* 130 0 */ CWORD_CWORD_CWORD_CWORD, + /* 131 1 */ CWORD_CWORD_CWORD_CWORD, + /* 132 2 */ CWORD_CWORD_CWORD_CWORD, + /* 133 3 */ CWORD_CWORD_CWORD_CWORD, + /* 134 4 */ CWORD_CWORD_CWORD_CWORD, + /* 135 5 */ CWORD_CWORD_CWORD_CWORD, + /* 136 6 */ CWORD_CWORD_CWORD_CWORD, + /* 137 7 */ CWORD_CWORD_CWORD_CWORD, + /* 138 8 */ CWORD_CWORD_CWORD_CWORD, + /* 139 9 "\t" */ CSPCL_CWORD_CWORD_CWORD, + /* 140 10 "\n" */ CNL_CNL_CNL_CNL, + /* 141 11 */ CWORD_CWORD_CWORD_CWORD, + /* 142 12 */ CWORD_CWORD_CWORD_CWORD, + /* 143 13 */ CWORD_CWORD_CWORD_CWORD, + /* 144 14 */ CWORD_CWORD_CWORD_CWORD, + /* 145 15 */ CWORD_CWORD_CWORD_CWORD, + /* 146 16 */ CWORD_CWORD_CWORD_CWORD, + /* 147 17 */ CWORD_CWORD_CWORD_CWORD, + /* 148 18 */ CWORD_CWORD_CWORD_CWORD, + /* 149 19 */ CWORD_CWORD_CWORD_CWORD, + /* 150 20 */ CWORD_CWORD_CWORD_CWORD, + /* 151 21 */ CWORD_CWORD_CWORD_CWORD, + /* 152 22 */ CWORD_CWORD_CWORD_CWORD, + /* 153 23 */ CWORD_CWORD_CWORD_CWORD, + /* 154 24 */ CWORD_CWORD_CWORD_CWORD, + /* 155 25 */ CWORD_CWORD_CWORD_CWORD, + /* 156 26 */ CWORD_CWORD_CWORD_CWORD, + /* 157 27 */ CWORD_CWORD_CWORD_CWORD, + /* 158 28 */ CWORD_CWORD_CWORD_CWORD, + /* 159 29 */ CWORD_CWORD_CWORD_CWORD, + /* 160 30 */ CWORD_CWORD_CWORD_CWORD, + /* 161 31 */ CWORD_CWORD_CWORD_CWORD, + /* 162 32 " " */ CSPCL_CWORD_CWORD_CWORD, + /* 163 33 "!" */ CWORD_CCTL_CCTL_CWORD, + /* 164 34 """ */ CDQUOTE_CENDQUOTE_CWORD_CWORD, + /* 165 35 "#" */ CWORD_CWORD_CWORD_CWORD, + /* 166 36 "$" */ CVAR_CVAR_CWORD_CVAR, + /* 167 37 "%" */ CWORD_CWORD_CWORD_CWORD, + /* 168 38 "&" */ CSPCL_CWORD_CWORD_CWORD, + /* 169 39 "'" */ CSQUOTE_CWORD_CENDQUOTE_CWORD, + /* 170 40 "(" */ CSPCL_CWORD_CWORD_CLP, + /* 171 41 ")" */ CSPCL_CWORD_CWORD_CRP, + /* 172 42 "*" */ CWORD_CCTL_CCTL_CWORD, + /* 173 43 "+" */ CWORD_CWORD_CWORD_CWORD, + /* 174 44 "," */ CWORD_CWORD_CWORD_CWORD, + /* 175 45 "-" */ CWORD_CCTL_CCTL_CWORD, + /* 176 46 "." */ CWORD_CWORD_CWORD_CWORD, + /* 177 47 "/" */ CWORD_CCTL_CCTL_CWORD, + /* 178 48 "0" */ CWORD_CWORD_CWORD_CWORD, + /* 179 49 "1" */ CWORD_CWORD_CWORD_CWORD, + /* 180 50 "2" */ CWORD_CWORD_CWORD_CWORD, + /* 181 51 "3" */ CWORD_CWORD_CWORD_CWORD, + /* 182 52 "4" */ CWORD_CWORD_CWORD_CWORD, + /* 183 53 "5" */ CWORD_CWORD_CWORD_CWORD, + /* 184 54 "6" */ CWORD_CWORD_CWORD_CWORD, + /* 185 55 "7" */ CWORD_CWORD_CWORD_CWORD, + /* 186 56 "8" */ CWORD_CWORD_CWORD_CWORD, + /* 187 57 "9" */ CWORD_CWORD_CWORD_CWORD, + /* 188 58 ":" */ CWORD_CCTL_CCTL_CWORD, + /* 189 59 ";" */ CSPCL_CWORD_CWORD_CWORD, + /* 190 60 "<" */ CSPCL_CWORD_CWORD_CWORD, + /* 191 61 "=" */ CWORD_CCTL_CCTL_CWORD, + /* 192 62 ">" */ CSPCL_CWORD_CWORD_CWORD, + /* 193 63 "?" */ CWORD_CCTL_CCTL_CWORD, + /* 194 64 "@" */ CWORD_CWORD_CWORD_CWORD, + /* 195 65 "A" */ CWORD_CWORD_CWORD_CWORD, + /* 196 66 "B" */ CWORD_CWORD_CWORD_CWORD, + /* 197 67 "C" */ CWORD_CWORD_CWORD_CWORD, + /* 198 68 "D" */ CWORD_CWORD_CWORD_CWORD, + /* 199 69 "E" */ CWORD_CWORD_CWORD_CWORD, + /* 200 70 "F" */ CWORD_CWORD_CWORD_CWORD, + /* 201 71 "G" */ CWORD_CWORD_CWORD_CWORD, + /* 202 72 "H" */ CWORD_CWORD_CWORD_CWORD, + /* 203 73 "I" */ CWORD_CWORD_CWORD_CWORD, + /* 204 74 "J" */ CWORD_CWORD_CWORD_CWORD, + /* 205 75 "K" */ CWORD_CWORD_CWORD_CWORD, + /* 206 76 "L" */ CWORD_CWORD_CWORD_CWORD, + /* 207 77 "M" */ CWORD_CWORD_CWORD_CWORD, + /* 208 78 "N" */ CWORD_CWORD_CWORD_CWORD, + /* 209 79 "O" */ CWORD_CWORD_CWORD_CWORD, + /* 210 80 "P" */ CWORD_CWORD_CWORD_CWORD, + /* 211 81 "Q" */ CWORD_CWORD_CWORD_CWORD, + /* 212 82 "R" */ CWORD_CWORD_CWORD_CWORD, + /* 213 83 "S" */ CWORD_CWORD_CWORD_CWORD, + /* 214 84 "T" */ CWORD_CWORD_CWORD_CWORD, + /* 215 85 "U" */ CWORD_CWORD_CWORD_CWORD, + /* 216 86 "V" */ CWORD_CWORD_CWORD_CWORD, + /* 217 87 "W" */ CWORD_CWORD_CWORD_CWORD, + /* 218 88 "X" */ CWORD_CWORD_CWORD_CWORD, + /* 219 89 "Y" */ CWORD_CWORD_CWORD_CWORD, + /* 220 90 "Z" */ CWORD_CWORD_CWORD_CWORD, + /* 221 91 "[" */ CWORD_CCTL_CCTL_CWORD, + /* 222 92 "\" */ CBACK_CBACK_CCTL_CBACK, + /* 223 93 "]" */ CWORD_CCTL_CCTL_CWORD, + /* 224 94 "^" */ CWORD_CWORD_CWORD_CWORD, + /* 225 95 "_" */ CWORD_CWORD_CWORD_CWORD, + /* 226 96 "`" */ CBQUOTE_CBQUOTE_CWORD_CBQUOTE, + /* 227 97 "a" */ CWORD_CWORD_CWORD_CWORD, + /* 228 98 "b" */ CWORD_CWORD_CWORD_CWORD, + /* 229 99 "c" */ CWORD_CWORD_CWORD_CWORD, + /* 230 100 "d" */ CWORD_CWORD_CWORD_CWORD, + /* 231 101 "e" */ CWORD_CWORD_CWORD_CWORD, + /* 232 102 "f" */ CWORD_CWORD_CWORD_CWORD, + /* 233 103 "g" */ CWORD_CWORD_CWORD_CWORD, + /* 234 104 "h" */ CWORD_CWORD_CWORD_CWORD, + /* 235 105 "i" */ CWORD_CWORD_CWORD_CWORD, + /* 236 106 "j" */ CWORD_CWORD_CWORD_CWORD, + /* 237 107 "k" */ CWORD_CWORD_CWORD_CWORD, + /* 238 108 "l" */ CWORD_CWORD_CWORD_CWORD, + /* 239 109 "m" */ CWORD_CWORD_CWORD_CWORD, + /* 240 110 "n" */ CWORD_CWORD_CWORD_CWORD, + /* 241 111 "o" */ CWORD_CWORD_CWORD_CWORD, + /* 242 112 "p" */ CWORD_CWORD_CWORD_CWORD, + /* 243 113 "q" */ CWORD_CWORD_CWORD_CWORD, + /* 244 114 "r" */ CWORD_CWORD_CWORD_CWORD, + /* 245 115 "s" */ CWORD_CWORD_CWORD_CWORD, + /* 246 116 "t" */ CWORD_CWORD_CWORD_CWORD, + /* 247 117 "u" */ CWORD_CWORD_CWORD_CWORD, + /* 248 118 "v" */ CWORD_CWORD_CWORD_CWORD, + /* 249 119 "w" */ CWORD_CWORD_CWORD_CWORD, + /* 250 120 "x" */ CWORD_CWORD_CWORD_CWORD, + /* 251 121 "y" */ CWORD_CWORD_CWORD_CWORD, + /* 252 122 "z" */ CWORD_CWORD_CWORD_CWORD, + /* 253 123 "{" */ CWORD_CWORD_CWORD_CWORD, + /* 254 124 "|" */ CSPCL_CWORD_CWORD_CWORD, + /* 255 125 "}" */ CENDVAR_CENDVAR_CWORD_CENDVAR, + /* 256 126 "~" */ CWORD_CCTL_CCTL_CWORD, + /* 257 127 */ CWORD_CWORD_CWORD_CWORD, +}; + +#endif /* USE_SIT_FUNCTION */ + +/* alias.c */ + + +#define ATABSIZE 39 + +static int funcblocksize; /* size of structures in function */ +static int funcstringsize; /* size of strings in node */ +static pointer funcblock; /* block to allocate function from */ +static char *funcstring; /* block to allocate strings from */ + +static const short nodesize[26] = { + SHELL_ALIGN(sizeof (struct ncmd)), + SHELL_ALIGN(sizeof (struct npipe)), + SHELL_ALIGN(sizeof (struct nredir)), + SHELL_ALIGN(sizeof (struct nredir)), + SHELL_ALIGN(sizeof (struct nredir)), + SHELL_ALIGN(sizeof (struct nbinary)), + SHELL_ALIGN(sizeof (struct nbinary)), + SHELL_ALIGN(sizeof (struct nbinary)), + SHELL_ALIGN(sizeof (struct nif)), + SHELL_ALIGN(sizeof (struct nbinary)), + SHELL_ALIGN(sizeof (struct nbinary)), + SHELL_ALIGN(sizeof (struct nfor)), + SHELL_ALIGN(sizeof (struct ncase)), + SHELL_ALIGN(sizeof (struct nclist)), + SHELL_ALIGN(sizeof (struct narg)), + SHELL_ALIGN(sizeof (struct narg)), + SHELL_ALIGN(sizeof (struct nfile)), + SHELL_ALIGN(sizeof (struct nfile)), + SHELL_ALIGN(sizeof (struct nfile)), + SHELL_ALIGN(sizeof (struct nfile)), + SHELL_ALIGN(sizeof (struct nfile)), + SHELL_ALIGN(sizeof (struct ndup)), + SHELL_ALIGN(sizeof (struct ndup)), + SHELL_ALIGN(sizeof (struct nhere)), + SHELL_ALIGN(sizeof (struct nhere)), + SHELL_ALIGN(sizeof (struct nnot)), +}; + + +static void calcsize(union node *); +static void sizenodelist(struct nodelist *); +static union node *copynode(union node *); +static struct nodelist *copynodelist(struct nodelist *); +static char *nodesavestr(char *); + + +static int evalstring(char *, int mask); +union node; /* BLETCH for ansi C */ +static void evaltree(union node *, int); +static void evalbackcmd(union node *, struct backcmd *); + +static int evalskip; /* set if we are skipping commands */ +static int skipcount; /* number of levels to skip */ +static int funcnest; /* depth of function calls */ + +/* reasons for skipping commands (see comment on breakcmd routine) */ +#define SKIPBREAK (1 << 0) +#define SKIPCONT (1 << 1) +#define SKIPFUNC (1 << 2) +#define SKIPFILE (1 << 3) +#define SKIPEVAL (1 << 4) + +/* + * This file was generated by the mkbuiltins program. + */ + +#if JOBS +static int bgcmd(int, char **); +#endif +static int breakcmd(int, char **); +static int cdcmd(int, char **); +#ifdef CONFIG_ASH_CMDCMD +static int commandcmd(int, char **); +#endif +static int dotcmd(int, char **); +static int evalcmd(int, char **); +#ifdef CONFIG_ASH_BUILTIN_ECHO +static int echocmd(int, char **); +#endif +#ifdef CONFIG_ASH_BUILTIN_TEST +static int testcmd(int, char **); +#endif +static int execcmd(int, char **); +static int exitcmd(int, char **); +static int exportcmd(int, char **); +static int falsecmd(int, char **); +#if JOBS +static int fgcmd(int, char **); +#endif +#ifdef CONFIG_ASH_GETOPTS +static int getoptscmd(int, char **); +#endif +static int hashcmd(int, char **); +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET +static int helpcmd(int argc, char **argv); +#endif +#if JOBS +static int jobscmd(int, char **); +#endif +#ifdef CONFIG_ASH_MATH_SUPPORT +static int letcmd(int, char **); +#endif +static int localcmd(int, char **); +static int pwdcmd(int, char **); +static int readcmd(int, char **); +static int returncmd(int, char **); +static int setcmd(int, char **); +static int shiftcmd(int, char **); +static int timescmd(int, char **); +static int trapcmd(int, char **); +static int truecmd(int, char **); +static int typecmd(int, char **); +static int umaskcmd(int, char **); +static int unsetcmd(int, char **); +static int waitcmd(int, char **); +static int ulimitcmd(int, char **); +#if JOBS +static int killcmd(int, char **); +#endif + +/* mail.h */ + +#ifdef CONFIG_ASH_MAIL +static void chkmail(void); +static void changemail(const char *); +#endif + +/* exec.h */ + +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDFUNCTION 1 /* command is a shell function */ +#define CMDBUILTIN 2 /* command is a shell builtin */ + +struct builtincmd { + const char *name; + int (*builtin)(int, char **); + /* unsigned flags; */ +}; + + +#define COMMANDCMD (builtincmd + 5 + \ + 2 * ENABLE_ASH_BUILTIN_TEST + \ + ENABLE_ASH_ALIAS + \ + ENABLE_ASH_JOB_CONTROL) +#define EXECCMD (builtincmd + 7 + \ + 2 * ENABLE_ASH_BUILTIN_TEST + \ + ENABLE_ASH_ALIAS + \ + ENABLE_ASH_JOB_CONTROL + \ + ENABLE_ASH_CMDCMD + \ + ENABLE_ASH_BUILTIN_ECHO) + +#define BUILTIN_NOSPEC "0" +#define BUILTIN_SPECIAL "1" +#define BUILTIN_REGULAR "2" +#define BUILTIN_SPEC_REG "3" +#define BUILTIN_ASSIGN "4" +#define BUILTIN_SPEC_ASSG "5" +#define BUILTIN_REG_ASSG "6" +#define BUILTIN_SPEC_REG_ASSG "7" + +#define IS_BUILTIN_SPECIAL(builtincmd) ((builtincmd)->name[0] & 1) +#define IS_BUILTIN_REGULAR(builtincmd) ((builtincmd)->name[0] & 2) +#define IS_BUILTIN_ASSIGN(builtincmd) ((builtincmd)->name[0] & 4) + +/* make sure to keep these in proper order since it is searched via bsearch() */ +static const struct builtincmd builtincmd[] = { + { BUILTIN_SPEC_REG ".", dotcmd }, + { BUILTIN_SPEC_REG ":", truecmd }, +#ifdef CONFIG_ASH_BUILTIN_TEST + { BUILTIN_REGULAR "[", testcmd }, + { BUILTIN_REGULAR "[[", testcmd }, +#endif +#ifdef CONFIG_ASH_ALIAS + { BUILTIN_REG_ASSG "alias", aliascmd }, +#endif +#if JOBS + { BUILTIN_REGULAR "bg", bgcmd }, +#endif + { BUILTIN_SPEC_REG "break", breakcmd }, + { BUILTIN_REGULAR "cd", cdcmd }, + { BUILTIN_NOSPEC "chdir", cdcmd }, +#ifdef CONFIG_ASH_CMDCMD + { BUILTIN_REGULAR "command", commandcmd }, +#endif + { BUILTIN_SPEC_REG "continue", breakcmd }, +#ifdef CONFIG_ASH_BUILTIN_ECHO + { BUILTIN_REGULAR "echo", echocmd }, +#endif + { BUILTIN_SPEC_REG "eval", evalcmd }, + { BUILTIN_SPEC_REG "exec", execcmd }, + { BUILTIN_SPEC_REG "exit", exitcmd }, + { BUILTIN_SPEC_REG_ASSG "export", exportcmd }, + { BUILTIN_REGULAR "false", falsecmd }, +#if JOBS + { BUILTIN_REGULAR "fg", fgcmd }, +#endif +#ifdef CONFIG_ASH_GETOPTS + { BUILTIN_REGULAR "getopts", getoptscmd }, +#endif + { BUILTIN_NOSPEC "hash", hashcmd }, +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET + { BUILTIN_NOSPEC "help", helpcmd }, +#endif +#if JOBS + { BUILTIN_REGULAR "jobs", jobscmd }, + { BUILTIN_REGULAR "kill", killcmd }, +#endif +#ifdef CONFIG_ASH_MATH_SUPPORT + { BUILTIN_NOSPEC "let", letcmd }, +#endif + { BUILTIN_ASSIGN "local", localcmd }, + { BUILTIN_NOSPEC "pwd", pwdcmd }, + { BUILTIN_REGULAR "read", readcmd }, + { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd }, + { BUILTIN_SPEC_REG "return", returncmd }, + { BUILTIN_SPEC_REG "set", setcmd }, + { BUILTIN_SPEC_REG "shift", shiftcmd }, + { BUILTIN_SPEC_REG "source", dotcmd }, +#ifdef CONFIG_ASH_BUILTIN_TEST + { BUILTIN_REGULAR "test", testcmd }, +#endif + { BUILTIN_SPEC_REG "times", timescmd }, + { BUILTIN_SPEC_REG "trap", trapcmd }, + { BUILTIN_REGULAR "true", truecmd }, + { BUILTIN_NOSPEC "type", typecmd }, + { BUILTIN_NOSPEC "ulimit", ulimitcmd }, + { BUILTIN_REGULAR "umask", umaskcmd }, +#ifdef CONFIG_ASH_ALIAS + { BUILTIN_REGULAR "unalias", unaliascmd }, +#endif + { BUILTIN_SPEC_REG "unset", unsetcmd }, + { BUILTIN_REGULAR "wait", waitcmd }, +}; + +#define NUMBUILTINS (sizeof (builtincmd) / sizeof (struct builtincmd) ) + +static const char *safe_applets[] = { + "[", "test", "echo", "cat", + "ln", "cp", "touch", "mkdir", "rm", + "cut", "hexdump", "awk", "sort", + "find", "xargs", "ls", "dd", + "chown", "chmod" +}; + + +struct cmdentry { + int cmdtype; + union param { + int index; + const struct builtincmd *cmd; + struct funcnode *func; + } u; +}; + + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_ABS 0x02 /* checks absolute paths */ +#define DO_NOFUNC 0x04 /* don't return shell functions, for command */ +#define DO_ALTPATH 0x08 /* using alternate path */ +#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ + +static const char *pathopt; /* set by padvance */ + +static void shellexec(char **, const char *, int) + ATTRIBUTE_NORETURN; +static char *padvance(const char **, const char *); +static void find_command(char *, struct cmdentry *, int, const char *); +static struct builtincmd *find_builtin(const char *); +static void hashcd(void); +static void changepath(const char *); +static void defun(char *, union node *); +static void unsetfunc(const char *); + +#ifdef CONFIG_ASH_MATH_SUPPORT_64 +typedef int64_t arith_t; +#define arith_t_type (long long) +#else +typedef long arith_t; +#define arith_t_type (long) +#endif + +#ifdef CONFIG_ASH_MATH_SUPPORT +static arith_t dash_arith(const char *); +static arith_t arith(const char *expr, int *perrcode); +#endif + +#ifdef CONFIG_ASH_RANDOM_SUPPORT +static unsigned long rseed; +static void change_random(const char *); +# ifndef DYNAMIC_VAR +# define DYNAMIC_VAR +# endif +#endif + +/* init.h */ + +static void reset(void); + +/* var.h */ + +/* + * Shell variables. + */ + +/* flags */ +#define VEXPORT 0x01 /* variable is exported */ +#define VREADONLY 0x02 /* variable cannot be modified */ +#define VSTRFIXED 0x04 /* variable struct is statically allocated */ +#define VTEXTFIXED 0x08 /* text is statically allocated */ +#define VSTACK 0x10 /* text is allocated on the stack */ +#define VUNSET 0x20 /* the variable is not set */ +#define VNOFUNC 0x40 /* don't call the callback function */ +#define VNOSET 0x80 /* do not set variable - just readonly test */ +#define VNOSAVE 0x100 /* when text is on the heap before setvareq */ +#ifdef DYNAMIC_VAR +# define VDYNAMIC 0x200 /* dynamic variable */ +# else +# define VDYNAMIC 0 +#endif + +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + const char *text; /* name=value */ + void (*func)(const char *); /* function to be called when */ + /* the variable gets set/unset */ +}; + +struct localvar { + struct localvar *next; /* next local variable in list */ + struct var *vp; /* the variable that was made local */ + int flags; /* saved flags */ + const char *text; /* saved text */ +}; + + +static struct localvar *localvars; + +/* + * Shell variables. + */ + +#ifdef CONFIG_ASH_GETOPTS +static void getoptsreset(const char *); +#endif + +#ifdef CONFIG_LOCALE_SUPPORT +static void change_lc_all(const char *value); +static void change_lc_ctype(const char *value); +#endif + + +#define VTABSIZE 39 + +static const char defpathvar[] = "PATH=/usr/local/bin:/usr/bin:/sbin:/bin"; +#ifdef IFS_BROKEN +static const char defifsvar[] = "IFS= \t\n"; +#define defifs (defifsvar + 4) +#else +static const char defifs[] = " \t\n"; +#endif + + +static struct var varinit[] = { +#ifdef IFS_BROKEN + { 0, VSTRFIXED|VTEXTFIXED, defifsvar, 0 }, +#else + { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0", 0 }, +#endif + +#ifdef CONFIG_ASH_MAIL + { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0", changemail }, + { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail }, +#endif + + { 0, VSTRFIXED|VTEXTFIXED, defpathvar, changepath }, + { 0, VSTRFIXED|VTEXTFIXED, "PS1=$ ", 0 }, + { 0, VSTRFIXED|VTEXTFIXED, "PS2=> ", 0 }, + { 0, VSTRFIXED|VTEXTFIXED, "PS4=+ ", 0 }, +#ifdef CONFIG_ASH_GETOPTS + { 0, VSTRFIXED|VTEXTFIXED, "OPTIND=1", getoptsreset }, +#endif +#ifdef CONFIG_ASH_RANDOM_SUPPORT + {0, VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random }, +#endif +#ifdef CONFIG_LOCALE_SUPPORT + {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_ALL\0", change_lc_all }, + {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_CTYPE\0", change_lc_ctype }, +#endif +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY + {0, VSTRFIXED | VTEXTFIXED | VUNSET, "HISTFILE\0", NULL }, +#endif +}; + +#define vifs varinit[0] +#ifdef CONFIG_ASH_MAIL +#define vmail (&vifs)[1] +#define vmpath (&vmail)[1] +#else +#define vmpath vifs +#endif +#define vpath (&vmpath)[1] +#define vps1 (&vpath)[1] +#define vps2 (&vps1)[1] +#define vps4 (&vps2)[1] +#define voptind (&vps4)[1] +#ifdef CONFIG_ASH_GETOPTS +#define vrandom (&voptind)[1] +#else +#define vrandom (&vps4)[1] +#endif +#define defpath (defpathvar + 5) + +/* + * The following macros access the values of the above variables. + * They have to skip over the name. They return the null string + * for unset variables. + */ + +#define ifsval() (vifs.text + 4) +#define ifsset() ((vifs.flags & VUNSET) == 0) +#define mailval() (vmail.text + 5) +#define mpathval() (vmpath.text + 9) +#define pathval() (vpath.text + 5) +#define ps1val() (vps1.text + 4) +#define ps2val() (vps2.text + 4) +#define ps4val() (vps4.text + 4) +#define optindval() (voptind.text + 7) + +#define mpathset() ((vmpath.flags & VUNSET) == 0) + +static void setvar(const char *, const char *, int); +static void setvareq(char *, int); +static void listsetvar(struct strlist *, int); +static char *lookupvar(const char *); +static char *bltinlookup(const char *); +static char **listvars(int, int, char ***); +#define environment() listvars(VEXPORT, VUNSET, 0) +static int showvars(const char *, int, int); +static void poplocalvars(void); +static int unsetvar(const char *); +#ifdef CONFIG_ASH_GETOPTS +static int setvarsafe(const char *, const char *, int); +#endif +static int varcmp(const char *, const char *); +static struct var **hashvar(const char *); + + +static int varequal(const char *a, const char *b) { + return !varcmp(a, b); +} + + +static int loopnest; /* current loop nesting level */ + +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ + + +struct redirtab { + struct redirtab *next; + int renamed[10]; + int nullredirs; +}; + +static struct redirtab *redirlist; +static int nullredirs; + +extern char **environ; + +/* output.h */ + + +static void outstr(const char *, FILE *); +static void outcslow(int, FILE *); +static void flushall(void); +static void flusherr(void); +static int out1fmt(const char *, ...) + __attribute__((__format__(__printf__,1,2))); +static int fmtstr(char *, size_t, const char *, ...) + __attribute__((__format__(__printf__,3,4))); + +static int preverrout_fd; /* save fd2 before print debug if xflag is set. */ + + +static void out1str(const char *p) +{ + outstr(p, stdout); +} + +static void out2str(const char *p) +{ + outstr(p, stderr); + flusherr(); +} + +/* + * Initialization code. + */ + +/* + * This routine initializes the builtin variables. + */ + +static void initvar(void) +{ + struct var *vp; + struct var *end; + struct var **vpp; + + /* + * PS1 depends on uid + */ +#if defined(CONFIG_FEATURE_COMMAND_EDITING) && defined(CONFIG_FEATURE_SH_FANCY_PROMPT) + vps1.text = "PS1=\\w \\$ "; +#else + if (!geteuid()) + vps1.text = "PS1=# "; +#endif + vp = varinit; + end = vp + sizeof(varinit) / sizeof(varinit[0]); + do { + vpp = hashvar(vp->text); + vp->next = *vpp; + *vpp = vp; + } while (++vp < end); +} + +static void init(void) +{ + + /* from input.c: */ + { + basepf.nextc = basepf.buf = basebuf; + } + + /* from trap.c: */ + { + signal(SIGCHLD, SIG_DFL); + } + + /* from var.c: */ + { + char **envp; + char ppid[32]; + const char *p; + struct stat st1, st2; + + initvar(); + for (envp = environ ; envp && *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + + snprintf(ppid, sizeof(ppid), "%d", (int) getppid()); + setvar("PPID", ppid, 0); + + p = lookupvar("PWD"); + if (p) + if (*p != '/' || stat(p, &st1) || stat(".", &st2) || + st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) + p = 0; + setpwd(p, 0); + } +} + +/* PEOF (the end of file marker) */ + +enum { + INPUT_PUSH_FILE = 1, + INPUT_NOFILE_OK = 2, +}; + +/* + * The input line number. Input.c just defines this variable, and saves + * and restores it when files are pushed and popped. The user of this + * package must set its value. + */ + +static int pgetc(void); +static int pgetc2(void); +static int preadbuffer(void); +static void pungetc(void); +static void pushstring(char *, void *); +static void popstring(void); +static void setinputfd(int, int); +static void setinputstring(char *); +static void popfile(void); +static void popallfiles(void); +static void closescript(void); + + +/* jobs.h */ + + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +/* mode flags for showjob(s) */ +#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */ +#define SHOW_PID 0x04 /* include process pid */ +#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */ + + +/* + * A job structure contains information about a job. A job is either a + * single process or a set of processes contained in a pipeline. In the + * latter case, pidlist will be non-NULL, and will point to a -1 terminated + * array of pids. + */ + +struct procstat { + pid_t pid; /* process id */ + int status; /* last process status from wait() */ + char *cmd; /* text of command being run */ +}; + +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ +#if JOBS + int stopstatus; /* status of a stopped job */ +#endif + uint32_t + nprocs: 16, /* number of processes */ + state: 8, +#define JOBRUNNING 0 /* at least one proc running */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ +#if JOBS + sigint: 1, /* job was killed by SIGINT */ + jobctl: 1, /* job running under job control */ +#endif + waited: 1, /* true if this entry has been waited for */ + used: 1, /* true if this entry is in used */ + changed: 1; /* true if status has changed */ + struct job *prev_job; /* previous job */ +}; + +static pid_t backgndpid; /* pid of last background process */ +static int job_warning; /* user was warned about stopped jobs */ +#if JOBS +static int jobctl; /* true if doing job control */ +#endif + +static struct job *makejob(union node *, int); +static int forkshell(struct job *, union node *, int); +static int waitforjob(struct job *); +static int stoppedjobs(void); + +#if ! JOBS +#define setjobctl(on) /* do nothing */ +#else +static void setjobctl(int); +static void showjobs(FILE *, int); +#endif + +/* main.h */ + + +/* pid of main shell */ +static int rootpid; +/* shell level: 0 for the main shell, 1 for its children, and so on */ +static int shlvl; +#define rootshell (!shlvl) + +static void readcmdfile(char *); +static int cmdloop(int); + +/* memalloc.h */ + + +struct stackmark { + struct stack_block *stackp; + char *stacknxt; + size_t stacknleft; + struct stackmark *marknext; +}; + +/* minimum size of a block */ +#define MINSIZE SHELL_ALIGN(504) + +struct stack_block { + struct stack_block *prev; + char space[MINSIZE]; +}; + +static struct stack_block stackbase; +static struct stack_block *stackp = &stackbase; +static struct stackmark *markp; +static char *stacknxt = stackbase.space; +static size_t stacknleft = MINSIZE; +static char *sstrend = stackbase.space + MINSIZE; +static int herefd = -1; + + +static pointer ckmalloc(size_t); +static pointer ckrealloc(pointer, size_t); +static char *savestr(const char *); +static pointer stalloc(size_t); +static void stunalloc(pointer); +static void setstackmark(struct stackmark *); +static void popstackmark(struct stackmark *); +static void growstackblock(void); +static void *growstackstr(void); +static char *makestrspace(size_t, char *); +static char *stnputs(const char *, size_t, char *); +static char *stputs(const char *, char *); + + +static char *_STPUTC(int c, char *p) { + if (p == sstrend) + p = growstackstr(); + *p++ = c; + return p; +} + +#define stackblock() ((void *)stacknxt) +#define stackblocksize() stacknleft +#define STARTSTACKSTR(p) ((p) = stackblock()) +#define STPUTC(c, p) ((p) = _STPUTC((c), (p))) +#define CHECKSTRSPACE(n, p) \ + ({ \ + char *q = (p); \ + size_t l = (n); \ + size_t m = sstrend - q; \ + if (l > m) \ + (p) = makestrspace(l, q); \ + 0; \ + }) +#define USTPUTC(c, p) (*p++ = (c)) +#define STACKSTRNUL(p) ((p) == sstrend? (p = growstackstr(), *p = '\0') : (*p = '\0')) +#define STUNPUTC(p) (--p) +#define STTOPC(p) p[-1] +#define STADJUST(amount, p) (p += (amount)) + +#define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock()) +#define ungrabstackstr(s, p) stunalloc((s)) +#define stackstrend() ((void *)sstrend) + +#define ckfree(p) free((pointer)(p)) + +/* mystring.h */ + + +#define DOLATSTRLEN 4 + +static char *prefix(const char *, const char *); +static int number(const char *); +static int is_number(const char *); +static char *single_quote(const char *); +static char *sstrdup(const char *); + +#define equal(s1, s2) (strcmp(s1, s2) == 0) +#define scopy(s1, s2) ((void)strcpy(s2, s1)) + +/* options.h */ + +struct shparam { + int nparam; /* # of positional parameters (without $0) */ + unsigned char malloc; /* if parameter list dynamically allocated */ + char **p; /* parameter list */ +#ifdef CONFIG_ASH_GETOPTS + int optind; /* next parameter to be processed by getopts */ + int optoff; /* used by getopts */ +#endif +}; + + +#define eflag optlist[0] +#define fflag optlist[1] +#define Iflag optlist[2] +#define iflag optlist[3] +#define mflag optlist[4] +#define nflag optlist[5] +#define sflag optlist[6] +#define xflag optlist[7] +#define vflag optlist[8] +#define Cflag optlist[9] +#define aflag optlist[10] +#define bflag optlist[11] +#define uflag optlist[12] +#define viflag optlist[13] + +#if DEBUG +#define nolog optlist[14] +#define debug optlist[15] +#endif + +#ifndef CONFIG_FEATURE_COMMAND_EDITING_VI +#define setvimode(on) viflag = 0 /* forcibly keep the option off */ +#endif + +/* options.c */ + + +static const char *const optletters_optnames[] = { + "e" "errexit", + "f" "noglob", + "I" "ignoreeof", + "i" "interactive", + "m" "monitor", + "n" "noexec", + "s" "stdin", + "x" "xtrace", + "v" "verbose", + "C" "noclobber", + "a" "allexport", + "b" "notify", + "u" "nounset", + "\0" "vi", +#if DEBUG + "\0" "nolog", + "\0" "debug", +#endif +}; + +#define optletters(n) optletters_optnames[(n)][0] +#define optnames(n) (&optletters_optnames[(n)][1]) + +#define NOPTS (sizeof(optletters_optnames)/sizeof(optletters_optnames[0])) + +static char optlist[NOPTS]; + + +static char *arg0; /* value of $0 */ +static struct shparam shellparam; /* $@ current positional parameters */ +static char **argptr; /* argument list for builtin commands */ +static char *optionarg; /* set by nextopt (like getopt) */ +static char *optptr; /* used by nextopt */ + +static char *minusc; /* argument to -c option */ + + +static void procargs(int, char **); +static void optschanged(void); +static void setparam(char **); +static void freeparam(volatile struct shparam *); +static int shiftcmd(int, char **); +static int setcmd(int, char **); +static int nextopt(const char *); + +/* redir.h */ + +/* flags passed to redirect */ +#define REDIR_PUSH 01 /* save previous values of file descriptors */ +#define REDIR_SAVEFD2 03 /* set preverrout */ + +union node; +static void redirect(union node *, int); +static void popredir(int); +static void clearredir(int); +static int copyfd(int, int); +static int redirectsafe(union node *, int); + +/* show.h */ + + +#if DEBUG +static void showtree(union node *); +static void trace(const char *, ...); +static void tracev(const char *, va_list); +static void trargs(char **); +static void trputc(int); +static void trputs(const char *); +static void opentrace(void); +#endif + +/* trap.h */ + + +/* trap handler commands */ +static char *trap[NSIG]; +/* current value of signal */ +static char sigmode[NSIG - 1]; +/* indicates specified signal received */ +static char gotsig[NSIG - 1]; + +static void clear_traps(void); +static void setsignal(int); +static void ignoresig(int); +static void onsig(int); +static int dotrap(void); +static void setinteractive(int); +static void exitshell(void) ATTRIBUTE_NORETURN; + + +static int is_safe_applet(char *name) +{ + int n = sizeof(safe_applets) / sizeof(char *); + int i; + for (i = 0; i < n; i++) + if (strcmp(safe_applets[i], name) == 0) + return 1; + + return 0; +} + + +/* + * This routine is called when an error or an interrupt occurs in an + * interactive shell and control is returned to the main command loop. + */ + +static void +reset(void) +{ + /* from eval.c: */ + { + evalskip = 0; + loopnest = 0; + } + + /* from input.c: */ + { + parselleft = parsenleft = 0; /* clear input buffer */ + popallfiles(); + } + + /* from parser.c: */ + { + tokpushback = 0; + checkkwd = 0; + } + + /* from redir.c: */ + { + clearredir(0); + } + +} + +#ifdef CONFIG_ASH_ALIAS +static struct alias *atab[ATABSIZE]; + +static void setalias(const char *, const char *); +static struct alias *freealias(struct alias *); +static struct alias **__lookupalias(const char *); + +static void +setalias(const char *name, const char *val) +{ + struct alias *ap, **app; + + app = __lookupalias(name); + ap = *app; + INTOFF; + if (ap) { + if (!(ap->flag & ALIASINUSE)) { + ckfree(ap->val); + } + ap->val = savestr(val); + ap->flag &= ~ALIASDEAD; + } else { + /* not found */ + ap = ckmalloc(sizeof (struct alias)); + ap->name = savestr(name); + ap->val = savestr(val); + ap->flag = 0; + ap->next = 0; + *app = ap; + } + INTON; +} + +static int +unalias(const char *name) +{ + struct alias **app; + + app = __lookupalias(name); + + if (*app) { + INTOFF; + *app = freealias(*app); + INTON; + return 0; + } + + return 1; +} + +static void +rmaliases(void) +{ + struct alias *ap, **app; + int i; + + INTOFF; + for (i = 0; i < ATABSIZE; i++) { + app = &atab[i]; + for (ap = *app; ap; ap = *app) { + *app = freealias(*app); + if (ap == *app) { + app = &ap->next; + } + } + } + INTON; +} + +static struct alias * +lookupalias(const char *name, int check) +{ + struct alias *ap = *__lookupalias(name); + + if (check && ap && (ap->flag & ALIASINUSE)) + return NULL; + return ap; +} + +/* + * TODO - sort output + */ +static int +aliascmd(int argc, char **argv) +{ + char *n, *v; + int ret = 0; + struct alias *ap; + + if (argc == 1) { + int i; + + for (i = 0; i < ATABSIZE; i++) + for (ap = atab[i]; ap; ap = ap->next) { + printalias(ap); + } + return 0; + } + while ((n = *++argv) != NULL) { + if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */ + if ((ap = *__lookupalias(n)) == NULL) { + fprintf(stderr, "%s: %s not found\n", "alias", n); + ret = 1; + } else + printalias(ap); + } else { + *v++ = '\0'; + setalias(n, v); + } + } + + return ret; +} + +static int +unaliascmd(int argc, char **argv) +{ + int i; + + while ((i = nextopt("a")) != '\0') { + if (i == 'a') { + rmaliases(); + return 0; + } + } + for (i = 0; *argptr; argptr++) { + if (unalias(*argptr)) { + fprintf(stderr, "%s: %s not found\n", "unalias", *argptr); + i = 1; + } + } + + return i; +} + +static struct alias * +freealias(struct alias *ap) { + struct alias *next; + + if (ap->flag & ALIASINUSE) { + ap->flag |= ALIASDEAD; + return ap; + } + + next = ap->next; + ckfree(ap->name); + ckfree(ap->val); + ckfree(ap); + return next; +} + +static void +printalias(const struct alias *ap) { + out1fmt("%s=%s\n", ap->name, single_quote(ap->val)); +} + +static struct alias ** +__lookupalias(const char *name) { + unsigned int hashval; + struct alias **app; + const char *p; + unsigned int ch; + + p = name; + + ch = (unsigned char)*p; + hashval = ch << 4; + while (ch) { + hashval += ch; + ch = (unsigned char)*++p; + } + app = &atab[hashval % ATABSIZE]; + + for (; *app; app = &(*app)->next) { + if (equal(name, (*app)->name)) { + break; + } + } + + return app; +} +#endif /* CONFIG_ASH_ALIAS */ + + +/* cd.c */ + +/* + * The cd and pwd commands. + */ + +#define CD_PHYSICAL 1 +#define CD_PRINT 2 + +static int docd(const char *, int); +static int cdopt(void); + +static char *curdir = nullstr; /* current working directory */ +static char *physdir = nullstr; /* physical working directory */ + +static int +cdopt(void) +{ + int flags = 0; + int i, j; + + j = 'L'; + while ((i = nextopt("LP"))) { + if (i != j) { + flags ^= CD_PHYSICAL; + j = i; + } + } + + return flags; +} + +static int +cdcmd(int argc, char **argv) +{ + const char *dest; + const char *path; + const char *p; + char c; + struct stat statb; + int flags; + + flags = cdopt(); + dest = *argptr; + if (!dest) + dest = bltinlookup(homestr); + else if (dest[0] == '-' && dest[1] == '\0') { + dest = bltinlookup("OLDPWD"); + flags |= CD_PRINT; + } + if (!dest) + dest = nullstr; + if (*dest == '/') + goto step7; + if (*dest == '.') { + c = dest[1]; +dotdot: + switch (c) { + case '\0': + case '/': + goto step6; + case '.': + c = dest[2]; + if (c != '.') + goto dotdot; + } + } + if (!*dest) + dest = "."; + if (!(path = bltinlookup("CDPATH"))) { +step6: +step7: + p = dest; + goto docd; + } + do { + c = *path; + p = padvance(&path, dest); + if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { + if (c && c != ':') + flags |= CD_PRINT; +docd: + if (!docd(p, flags)) + goto out; + break; + } + } while (path); + sh_error("can't cd to %s", dest); + /* NOTREACHED */ +out: + if (flags & CD_PRINT) + out1fmt(snlfmt, curdir); + return 0; +} + + +/* + * Update curdir (the name of the current directory) in response to a + * cd command. + */ + +static const char * updatepwd(const char *dir) +{ + char *new; + char *p; + char *cdcomppath; + const char *lim; + + cdcomppath = sstrdup(dir); + STARTSTACKSTR(new); + if (*dir != '/') { + if (curdir == nullstr) + return 0; + new = stputs(curdir, new); + } + new = makestrspace(strlen(dir) + 2, new); + lim = stackblock() + 1; + if (*dir != '/') { + if (new[-1] != '/') + USTPUTC('/', new); + if (new > lim && *lim == '/') + lim++; + } else { + USTPUTC('/', new); + cdcomppath++; + if (dir[1] == '/' && dir[2] != '/') { + USTPUTC('/', new); + cdcomppath++; + lim++; + } + } + p = strtok(cdcomppath, "/"); + while (p) { + switch(*p) { + case '.': + if (p[1] == '.' && p[2] == '\0') { + while (new > lim) { + STUNPUTC(new); + if (new[-1] == '/') + break; + } + break; + } else if (p[1] == '\0') + break; + /* fall through */ + default: + new = stputs(p, new); + USTPUTC('/', new); + } + p = strtok(0, "/"); + } + if (new > lim) + STUNPUTC(new); + *new = 0; + return stackblock(); +} + +/* + * Actually do the chdir. We also call hashcd to let the routines in exec.c + * know that the current directory has changed. + */ + +static int +docd(const char *dest, int flags) +{ + const char *dir = 0; + int err; + + TRACE(("docd(\"%s\", %d) called\n", dest, flags)); + + INTOFF; + if (!(flags & CD_PHYSICAL)) { + dir = updatepwd(dest); + if (dir) + dest = dir; + } + err = chdir(dest); + if (err) + goto out; + setpwd(dir, 1); + hashcd(); +out: + INTON; + return err; +} + +/* + * Find out what the current directory is. If we already know the current + * directory, this routine returns immediately. + */ +static char * getpwd(void) +{ + char *dir = getcwd(0, 0); + return dir ? dir : nullstr; +} + +static int +pwdcmd(int argc, char **argv) +{ + int flags; + const char *dir = curdir; + + flags = cdopt(); + if (flags) { + if (physdir == nullstr) + setpwd(dir, 0); + dir = physdir; + } + out1fmt(snlfmt, dir); + return 0; +} + +static void +setpwd(const char *val, int setold) +{ + char *oldcur, *dir; + + oldcur = dir = curdir; + + if (setold) { + setvar("OLDPWD", oldcur, VEXPORT); + } + INTOFF; + if (physdir != nullstr) { + if (physdir != oldcur) + free(physdir); + physdir = nullstr; + } + if (oldcur == val || !val) { + char *s = getpwd(); + physdir = s; + if (!val) + dir = s; + } else + dir = savestr(val); + if (oldcur != dir && oldcur != nullstr) { + free(oldcur); + } + curdir = dir; + INTON; + setvar("PWD", dir, VEXPORT); +} + +/* error.c */ + +/* + * Errors and exceptions. + */ + +/* + * Code to handle exceptions in C. + */ + + + +static void exverror(int, const char *, va_list) + ATTRIBUTE_NORETURN; + +/* + * Called to raise an exception. Since C doesn't include exceptions, we + * just do a longjmp to the exception handler. The type of exception is + * stored in the global variable "exception". + */ + +static void +exraise(int e) +{ +#if DEBUG + if (handler == NULL) + abort(); +#endif + INTOFF; + + exception = e; + longjmp(handler->loc, 1); +} + + +/* + * Called from trap.c when a SIGINT is received. (If the user specifies + * that SIGINT is to be trapped or ignored using the trap builtin, then + * this routine is not called.) Suppressint is nonzero when interrupts + * are held using the INTOFF macro. (The test for iflag is just + * defensive programming.) + */ + +static void +onint(void) { + int i; + + intpending = 0; + i = EXSIG; + if (gotsig[SIGINT - 1] && !trap[SIGINT]) { + if (!(rootshell && iflag)) { + signal(SIGINT, SIG_DFL); + raise(SIGINT); + } + i = EXINT; + } + exraise(i); + /* NOTREACHED */ +} + +static void +exvwarning(const char *msg, va_list ap) +{ + FILE *errs; + + errs = stderr; + fprintf(errs, "%s: ", arg0); + if (commandname) { + const char *fmt = (!iflag || parsefile->fd) ? + "%s: %d: " : "%s: "; + fprintf(errs, fmt, commandname, startlinno); + } + vfprintf(errs, msg, ap); + outcslow('\n', errs); +} + +/* + * Exverror is called to raise the error exception. If the second argument + * is not NULL then error prints an error message using printf style + * formatting. It then raises the error exception. + */ +static void +exverror(int cond, const char *msg, va_list ap) +{ +#if DEBUG + if (msg) { + TRACE(("exverror(%d, \"", cond)); + TRACEV((msg, ap)); + TRACE(("\") pid=%d\n", getpid())); + } else + TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid())); + if (msg) +#endif + exvwarning(msg, ap); + + flushall(); + exraise(cond); + /* NOTREACHED */ +} + + +static void +sh_error(const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + exverror(EXERROR, msg, ap); + /* NOTREACHED */ + va_end(ap); +} + + +static void +exerror(int cond, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + exverror(cond, msg, ap); + /* NOTREACHED */ + va_end(ap); +} + +/* + * error/warning routines for external builtins + */ + +static void +sh_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + exvwarning(fmt, ap); + va_end(ap); +} + + +/* + * Return a string describing an error. The returned string may be a + * pointer to a static buffer that will be overwritten on the next call. + * Action describes the operation that got the error. + */ + +static const char * +errmsg(int e, const char *em) +{ + if(e == ENOENT || e == ENOTDIR) { + + return em; + } + return strerror(e); +} + + +/* eval.c */ + +/* + * Evaluate a command. + */ + +/* flags in argument to evaltree */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 04 /* command executing within back quotes */ + + +static void evalloop(union node *, int); +static void evalfor(union node *, int); +static void evalcase(union node *, int); +static void evalsubshell(union node *, int); +static void expredir(union node *); +static void evalpipe(union node *, int); +static void evalcommand(union node *, int); +static int evalbltin(const struct builtincmd *, int, char **); +static int evalfun(struct funcnode *, int, char **, int); +static void prehash(union node *); +static int bltincmd(int, char **); + + +static const struct builtincmd bltin = { + "\0\0", bltincmd +}; + + +/* + * Called to reset things after an exception. + */ + +/* + * The eval command. + */ + +static int +evalcmd(int argc, char **argv) +{ + char *p; + char *concat; + char **ap; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + concat = stputs(p, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, ~SKIPEVAL); + + } + return exitstatus; +} + + +/* + * Execute a command or commands contained in a string. + */ + +static int +evalstring(char *s, int mask) +{ + union node *n; + struct stackmark smark; + int skip; + + setinputstring(s); + setstackmark(&smark); + + skip = 0; + while ((n = parsecmd(0)) != NEOF) { + evaltree(n, 0); + popstackmark(&smark); + skip = evalskip; + if (skip) + break; + } + popfile(); + + skip &= mask; + evalskip = skip; + return skip; +} + + + +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ + +static void +evaltree(union node *n, int flags) +{ + int checkexit = 0; + void (*evalfn)(union node *, int); + unsigned isor; + int status; + if (n == NULL) { + TRACE(("evaltree(NULL) called\n")); + goto out; + } + TRACE(("pid %d, evaltree(%p: %d, %d) called\n", + getpid(), n, n->type, flags)); + switch (n->type) { + default: +#if DEBUG + out1fmt("Node type = %d\n", n->type); + fflush(stdout); + break; +#endif + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + status = !exitstatus; + goto setstatus; + case NREDIR: + expredir(n->nredir.redirect); + status = redirectsafe(n->nredir.redirect, REDIR_PUSH); + if (!status) { + evaltree(n->nredir.n, flags & EV_TESTED); + status = exitstatus; + } + popredir(0); + goto setstatus; + case NCMD: + evalfn = evalcommand; +checkexit: + if (eflag && !(flags & EV_TESTED)) + checkexit = ~0; + goto calleval; + case NFOR: + evalfn = evalfor; + goto calleval; + case NWHILE: + case NUNTIL: + evalfn = evalloop; + goto calleval; + case NSUBSHELL: + case NBACKGND: + evalfn = evalsubshell; + goto calleval; + case NPIPE: + evalfn = evalpipe; + goto checkexit; + case NCASE: + evalfn = evalcase; + goto calleval; + case NAND: + case NOR: + case NSEMI: +#if NAND + 1 != NOR +#error NAND + 1 != NOR +#endif +#if NOR + 1 != NSEMI +#error NOR + 1 != NSEMI +#endif + isor = n->type - NAND; + evaltree( + n->nbinary.ch1, + (flags | ((isor >> 1) - 1)) & EV_TESTED + ); + if (!exitstatus == isor) + break; + if (!evalskip) { + n = n->nbinary.ch2; +evaln: + evalfn = evaltree; +calleval: + evalfn(n, flags); + break; + } + break; + case NIF: + evaltree(n->nif.test, EV_TESTED); + if (evalskip) + break; + if (exitstatus == 0) { + n = n->nif.ifpart; + goto evaln; + } else if (n->nif.elsepart) { + n = n->nif.elsepart; + goto evaln; + } + goto success; + case NDEFUN: + defun(n->narg.text, n->narg.next); +success: + status = 0; +setstatus: + exitstatus = status; + break; + } +out: + if ((checkexit & exitstatus)) + evalskip |= SKIPEVAL; + else if (pendingsigs && dotrap()) + goto exexit; + + if (flags & EV_EXIT) { +exexit: + exraise(EXEXIT); + } +} + + +#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) +static +#endif +void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); + + +static void +evalloop(union node *n, int flags) +{ + int status; + + loopnest++; + status = 0; + flags &= EV_TESTED; + for (;;) { + int i; + + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip) { +skipping: if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + i = exitstatus; + if (n->type != NWHILE) + i = !i; + if (i != 0) + break; + evaltree(n->nbinary.ch2, flags); + status = exitstatus; + if (evalskip) + goto skipping; + } + loopnest--; + exitstatus = status; +} + + + +static void +evalfor(union node *n, int flags) +{ + struct arglist arglist; + union node *argp; + struct strlist *sp; + struct stackmark smark; + + setstackmark(&smark); + arglist.lastp = &arglist.list; + for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD); + /* XXX */ + if (evalskip) + goto out; + } + *arglist.lastp = NULL; + + exitstatus = 0; + loopnest++; + flags &= EV_TESTED; + for (sp = arglist.list ; sp ; sp = sp->next) { + setvar(n->nfor.var, sp->text, 0); + evaltree(n->nfor.body, flags); + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + } + loopnest--; +out: + popstackmark(&smark); +} + + + +static void +evalcase(union node *n, int flags) +{ + union node *cp; + union node *patp; + struct arglist arglist; + struct stackmark smark; + + setstackmark(&smark); + arglist.lastp = &arglist.list; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + exitstatus = 0; + for (cp = n->ncase.cases ; cp && evalskip == 0 ; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) { + if (casematch(patp, arglist.list->text)) { + if (evalskip == 0) { + evaltree(cp->nclist.body, flags); + } + goto out; + } + } + } +out: + popstackmark(&smark); +} + + + +/* + * Kick off a subshell to evaluate a tree. + */ + +static void +evalsubshell(union node *n, int flags) +{ + struct job *jp; + int backgnd = (n->type == NBACKGND); + int status; + + expredir(n->nredir.redirect); + if (!backgnd && flags & EV_EXIT && !trap[0]) + goto nofork; + INTOFF; + jp = makejob(n, 1); + if (forkshell(jp, n, backgnd) == 0) { + INTON; + flags |= EV_EXIT; + if (backgnd) + flags &=~ EV_TESTED; +nofork: + redirect(n->nredir.redirect, 0); + evaltreenr(n->nredir.n, flags); + /* never returns */ + } + status = 0; + if (! backgnd) + status = waitforjob(jp); + exitstatus = status; + INTON; +} + + + +/* + * Compute the names of the files in a redirection list. + */ + +static void +expredir(union node *n) +{ + union node *redir; + + for (redir = n ; redir ; redir = redir->nfile.next) { + struct arglist fn; + memset(&fn, 0, sizeof(struct arglist)); + fn.lastp = &fn.list; + switch (redir->type) { + case NFROMTO: + case NFROM: + case NTO: + case NCLOBBER: + case NAPPEND: + expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); + redir->nfile.expfname = fn.list->text; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE); + if (fn.list != NULL) + fixredir(redir, fn.list->text, 1); + else + sh_error("redir error"); + } + break; + } + } +} + + + +/* + * Evaluate a pipeline. All the processes in the pipeline are children + * of the process creating the pipeline. (This differs from some versions + * of the shell, which make the last process in a pipeline the parent + * of all the rest.) + */ + +static void +evalpipe(union node *n, int flags) +{ + struct job *jp; + struct nodelist *lp; + int pipelen; + int prevfd; + int pip[2]; + + TRACE(("evalpipe(0x%lx) called\n", (long)n)); + pipelen = 0; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) + pipelen++; + flags |= EV_EXIT; + INTOFF; + jp = makejob(n, pipelen); + prevfd = -1; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + prehash(lp->n); + pip[1] = -1; + if (lp->next) { + if (pipe(pip) < 0) { + close(prevfd); + sh_error("Pipe call failed"); + } + } + if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { + INTON; + if (pip[1] >= 0) { + close(pip[0]); + } + if (prevfd > 0) { + dup2(prevfd, 0); + close(prevfd); + } + if (pip[1] > 1) { + dup2(pip[1], 1); + close(pip[1]); + } + evaltreenr(lp->n, flags); + /* never returns */ + } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + close(pip[1]); + } + if (n->npipe.backgnd == 0) { + exitstatus = waitforjob(jp); + TRACE(("evalpipe: job done exit status %d\n", exitstatus)); + } + INTON; +} + + + +/* + * Execute a command inside back quotes. If it's a builtin command, we + * want to save its output in a block obtained from malloc. Otherwise + * we fork off a subprocess and get the output of the command via a pipe. + * Should be called with interrupts off. + */ + +static void +evalbackcmd(union node *n, struct backcmd *result) +{ + int saveherefd; + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + if (n == NULL) { + goto out; + } + + saveherefd = herefd; + herefd = -1; + + { + int pip[2]; + struct job *jp; + + if (pipe(pip) < 0) + sh_error("Pipe call failed"); + jp = makejob(n, 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + close(1); + copyfd(pip[1], 1); + close(pip[1]); + } + eflag = 0; + evaltreenr(n, EV_EXIT); + /* NOTREACHED */ + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + } + herefd = saveherefd; +out: + TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +#ifdef CONFIG_ASH_CMDCMD +static char ** parse_command_args(char **argv, const char **path) +{ + char *cp, c; + + for (;;) { + cp = *++argv; + if (!cp) + return 0; + if (*cp++ != '-') + break; + if (!(c = *cp++)) + break; + if (c == '-' && !*cp) { + argv++; + break; + } + do { + switch (c) { + case 'p': + *path = defpath; + break; + default: + /* run 'typecmd' for other options */ + return 0; + } + } while ((c = *cp++)); + } + return argv; +} +#endif + +static int isassignment(const char *p) +{ + const char *q = endofname(p); + if (p == q) + return 0; + return *q == '='; +} + +#ifdef CONFIG_ASH_EXPAND_PRMT +static const char *expandstr(const char *ps); +#else +#define expandstr(s) s +#endif + +/* + * Execute a simple command. + */ + +static void +evalcommand(union node *cmd, int flags) +{ + struct stackmark smark; + union node *argp; + struct arglist arglist; + struct arglist varlist; + char **argv; + int argc; + const struct strlist *sp; + struct cmdentry cmdentry; + struct job *jp; + char *lastarg; + const char *path; + int spclbltin; + int cmd_is_exec; + int status; + char **nargv; + struct builtincmd *bcmd; + int pseudovarflag = 0; + + /* First expand the arguments. */ + TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); + setstackmark(&smark); + back_exitstatus = 0; + + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.cmd = &bltin; + varlist.lastp = &varlist.list; + *varlist.lastp = NULL; + arglist.lastp = &arglist.list; + *arglist.lastp = NULL; + + argc = 0; + if (cmd->ncmd.args) + { + bcmd = find_builtin(cmd->ncmd.args->narg.text); + pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd); + } + + for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) { + struct strlist **spp; + + spp = arglist.lastp; + if (pseudovarflag && isassignment(argp->narg.text)) + expandarg(argp, &arglist, EXP_VARTILDE); + else + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + + for (sp = *spp; sp; sp = sp->next) + argc++; + } + + argv = nargv = stalloc(sizeof (char *) * (argc + 1)); + for (sp = arglist.list ; sp ; sp = sp->next) { + TRACE(("evalcommand arg: %s\n", sp->text)); + *nargv++ = sp->text; + } + *nargv = NULL; + + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = nargv[-1]; + + preverrout_fd = 2; + expredir(cmd->ncmd.redirect); + status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2); + + path = vpath.text; + for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) { + struct strlist **spp; + char *p; + + spp = varlist.lastp; + expandarg(argp, &varlist, EXP_VARTILDE); + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + p = (*spp)->text; + if (varequal(p, path)) + path = p; + } + + /* Print the command if xflag is set. */ + if (xflag) { + int n; + const char *p = " %s"; + + p++; + dprintf(preverrout_fd, p, expandstr(ps4val())); + + sp = varlist.list; + for(n = 0; n < 2; n++) { + while (sp) { + dprintf(preverrout_fd, p, sp->text); + sp = sp->next; + if(*p == '%') { + p--; + } + } + sp = arglist.list; + } + full_write(preverrout_fd, "\n", 1); + } + + cmd_is_exec = 0; + spclbltin = -1; + + /* Now locate the command. */ + if (argc) { + const char *oldpath; + int cmd_flag = DO_ERR; + + path += 5; + oldpath = path; + for (;;) { + find_command(argv[0], &cmdentry, cmd_flag, path); + if (cmdentry.cmdtype == CMDUNKNOWN) { + status = 127; + flusherr(); + goto bail; + } + + /* implement bltin and command here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + if (spclbltin < 0) + spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd); + if (cmdentry.u.cmd == EXECCMD) + cmd_is_exec++; +#ifdef CONFIG_ASH_CMDCMD + if (cmdentry.u.cmd == COMMANDCMD) { + + path = oldpath; + nargv = parse_command_args(argv, &path); + if (!nargv) + break; + argc -= nargv - argv; + argv = nargv; + cmd_flag |= DO_NOFUNC; + } else +#endif + break; + } + } + + if (status) { + /* We have a redirection error. */ + if (spclbltin > 0) + exraise(EXERROR); +bail: + exitstatus = status; + goto out; + } + + /* Execute the command. */ + switch (cmdentry.cmdtype) { + default: + /* Fork off a child process if necessary. */ + if (!(flags & EV_EXIT) || trap[0]) { + INTOFF; + jp = makejob(cmd, 1); + if (forkshell(jp, cmd, FORK_FG) != 0) { + exitstatus = waitforjob(jp); + INTON; + break; + } + FORCEINTON; + } + listsetvar(varlist.list, VEXPORT|VSTACK); + shellexec(argv, path, cmdentry.u.index); + /* NOTREACHED */ + + case CMDBUILTIN: + cmdenviron = varlist.list; + if (cmdenviron) { + struct strlist *list = cmdenviron; + int i = VNOSET; + if (spclbltin > 0 || argc == 0) { + i = 0; + if (cmd_is_exec && argc > 1) + i = VEXPORT; + } + listsetvar(list, i); + } + if (evalbltin(cmdentry.u.cmd, argc, argv)) { + int exit_status; + int i, j; + + i = exception; + if (i == EXEXIT) + goto raise; + + exit_status = 2; + j = 0; + if (i == EXINT) + j = SIGINT; + if (i == EXSIG) + j = pendingsigs; + if (j) + exit_status = j + 128; + exitstatus = exit_status; + + if (i == EXINT || spclbltin > 0) { +raise: + longjmp(handler->loc, 1); + } + FORCEINTON; + } + break; + + case CMDFUNCTION: + listsetvar(varlist.list, 0); + if (evalfun(cmdentry.u.func, argc, argv, flags)) + goto raise; + break; + } + +out: + popredir(cmd_is_exec); + if (lastarg) + /* dsl: I think this is intended to be used to support + * '_' in 'vi' command mode during line editing... + * However I implemented that within libedit itself. + */ + setvar("_", lastarg, 0); + popstackmark(&smark); +} + +static int +evalbltin(const struct builtincmd *cmd, int argc, char **argv) { + char *volatile savecmdname; + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int i; + + savecmdname = commandname; + if ((i = setjmp(jmploc.loc))) + goto cmddone; + savehandler = handler; + handler = &jmploc; + commandname = argv[0]; + argptr = argv + 1; + optptr = NULL; /* initialize nextopt */ + exitstatus = (*cmd->builtin)(argc, argv); + flushall(); +cmddone: + exitstatus |= ferror(stdout); + clearerr(stdout); + commandname = savecmdname; + exsig = 0; + handler = savehandler; + + return i; +} + +static int +evalfun(struct funcnode *func, int argc, char **argv, int flags) +{ + volatile struct shparam saveparam; + struct localvar *volatile savelocalvars; + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int e; + + saveparam = shellparam; + savelocalvars = localvars; + if ((e = setjmp(jmploc.loc))) { + goto funcdone; + } + INTOFF; + savehandler = handler; + handler = &jmploc; + localvars = NULL; + shellparam.malloc = 0; + func->count++; + funcnest++; + INTON; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; +#ifdef CONFIG_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + evaltree(&func->n, flags & EV_TESTED); +funcdone: + INTOFF; + funcnest--; + freefunc(func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + handler = savehandler; + INTON; + evalskip &= ~SKIPFUNC; + return e; +} + + +static int goodname(const char *p) +{ + return !*endofname(p); +} + +/* + * Search for a command. This is called before we fork so that the + * location of the command will be available in the parent as well as + * the child. The check for "goodname" is an overly conservative + * check that the name will not be subject to expansion. + */ + +static void +prehash(union node *n) +{ + struct cmdentry entry; + + if (n->type == NCMD && n->ncmd.args) + if (goodname(n->ncmd.args->narg.text)) + find_command(n->ncmd.args->narg.text, &entry, 0, + pathval()); +} + + + +/* + * Builtin commands. Builtin commands whose functions are closely + * tied to evaluation are implemented here. + */ + +/* + * No command given. + */ + +static int +bltincmd(int argc, char **argv) +{ + /* + * Preserve exitstatus of a previous possible redirection + * as POSIX mandates + */ + return back_exitstatus; +} + + +/* + * Handle break and continue commands. Break, continue, and return are + * all handled by setting the evalskip flag. The evaluation routines + * above all check this flag, and if it is set they start skipping + * commands rather than executing them. The variable skipcount is + * the number of loops to break/continue, or the number of function + * levels to return. (The latter is always 1.) It should probably + * be an error to break out of more loops than exist, but it isn't + * in the standard shell so we don't make it one here. + */ + +static int +breakcmd(int argc, char **argv) +{ + int n = argc > 1 ? number(argv[1]) : 1; + + if (n <= 0) + sh_error(illnum, argv[1]); + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; + } + return 0; +} + + +/* + * The return command. + */ + +static int +returncmd(int argc, char **argv) +{ + /* + * If called outside a function, do what ksh does; + * skip the rest of the file. + */ + evalskip = funcnest ? SKIPFUNC : SKIPFILE; + return argv[1] ? number(argv[1]) : exitstatus; +} + + +static int +falsecmd(int argc, char **argv) +{ + return 1; +} + + +static int +truecmd(int argc, char **argv) +{ + return 0; +} + + +static int +execcmd(int argc, char **argv) +{ + if (argc > 1) { + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + shellexec(argv + 1, pathval(), 0); + } + return 0; +} + + +/* exec.c */ + +/* + * When commands are first encountered, they are entered in a hash table. + * This ensures that a full path search will not have to be done for them + * on each invocation. + * + * We should investigate converting to a linear search, even though that + * would make the command name "hash" a misnomer. + */ + +#define CMDTABLESIZE 31 /* should be prime */ +#define ARB 1 /* actual size determined at run time */ + + + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + short cmdtype; /* index identifying command */ + char rehash; /* if set, cd done since entry created */ + char cmdname[ARB]; /* name of command */ +}; + + +static struct tblentry *cmdtable[CMDTABLESIZE]; +static int builtinloc = -1; /* index in path of %builtin, or -1 */ + + +static void tryexec(char *, char **, char **); +static void clearcmdentry(int); +static struct tblentry *cmdlookup(const char *, int); +static void delete_cmd_entry(void); + + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + */ + +static void +shellexec(char **argv, const char *path, int idx) +{ + char *cmdname; + int e; + char **envp; + int exerrno; + + clearredir(1); + envp = environment(); + if (strchr(argv[0], '/') != NULL + || is_safe_applet(argv[0]) +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + || find_applet_by_name(argv[0]) +#endif + ) { + tryexec(argv[0], argv, envp); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, argv[0])) != NULL) { + if (--idx < 0 && pathopt == NULL) { + tryexec(cmdname, argv, envp); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + } + stunalloc(cmdname); + } + } + + /* Map to POSIX errors */ + switch (e) { + case EACCES: + exerrno = 126; + break; + case ENOENT: + exerrno = 127; + break; + default: + exerrno = 2; + break; + } + exitstatus = exerrno; + TRACE(("shellexec failed for %s, errno %d, suppressint %d\n", + argv[0], e, suppressint )); + exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC)); + /* NOTREACHED */ +} + + +static void +tryexec(char *cmd, char **argv, char **envp) +{ + int repeated = 0; + struct BB_applet *a; + int argc = 0; + char **c; + + if(strchr(cmd, '/') == NULL && is_safe_applet(cmd) && (a = find_applet_by_name(cmd)) != NULL) { + c = argv; + while (*c != NULL) { + c++; argc++; + } + applet_name = cmd; + exit(a->main(argc, argv)); + } +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + if(find_applet_by_name(cmd) != NULL) { + /* re-exec ourselves with the new arguments */ + execve(CONFIG_BUSYBOX_EXEC_PATH,argv,envp); + /* If they called chroot or otherwise made the binary no longer + * executable, fall through */ + } +#endif + +repeat: +#ifdef SYSV + do { + execve(cmd, argv, envp); + } while (errno == EINTR); +#else + execve(cmd, argv, envp); +#endif + if (repeated++) { + ckfree(argv); + } else if (errno == ENOEXEC) { + char **ap; + char **new; + + for (ap = argv; *ap; ap++) + ; + ap = new = ckmalloc((ap - argv + 2) * sizeof(char *)); + ap[1] = cmd; + *ap = cmd = (char *)DEFAULT_SHELL; + ap += 2; + argv++; + while ((*ap++ = *argv++)) + ; + argv = new; + goto repeat; + } +} + + + +/* + * Do a path search. The variable path (passed by reference) should be + * set to the start of the path before the first call; padvance will update + * this value as it proceeds. Successive calls to padvance will return + * the possible path expansions in sequence. If an option (indicated by + * a percent sign) appears in the path entry then the global variable + * pathopt will be set to point to it; otherwise pathopt will be set to + * NULL. + */ + +static char * +padvance(const char **path, const char *name) +{ + const char *p; + char *q; + const char *start; + size_t len; + + if (*path == NULL) + return NULL; + start = *path; + for (p = start ; *p && *p != ':' && *p != '%' ; p++); + len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ + while (stackblocksize() < len) + growstackblock(); + q = stackblock(); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + *q++ = '/'; + } + strcpy(q, name); + pathopt = NULL; + if (*p == '%') { + pathopt = ++p; + while (*p && *p != ':') p++; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return stalloc(len); +} + + +/*** Command hashing code ***/ + +static void +printentry(struct tblentry *cmdp) +{ + int idx; + const char *path; + char *name; + + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, cmdp->cmdname); + stunalloc(name); + } while (--idx >= 0); + out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr)); +} + + +static int +hashcmd(int argc, char **argv) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + struct cmdentry entry; + char *name; + + while ((c = nextopt("r")) != '\0') { + clearcmdentry(0); + return 0; + } + if (*argptr == NULL) { + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp); + } + } + return 0; + } + c = 0; + while ((name = *argptr) != NULL) { + if ((cmdp = cmdlookup(name, 0)) != NULL + && (cmdp->cmdtype == CMDNORMAL + || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))) + delete_cmd_entry(); + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + c = 1; + argptr++; + } + return c; +} + + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ + +static void +find_command(char *name, struct cmdentry *entry, int act, const char *path) +{ + struct tblentry *cmdp; + int idx; + int prev; + char *fullname; + struct stat statb; + int e; + int updatetbl; + struct builtincmd *bcmd; + + /* If name contains a slash, don't use PATH or hash table */ + if (strchr(name, '/') != NULL) { + entry->u.index = -1; + if (act & DO_ABS) { + while (stat(name, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + entry->cmdtype = CMDUNKNOWN; + return; + } + } + entry->cmdtype = CMDNORMAL; + return; + } + +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + if (find_applet_by_name(name)) { + entry->cmdtype = CMDNORMAL; + entry->u.index = -1; + return; + } +#endif + + if (is_safe_applet(name)) { + entry->cmdtype = CMDNORMAL; + entry->u.index = -1; + return; + } + + updatetbl = (path == pathval()); + if (!updatetbl) { + act |= DO_ALTPATH; + if (strstr(path, "%builtin") != NULL) + act |= DO_ALTBLTIN; + } + + /* If name is in the table, check answer will be ok */ + if ((cmdp = cmdlookup(name, 0)) != NULL) { + int bit; + + switch (cmdp->cmdtype) { + default: +#if DEBUG + abort(); +#endif + case CMDNORMAL: + bit = DO_ALTPATH; + break; + case CMDFUNCTION: + bit = DO_NOFUNC; + break; + case CMDBUILTIN: + bit = DO_ALTBLTIN; + break; + } + if (act & bit) { + updatetbl = 0; + cmdp = NULL; + } else if (cmdp->rehash == 0) + /* if not invalidated by cd, we're done */ + goto success; + } + + /* If %builtin not in path, check for builtin next */ + bcmd = find_builtin(name); + if (bcmd && (IS_BUILTIN_REGULAR(bcmd) || ( + act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc <= 0 + ))) + goto builtin_success; + + /* We have to search path. */ + prev = -1; /* where to start */ + if (cmdp && cmdp->rehash) { /* doing a rehash */ + if (cmdp->cmdtype == CMDBUILTIN) + prev = builtinloc; + else + prev = cmdp->param.index; + } + + e = ENOENT; + idx = -1; +loop: + while ((fullname = padvance(&path, name)) != NULL) { + stunalloc(fullname); + idx++; + if (pathopt) { + if (prefix(pathopt, "builtin")) { + if (bcmd) + goto builtin_success; + continue; + } else if (!(act & DO_NOFUNC) && + prefix(pathopt, "func")) { + /* handled below */ + } else { + /* ignore unimplemented options */ + continue; + } + } + /* if rehash, don't redo absolute path names */ + if (fullname[0] == '/' && idx <= prev) { + if (idx < prev) + continue; + TRACE(("searchexec \"%s\": no change\n", name)); + goto success; + } + while (stat(fullname, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + goto loop; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + continue; + if (pathopt) { /* this is a %func directory */ + stalloc(strlen(fullname) + 1); + readcmdfile(fullname); + if ((cmdp = cmdlookup(name, 0)) == NULL || + cmdp->cmdtype != CMDFUNCTION) + sh_error("%s not defined in %s", name, fullname); + stunalloc(fullname); + goto success; + } + TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); + if (!updatetbl) { + entry->cmdtype = CMDNORMAL; + entry->u.index = idx; + return; + } + INTOFF; + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + INTON; + goto success; + } + + /* We failed. If there was an entry for this command, delete it */ + if (cmdp && updatetbl) + delete_cmd_entry(); + if (act & DO_ERR) + sh_warnx("%s: %s", name, errmsg(e, E_EXEC)); + entry->cmdtype = CMDUNKNOWN; + return; + +builtin_success: + if (!updatetbl) { + entry->cmdtype = CMDBUILTIN; + entry->u.cmd = bcmd; + return; + } + INTOFF; + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.cmd = bcmd; + INTON; +success: + cmdp->rehash = 0; + entry->cmdtype = cmdp->cmdtype; + entry->u = cmdp->param; +} + + +/* + * Wrapper around strcmp for qsort/bsearch/... + */ +static int pstrcmp(const void *a, const void *b) +{ + return strcmp((const char *) a, (*(const char *const *) b) + 1); +} + +/* + * Search the table of builtin commands. + */ + +static struct builtincmd * +find_builtin(const char *name) +{ + struct builtincmd *bp; + + bp = bsearch( + name, builtincmd, NUMBUILTINS, sizeof(struct builtincmd), + pstrcmp + ); + return bp; +} + + + +/* + * Called when a cd is done. Marks all commands so the next time they + * are executed they will be rehashed. + */ + +static void +hashcd(void) +{ + struct tblentry **pp; + struct tblentry *cmdp; + + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL || ( + cmdp->cmdtype == CMDBUILTIN && + !(IS_BUILTIN_REGULAR(cmdp->param.cmd)) && + builtinloc > 0 + )) + cmdp->rehash = 1; + } + } +} + + + +/* + * Fix command hash table when PATH changed. + * Called before PATH is changed. The argument is the new value of PATH; + * pathval() still returns the old value at this point. + * Called with interrupts off. + */ + +static void +changepath(const char *newval) +{ + const char *old, *new; + int idx; + int firstchange; + int idx_bltin; + + old = pathval(); + new = newval; + firstchange = 9999; /* assume no change */ + idx = 0; + idx_bltin = -1; + for (;;) { + if (*old != *new) { + firstchange = idx; + if ((*old == '\0' && *new == ':') + || (*old == ':' && *new == '\0')) + firstchange++; + old = new; /* ignore subsequent differences */ + } + if (*new == '\0') + break; + if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin")) + idx_bltin = idx; + if (*new == ':') { + idx++; + } + new++, old++; + } + if (builtinloc < 0 && idx_bltin >= 0) + builtinloc = idx_bltin; /* zap builtins */ + if (builtinloc >= 0 && idx_bltin < 0) + firstchange = 0; + clearcmdentry(firstchange); + builtinloc = idx_bltin; +} + + +/* + * Clear out command entries. The argument specifies the first entry in + * PATH which has changed. + */ + +static void +clearcmdentry(int firstchange) +{ + struct tblentry **tblp; + struct tblentry **pp; + struct tblentry *cmdp; + + INTOFF; + for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { + pp = tblp; + while ((cmdp = *pp) != NULL) { + if ((cmdp->cmdtype == CMDNORMAL && + cmdp->param.index >= firstchange) + || (cmdp->cmdtype == CMDBUILTIN && + builtinloc >= firstchange)) { + *pp = cmdp->next; + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + INTON; +} + + + +/* + * Locate a command in the command hash table. If "add" is nonzero, + * add the command to the table if it is not already present. The + * variable "lastcmdentry" is set to point to the address of the link + * pointing to the entry, so that delete_cmd_entry can delete the + * entry. + * + * Interrupts must be off if called with add != 0. + */ + +static struct tblentry **lastcmdentry; + + +static struct tblentry * +cmdlookup(const char *name, int add) +{ + unsigned int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + + p = name; + hashval = (unsigned char)*p << 4; + while (*p) + hashval += (unsigned char)*p++; + hashval &= 0x7FFF; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (equal(cmdp->cmdname, name)) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB + + strlen(name) + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + strcpy(cmdp->cmdname, name); + } + lastcmdentry = pp; + return cmdp; +} + +/* + * Delete the command entry returned on the last lookup. + */ + +static void +delete_cmd_entry(void) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = *lastcmdentry; + *lastcmdentry = cmdp->next; + if (cmdp->cmdtype == CMDFUNCTION) + freefunc(cmdp->param.func); + ckfree(cmdp); + INTON; +} + + +/* + * Add a new command entry, replacing any existing command entry for + * the same name - except special builtins. + */ + +static void addcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + freefunc(cmdp->param.func); + } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + cmdp->rehash = 0; +} + +/* + * Make a copy of a parse tree. + */ + +static struct funcnode * copyfunc(union node *n) +{ + struct funcnode *f; + size_t blocksize; + + funcblocksize = offsetof(struct funcnode, n); + funcstringsize = 0; + calcsize(n); + blocksize = funcblocksize; + f = ckmalloc(blocksize + funcstringsize); + funcblock = (char *) f + offsetof(struct funcnode, n); + funcstring = (char *) f + blocksize; + copynode(n); + f->count = 0; + return f; +} + +/* + * Define a shell function. + */ + +static void +defun(char *name, union node *func) +{ + struct cmdentry entry; + + INTOFF; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + addcmdentry(name, &entry); + INTON; +} + + +/* + * Delete a function if it exists. + */ + +static void +unsetfunc(const char *name) +{ + struct tblentry *cmdp; + + if ((cmdp = cmdlookup(name, 0)) != NULL && + cmdp->cmdtype == CMDFUNCTION) + delete_cmd_entry(); +} + +/* + * Locate and print what a word is... + */ + + +#ifdef CONFIG_ASH_CMDCMD +static int +describe_command(char *command, int describe_command_verbose) +#else +#define describe_command_verbose 1 +static int +describe_command(char *command) +#endif +{ + struct cmdentry entry; + struct tblentry *cmdp; +#ifdef CONFIG_ASH_ALIAS + const struct alias *ap; +#endif + const char *path = pathval(); + + if (describe_command_verbose) { + out1str(command); + } + + /* First look at the keywords */ + if (findkwd(command)) { + out1str(describe_command_verbose ? " is a shell keyword" : command); + goto out; + } + +#ifdef CONFIG_ASH_ALIAS + /* Then look at the aliases */ + if ((ap = lookupalias(command, 0)) != NULL) { + if (describe_command_verbose) { + out1fmt(" is an alias for %s", ap->val); + } else { + out1str("alias "); + printalias(ap); + return 0; + } + goto out; + } +#endif + /* Then check if it is a tracked alias */ + if ((cmdp = cmdlookup(command, 0)) != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + } else { + /* Finally use brute force */ + find_command(command, &entry, DO_ABS, path); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + int j = entry.u.index; + char *p; + if (j == -1) { + p = command; + } else { + do { + p = padvance(&path, command); + stunalloc(p); + } while (--j >= 0); + } + if (describe_command_verbose) { + out1fmt(" is%s %s", + (cmdp ? " a tracked alias for" : nullstr), p + ); + } else { + out1str(p); + } + break; + } + + case CMDFUNCTION: + if (describe_command_verbose) { + out1str(" is a shell function"); + } else { + out1str(command); + } + break; + + case CMDBUILTIN: + if (describe_command_verbose) { + out1fmt(" is a %sshell builtin", + IS_BUILTIN_SPECIAL(entry.u.cmd) ? + "special " : nullstr + ); + } else { + out1str(command); + } + break; + + default: + if (describe_command_verbose) { + out1str(": not found\n"); + } + return 127; + } + +out: + outstr("\n", stdout); + return 0; +} + +static int +typecmd(int argc, char **argv) +{ + int i; + int err = 0; + + for (i = 1; i < argc; i++) { +#ifdef CONFIG_ASH_CMDCMD + err |= describe_command(argv[i], 1); +#else + err |= describe_command(argv[i]); +#endif + } + return err; +} + +#ifdef CONFIG_ASH_CMDCMD +static int +commandcmd(int argc, char **argv) +{ + int c; + enum { + VERIFY_BRIEF = 1, + VERIFY_VERBOSE = 2, + } verify = 0; + + while ((c = nextopt("pvV")) != '\0') + if (c == 'V') + verify |= VERIFY_VERBOSE; + else if (c == 'v') + verify |= VERIFY_BRIEF; +#if DEBUG + else if (c != 'p') + abort(); +#endif + if (verify) + return describe_command(*argptr, verify - VERIFY_BRIEF); + + return 0; +} +#endif + +/* expand.c */ + +/* + * Routines to expand arguments to commands. We have to deal with + * backquotes, shell variables, and file metacharacters. + */ + +/* + * _rmescape() flags + */ +#define RMESCAPE_ALLOC 0x1 /* Allocate a new string */ +#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ +#define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */ +#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ +#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ + +/* + * Structure specifying which parts of the string should be searched + * for IFS characters. + */ + +struct ifsregion { + struct ifsregion *next; /* next region in list */ + int begoff; /* offset of start of region */ + int endoff; /* offset of end of region */ + int nulonly; /* search for nul bytes only */ +}; + +/* output of current string */ +static char *expdest; +/* list of back quote expressions */ +static struct nodelist *argbackq; +/* first struct in list of ifs regions */ +static struct ifsregion ifsfirst; +/* last struct in list */ +static struct ifsregion *ifslastp; +/* holds expanded arg list */ +static struct arglist exparg; + +static void argstr(char *, int); +static char *exptilde(char *, char *, int); +static void expbackq(union node *, int, int); +static const char *subevalvar(char *, char *, int, int, int, int, int); +static char *evalvar(char *, int); +static void strtodest(const char *, int, int); +static void memtodest(const char *p, size_t len, int syntax, int quotes); +static ssize_t varvalue(char *, int, int); +static void recordregion(int, int, int); +static void removerecordregions(int); +static void ifsbreakup(char *, struct arglist *); +static void ifsfree(void); +static void expandmeta(struct strlist *, int); +static int patmatch(char *, const char *); + +static int cvtnum(arith_t); +static size_t esclen(const char *, const char *); +static char *scanleft(char *, char *, char *, char *, int, int); +static char *scanright(char *, char *, char *, char *, int, int); +static void varunset(const char *, const char *, const char *, int) + ATTRIBUTE_NORETURN; + + +#define pmatch(a, b) !fnmatch((a), (b), 0) +/* + * Prepare a pattern for a expmeta (internal glob(3)) call. + * + * Returns an stalloced string. + */ + +static char * preglob(const char *pattern, int quoted, int flag) { + flag |= RMESCAPE_GLOB; + if (quoted) { + flag |= RMESCAPE_QUOTED; + } + return _rmescapes((char *)pattern, flag); +} + + +static size_t +esclen(const char *start, const char *p) { + size_t esc = 0; + + while (p > start && *--p == CTLESC) { + esc++; + } + return esc; +} + + +/* + * Expand shell variables and backquotes inside a here document. + */ + +static void expandhere(union node *arg, int fd) +{ + herefd = fd; + expandarg(arg, (struct arglist *)NULL, 0); + full_write(fd, stackblock(), expdest - (char *)stackblock()); +} + + +/* + * Perform variable substitution and command substitution on an argument, + * placing the resulting list of arguments in arglist. If EXP_FULL is true, + * perform splitting and file name expansion. When arglist is NULL, perform + * here document expansion. + */ + +void +expandarg(union node *arg, struct arglist *arglist, int flag) +{ + struct strlist *sp; + char *p; + + argbackq = arg->narg.backquote; + STARTSTACKSTR(expdest); + ifsfirst.next = NULL; + ifslastp = NULL; + argstr(arg->narg.text, flag); + p = _STPUTC('\0', expdest); + expdest = p - 1; + if (arglist == NULL) { + return; /* here document expanded */ + } + p = grabstackstr(p); + exparg.lastp = &exparg.list; + /* + * TODO - EXP_REDIR + */ + if (flag & EXP_FULL) { + ifsbreakup(p, &exparg); + *exparg.lastp = NULL; + exparg.lastp = &exparg.list; + expandmeta(exparg.list, flag); + } else { + if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ + rmescapes(p); + sp = (struct strlist *)stalloc(sizeof (struct strlist)); + sp->text = p; + *exparg.lastp = sp; + exparg.lastp = &sp->next; + } + if (ifsfirst.next) + ifsfree(); + *exparg.lastp = NULL; + if (exparg.list) { + *arglist->lastp = exparg.list; + arglist->lastp = exparg.lastp; + } +} + + +/* + * Perform variable and command substitution. If EXP_FULL is set, output CTLESC + * characters to allow for further processing. Otherwise treat + * $@ like $* since no splitting will be performed. + */ + +static void +argstr(char *p, int flag) +{ + static const char spclchars[] = { + '=', + ':', + CTLQUOTEMARK, + CTLENDVAR, + CTLESC, + CTLVAR, + CTLBACKQ, + CTLBACKQ | CTLQUOTE, +#ifdef CONFIG_ASH_MATH_SUPPORT + CTLENDARI, +#endif + 0 + }; + const char *reject = spclchars; + int c; + int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ + int breakall = flag & EXP_WORD; + int inquotes; + size_t length; + int startloc; + + if (!(flag & EXP_VARTILDE)) { + reject += 2; + } else if (flag & EXP_VARTILDE2) { + reject++; + } + inquotes = 0; + length = 0; + if (flag & EXP_TILDE) { + char *q; + + flag &= ~EXP_TILDE; +tilde: + q = p; + if (*q == CTLESC && (flag & EXP_QWORD)) + q++; + if (*q == '~') + p = exptilde(p, q, flag); + } +start: + startloc = expdest - (char *)stackblock(); + for (;;) { + length += strcspn(p + length, reject); + c = p[length]; + if (c && (!(c & 0x80) +#ifdef CONFIG_ASH_MATH_SUPPORT + || c == CTLENDARI +#endif + )) { + /* c == '=' || c == ':' || c == CTLENDARI */ + length++; + } + if (length > 0) { + int newloc; + expdest = stnputs(p, length, expdest); + newloc = expdest - (char *)stackblock(); + if (breakall && !inquotes && newloc > startloc) { + recordregion(startloc, newloc, 0); + } + startloc = newloc; + } + p += length + 1; + length = 0; + + switch (c) { + case '\0': + goto breakloop; + case '=': + if (flag & EXP_VARTILDE2) { + p--; + continue; + } + flag |= EXP_VARTILDE2; + reject++; + /* fall through */ + case ':': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + if (*--p == '~') { + goto tilde; + } + continue; + } + + switch (c) { + case CTLENDVAR: /* ??? */ + goto breakloop; + case CTLQUOTEMARK: + /* "$@" syntax adherence hack */ + if ( + !inquotes && + !memcmp(p, dolatstr, DOLATSTRLEN) && + (p[4] == CTLQUOTEMARK || ( + p[4] == CTLENDVAR && + p[5] == CTLQUOTEMARK + )) + ) { + p = evalvar(p + 1, flag) + 1; + goto start; + } + inquotes = !inquotes; +addquote: + if (quotes) { + p--; + length++; + startloc++; + } + break; + case CTLESC: + startloc++; + length++; + goto addquote; + case CTLVAR: + p = evalvar(p, flag); + goto start; + case CTLBACKQ: + c = 0; + case CTLBACKQ|CTLQUOTE: + expbackq(argbackq->n, c, quotes); + argbackq = argbackq->next; + goto start; +#ifdef CONFIG_ASH_MATH_SUPPORT + case CTLENDARI: + p--; + expari(quotes); + goto start; +#endif + } + } +breakloop: + ; +} + +static char * +exptilde(char *startp, char *p, int flag) +{ + char c; + char *name; + struct passwd *pw; + const char *home; + int quotes = flag & (EXP_FULL | EXP_CASE); + int startloc; + + name = p + 1; + + while ((c = *++p) != '\0') { + switch(c) { + case CTLESC: + return startp; + case CTLQUOTEMARK: + return startp; + case ':': + if (flag & EXP_VARTILDE) + goto done; + break; + case '/': + case CTLENDVAR: + goto done; + } + } +done: + *p = '\0'; + if (*name == '\0') { + home = lookupvar(homestr); + } else { + if ((pw = getpwnam(name)) == NULL) + goto lose; + home = pw->pw_dir; + } + if (!home || !*home) + goto lose; + *p = c; + startloc = expdest - (char *)stackblock(); + strtodest(home, SQSYNTAX, quotes); + recordregion(startloc, expdest - (char *)stackblock(), 0); + return p; +lose: + *p = c; + return startp; +} + + +static void +removerecordregions(int endoff) +{ + if (ifslastp == NULL) + return; + + if (ifsfirst.endoff > endoff) { + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifsfirst.next->next; + ckfree(ifsfirst.next); + ifsfirst.next = ifsp; + INTON; + } + if (ifsfirst.begoff > endoff) + ifslastp = NULL; + else { + ifslastp = &ifsfirst; + ifsfirst.endoff = endoff; + } + return; + } + + ifslastp = &ifsfirst; + while (ifslastp->next && ifslastp->next->begoff < endoff) + ifslastp=ifslastp->next; + while (ifslastp->next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifslastp->next->next; + ckfree(ifslastp->next); + ifslastp->next = ifsp; + INTON; + } + if (ifslastp->endoff > endoff) + ifslastp->endoff = endoff; +} + + +#ifdef CONFIG_ASH_MATH_SUPPORT +/* + * Expand arithmetic expression. Backup to start of expression, + * evaluate, place result in (backed up) result, adjust string position. + */ +void +expari(int quotes) +{ + char *p, *start; + int begoff; + int flag; + int len; + + /* ifsfree(); */ + + /* + * This routine is slightly over-complicated for + * efficiency. Next we scan backwards looking for the + * start of arithmetic. + */ + start = stackblock(); + p = expdest - 1; + *p = '\0'; + p--; + do { + int esc; + + while (*p != CTLARI) { + p--; +#if DEBUG + if (p < start) { + sh_error("missing CTLARI (shouldn't happen)"); + } +#endif + } + + esc = esclen(start, p); + if (!(esc % 2)) { + break; + } + + p -= esc + 1; + } while (1); + + begoff = p - start; + + removerecordregions(begoff); + + flag = p[1]; + + expdest = p; + + if (quotes) + rmescapes(p + 2); + + len = cvtnum(dash_arith(p + 2)); + + if (flag != '"') + recordregion(begoff, begoff + len, 0); +} +#endif + +/* + * Expand stuff in backwards quotes. + */ + +static void +expbackq(union node *cmd, int quoted, int quotes) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest; + int startloc; + int syntax = quoted? DQSYNTAX : BASESYNTAX; + struct stackmark smark; + + INTOFF; + setstackmark(&smark); + dest = expdest; + startloc = dest - (char *)stackblock(); + grabstackstr(dest); + evalbackcmd(cmd, (struct backcmd *) &in); + popstackmark(&smark); + + p = in.buf; + i = in.nleft; + if (i == 0) + goto read; + for (;;) { + memtodest(p, i, syntax, quotes); +read: + if (in.fd < 0) + break; + i = safe_read(in.fd, buf, sizeof buf); + TRACE(("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + } + + if (in.buf) + ckfree(in.buf); + if (in.fd >= 0) { + close(in.fd); + back_exitstatus = waitforjob(in.jp); + } + INTON; + + /* Eat all trailing newlines */ + dest = expdest; + for (; dest > (char *)stackblock() && dest[-1] == '\n';) + STUNPUTC(dest); + expdest = dest; + + if (quoted == 0) + recordregion(startloc, dest - (char *)stackblock(), 0); + TRACE(("evalbackq: size=%d: \"%.*s\"\n", + (dest - (char *)stackblock()) - startloc, + (dest - (char *)stackblock()) - startloc, + stackblock() + startloc)); +} + + +static char * +scanleft(char *startp, char *rmesc, char *rmescend, char *str, int quotes, + int zero) +{ + char *loc; + char *loc2; + char c; + + loc = startp; + loc2 = rmesc; + do { + int match; + const char *s = loc2; + c = *loc2; + if (zero) { + *loc2 = '\0'; + s = rmesc; + } + match = pmatch(str, s); + *loc2 = c; + if (match) + return loc; + if (quotes && *loc == CTLESC) + loc++; + loc++; + loc2++; + } while (c); + return 0; +} + + +static char * +scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes, + int zero) +{ + int esc = 0; + char *loc; + char *loc2; + + for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) { + int match; + char c = *loc2; + const char *s = loc2; + if (zero) { + *loc2 = '\0'; + s = rmesc; + } + match = pmatch(str, s); + *loc2 = c; + if (match) + return loc; + loc--; + if (quotes) { + if (--esc < 0) { + esc = esclen(startp, loc); + } + if (esc % 2) { + esc--; + loc--; + } + } + } + return 0; +} + +static const char * +subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int quotes) +{ + char *startp; + char *loc; + int saveherefd = herefd; + struct nodelist *saveargbackq = argbackq; + int amount; + char *rmesc, *rmescend; + int zero; + char *(*scan)(char *, char *, char *, char *, int , int); + + herefd = -1; + argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0); + STPUTC('\0', expdest); + herefd = saveherefd; + argbackq = saveargbackq; + startp = stackblock() + startloc; + + switch (subtype) { + case VSASSIGN: + setvar(str, startp, 0); + amount = startp - expdest; + STADJUST(amount, expdest); + return startp; + + case VSQUESTION: + varunset(p, str, startp, varflags); + /* NOTREACHED */ + } + + subtype -= VSTRIMRIGHT; +#if DEBUG + if (subtype < 0 || subtype > 3) + abort(); +#endif + + rmesc = startp; + rmescend = stackblock() + strloc; + if (quotes) { + rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW); + if (rmesc != startp) { + rmescend = expdest; + startp = stackblock() + startloc; + } + } + rmescend--; + str = stackblock() + strloc; + preglob(str, varflags & VSQUOTE, 0); + + /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */ + zero = subtype >> 1; + /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */ + scan = (subtype & 1) ^ zero ? scanleft : scanright; + + loc = scan(startp, rmesc, rmescend, str, quotes, zero); + if (loc) { + if (zero) { + memmove(startp, loc, str - loc); + loc = startp + (str - loc) - 1; + } + *loc = '\0'; + amount = loc - expdest; + STADJUST(amount, expdest); + } + return loc; +} + + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ +static char * +evalvar(char *p, int flag) +{ + int subtype; + int varflags; + char *var; + int patloc; + int c; + int startloc; + ssize_t varlen; + int easy; + int quotes; + int quoted; + + quotes = flag & (EXP_FULL | EXP_CASE); + varflags = *p++; + subtype = varflags & VSTYPE; + quoted = varflags & VSQUOTE; + var = p; + easy = (!quoted || (*var == '@' && shellparam.nparam)); + startloc = expdest - (char *)stackblock(); + p = strchr(p, '=') + 1; + +again: + varlen = varvalue(var, varflags, flag); + if (varflags & VSNUL) + varlen--; + + if (subtype == VSPLUS) { + varlen = -1 - varlen; + goto vsplus; + } + + if (subtype == VSMINUS) { +vsplus: + if (varlen < 0) { + argstr( + p, flag | EXP_TILDE | + (quoted ? EXP_QWORD : EXP_WORD) + ); + goto end; + } + if (easy) + goto record; + goto end; + } + + if (subtype == VSASSIGN || subtype == VSQUESTION) { + if (varlen < 0) { + if (subevalvar(p, var, 0, subtype, startloc, + varflags, 0)) { + varflags &= ~VSNUL; + /* + * Remove any recorded regions beyond + * start of variable + */ + removerecordregions(startloc); + goto again; + } + goto end; + } + if (easy) + goto record; + goto end; + } + + if (varlen < 0 && uflag) + varunset(p, var, 0, 0); + + if (subtype == VSLENGTH) { + cvtnum(varlen > 0 ? varlen : 0); + goto record; + } + + if (subtype == VSNORMAL) { + if (!easy) + goto end; +record: + recordregion(startloc, expdest - (char *)stackblock(), quoted); + goto end; + } + +#if DEBUG + switch (subtype) { + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + break; + default: + abort(); + } +#endif + + if (varlen >= 0) { + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - (char *)stackblock(); + if (subevalvar(p, NULL, patloc, subtype, + startloc, varflags, quotes) == 0) { + int amount = expdest - ( + (char *)stackblock() + patloc - 1 + ); + STADJUST(-amount, expdest); + } + /* Remove any recorded regions beyond start of variable */ + removerecordregions(startloc); + goto record; + } + +end: + if (subtype != VSNORMAL) { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + if ((c = *p++) == CTLESC) + p++; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { + if (varlen >= 0) + argbackq = argbackq->next; + } else if (c == CTLVAR) { + if ((*p++ & VSTYPE) != VSNORMAL) + nesting++; + } else if (c == CTLENDVAR) { + if (--nesting == 0) + break; + } + } + } + return p; +} + + +/* + * Put a string on the stack. + */ + +static void +memtodest(const char *p, size_t len, int syntax, int quotes) { + char *q = expdest; + + q = makestrspace(len * 2, q); + + while (len--) { + int c = SC2INT(*p++); + if (!c) + continue; + if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK)) + USTPUTC(CTLESC, q); + USTPUTC(c, q); + } + + expdest = q; +} + + +static void +strtodest(const char *p, int syntax, int quotes) +{ + memtodest(p, strlen(p), syntax, quotes); +} + + +/* + * Add the value of a specialized variable to the stack string. + */ + +static ssize_t +varvalue(char *name, int varflags, int flags) +{ + int num; + char *p; + int i; + int sep = 0; + int sepq = 0; + ssize_t len = 0; + char **ap; + int syntax; + int quoted = varflags & VSQUOTE; + int subtype = varflags & VSTYPE; + int quotes = flags & (EXP_FULL | EXP_CASE); + + if (quoted && (flags & EXP_FULL)) + sep = 1 << CHAR_BIT; + + syntax = quoted ? DQSYNTAX : BASESYNTAX; + switch (*name) { + case '$': + num = rootpid; + goto numvar; + case '?': + num = exitstatus; + goto numvar; + case '#': + num = shellparam.nparam; + goto numvar; + case '!': + num = backgndpid; + if (num == 0) + return -1; +numvar: + len = cvtnum(num); + break; + case '-': + p = makestrspace(NOPTS, expdest); + for (i = NOPTS - 1; i >= 0; i--) { + if (optlist[i]) { + USTPUTC(optletters(i), p); + len++; + } + } + expdest = p; + break; + case '@': + if (sep) + goto param; + /* fall through */ + case '*': + sep = ifsset() ? SC2INT(ifsval()[0]) : ' '; + if (quotes && (SIT(sep, syntax) == CCTL || SIT(sep, syntax) == CBACK)) + sepq = 1; +param: + if (!(ap = shellparam.p)) + return -1; + while ((p = *ap++)) { + size_t partlen; + + partlen = strlen(p); + len += partlen; + + if (!(subtype == VSPLUS || subtype == VSLENGTH)) + memtodest(p, partlen, syntax, quotes); + + if (*ap && sep) { + char *q; + + len++; + if (subtype == VSPLUS || subtype == VSLENGTH) { + continue; + } + q = expdest; + if (sepq) + STPUTC(CTLESC, q); + STPUTC(sep, q); + expdest = q; + } + } + return len; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + num = atoi(name); + if (num < 0 || num > shellparam.nparam) + return -1; + p = num ? shellparam.p[num - 1] : arg0; + goto value; + default: + p = lookupvar(name); +value: + if (!p) + return -1; + + len = strlen(p); + if (!(subtype == VSPLUS || subtype == VSLENGTH)) + memtodest(p, len, syntax, quotes); + return len; + } + + if (subtype == VSPLUS || subtype == VSLENGTH) + STADJUST(-len, expdest); + return len; +} + + +/* + * Record the fact that we have to scan this region of the + * string for IFS characters. + */ + +static void +recordregion(int start, int end, int nulonly) +{ + struct ifsregion *ifsp; + + if (ifslastp == NULL) { + ifsp = &ifsfirst; + } else { + INTOFF; + ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion)); + ifsp->next = NULL; + ifslastp->next = ifsp; + INTON; + } + ifslastp = ifsp; + ifslastp->begoff = start; + ifslastp->endoff = end; + ifslastp->nulonly = nulonly; +} + + +/* + * Break the argument string into pieces based upon IFS and add the + * strings to the argument list. The regions of the string to be + * searched for IFS characters have been stored by recordregion. + */ +static void +ifsbreakup(char *string, struct arglist *arglist) +{ + struct ifsregion *ifsp; + struct strlist *sp; + char *start; + char *p; + char *q; + const char *ifs, *realifs; + int ifsspc; + int nulonly; + + + start = string; + if (ifslastp != NULL) { + ifsspc = 0; + nulonly = 0; + realifs = ifsset() ? ifsval() : defifs; + ifsp = &ifsfirst; + do { + p = string + ifsp->begoff; + nulonly = ifsp->nulonly; + ifs = nulonly ? nullstr : realifs; + ifsspc = 0; + while (p < string + ifsp->endoff) { + q = p; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p)) { + if (!nulonly) + ifsspc = (strchr(defifs, *p) != NULL); + /* Ignore IFS whitespace at start */ + if (q == start && ifsspc) { + p++; + start = p; + continue; + } + *q = '\0'; + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + p++; + if (!nulonly) { + for (;;) { + if (p >= string + ifsp->endoff) { + break; + } + q = p; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p) == NULL ) { + p = q; + break; + } else if (strchr(defifs, *p) == NULL) { + if (ifsspc) { + p++; + ifsspc = 0; + } else { + p = q; + break; + } + } else + p++; + } + } + start = p; + } else + p++; + } + } while ((ifsp = ifsp->next) != NULL); + if (nulonly) + goto add; + } + + if (!*start) + return; + +add: + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; +} + +static void +ifsfree(void) +{ + struct ifsregion *p; + + INTOFF; + p = ifsfirst.next; + do { + struct ifsregion *ifsp; + ifsp = p->next; + ckfree(p); + p = ifsp; + } while (p); + ifslastp = NULL; + ifsfirst.next = NULL; + INTON; +} + +static void expmeta(char *, char *); +static struct strlist *expsort(struct strlist *); +static struct strlist *msort(struct strlist *, int); + +static char *expdir; + + +static void +expandmeta(struct strlist *str, int flag) +{ + static const char metachars[] = { + '*', '?', '[', 0 + }; + /* TODO - EXP_REDIR */ + + while (str) { + struct strlist **savelastp; + struct strlist *sp; + char *p; + + if (fflag) + goto nometa; + if (!strpbrk(str->text, metachars)) + goto nometa; + savelastp = exparg.lastp; + + INTOFF; + p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP); + { + int i = strlen(str->text); + expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */ + } + + expmeta(expdir, p); + ckfree(expdir); + if (p != str->text) + ckfree(p); + INTON; + if (exparg.lastp == savelastp) { + /* + * no matches + */ +nometa: + *exparg.lastp = str; + rmescapes(str->text); + exparg.lastp = &str->next; + } else { + *exparg.lastp = NULL; + *savelastp = sp = expsort(*savelastp); + while (sp->next != NULL) + sp = sp->next; + exparg.lastp = &sp->next; + } + str = str->next; + } +} + +/* + * Add a file name to the list. + */ + +static void +addfname(const char *name) +{ + struct strlist *sp; + + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = sstrdup(name); + *exparg.lastp = sp; + exparg.lastp = &sp->next; +} + + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ + +static void +expmeta(char *enddir, char *name) +{ + char *p; + const char *cp; + char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; + + metaflag = 0; + start = name; + for (p = name; *p; p++) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + char *q = p + 1; + if (*q == '!') + q++; + for (;;) { + if (*q == '\\') + q++; + if (*q == '/' || *q == '\0') + break; + if (*++q == ']') { + metaflag = 1; + break; + } + } + } else if (*p == '\\') + p++; + else if (*p == '/') { + if (metaflag) + goto out; + start = p + 1; + } + } +out: + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + p = name; + do { + if (*p == '\\') + p++; + *enddir++ = *p; + } while (*p++); + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + addfname(expdir); + return; + } + endname = p; + if (name < start) { + p = name; + do { + if (*p == '\\') + p++; + *enddir++ = *p++; + } while (p < start); + } + if (enddir == expdir) { + cp = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + cp = "/"; + } else { + cp = expdir; + enddir[-1] = '\0'; + } + if ((dirp = opendir(cp)) == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname++ = '\0'; + } + matchdot = 0; + p = start; + if (*p == '\\') + p++; + if (*p == '.') + matchdot++; + while (! intpending && (dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && ! matchdot) + continue; + if (pmatch(start, dp->d_name)) { + if (atend) { + scopy(dp->d_name, enddir); + addfname(expdir); + } else { + for (p = enddir, cp = dp->d_name; + (*p++ = *cp++) != '\0';) + continue; + p[-1] = '/'; + expmeta(p, endname); + } + } + } + closedir(dirp); + if (! atend) + endname[-1] = '/'; +} + +/* + * Sort the results of file name expansion. It calculates the number of + * strings to sort and then calls msort (short for merge sort) to do the + * work. + */ + +static struct strlist * +expsort(struct strlist *str) +{ + int len; + struct strlist *sp; + + len = 0; + for (sp = str ; sp ; sp = sp->next) + len++; + return msort(str, len); +} + + +static struct strlist * +msort(struct strlist *list, int len) +{ + struct strlist *p, *q = NULL; + struct strlist **lpp; + int half; + int n; + + if (len <= 1) + return list; + half = len >> 1; + p = list; + for (n = half ; --n >= 0 ; ) { + q = p; + p = p->next; + } + q->next = NULL; /* terminate first half of list */ + q = msort(list, half); /* sort first half of list */ + p = msort(p, len - half); /* sort second half */ + lpp = &list; + for (;;) { +#ifdef CONFIG_LOCALE_SUPPORT + if (strcoll(p->text, q->text) < 0) +#else + if (strcmp(p->text, q->text) < 0) +#endif + { + *lpp = p; + lpp = &p->next; + if ((p = *lpp) == NULL) { + *lpp = q; + break; + } + } else { + *lpp = q; + lpp = &q->next; + if ((q = *lpp) == NULL) { + *lpp = p; + break; + } + } + } + return list; +} + + +/* + * Returns true if the pattern matches the string. + */ + +static int patmatch(char *pattern, const char *string) +{ + return pmatch(preglob(pattern, 0, 0), string); +} + + +/* + * Remove any CTLESC characters from a string. + */ + +static char * +_rmescapes(char *str, int flag) +{ + char *p, *q, *r; + static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 }; + unsigned inquotes; + int notescaped; + int globbing; + + p = strpbrk(str, qchars); + if (!p) { + return str; + } + q = p; + r = str; + if (flag & RMESCAPE_ALLOC) { + size_t len = p - str; + size_t fulllen = len + strlen(p) + 1; + + if (flag & RMESCAPE_GROW) { + r = makestrspace(fulllen, expdest); + } else if (flag & RMESCAPE_HEAP) { + r = ckmalloc(fulllen); + } else { + r = stalloc(fulllen); + } + q = r; + if (len > 0) { + q = mempcpy(q, str, len); + } + } + inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED; + globbing = flag & RMESCAPE_GLOB; + notescaped = globbing; + while (*p) { + if (*p == CTLQUOTEMARK) { + inquotes = ~inquotes; + p++; + notescaped = globbing; + continue; + } + if (*p == '\\') { + /* naked back slash */ + notescaped = 0; + goto copy; + } + if (*p == CTLESC) { + p++; + if (notescaped && inquotes && *p != '/') { + *q++ = '\\'; + } + } + notescaped = globbing; +copy: + *q++ = *p++; + } + *q = '\0'; + if (flag & RMESCAPE_GROW) { + expdest = r; + STADJUST(q - r + 1, expdest); + } + return r; +} + + +/* + * See if a pattern matches in a case statement. + */ + +int +casematch(union node *pattern, char *val) +{ + struct stackmark smark; + int result; + + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + ifslastp = NULL; + argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); + STACKSTRNUL(expdest); + result = patmatch(stackblock(), val); + popstackmark(&smark); + return result; +} + +/* + * Our own itoa(). + */ + +static int +cvtnum(arith_t num) +{ + int len; + + expdest = makestrspace(32, expdest); +#ifdef CONFIG_ASH_MATH_SUPPORT_64 + len = fmtstr(expdest, 32, "%lld", (long long) num); +#else + len = fmtstr(expdest, 32, "%ld", num); +#endif + STADJUST(len, expdest); + return len; +} + +static void +varunset(const char *end, const char *var, const char *umsg, int varflags) +{ + const char *msg; + const char *tail; + + tail = nullstr; + msg = "parameter not set"; + if (umsg) { + if (*end == CTLENDVAR) { + if (varflags & VSNUL) + tail = " or null"; + } else + msg = umsg; + } + sh_error("%.*s: %s%s", end - var - 1, var, msg, tail); +} + + +/* input.c */ + +/* + * This implements the input routines used by the parser. + */ + +#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ + +static void pushfile(void); + +/* + * Read a character from the script, returning PEOF on end of file. + * Nul characters in the input are silently discarded. + */ + + +#define pgetc_as_macro() (--parsenleft >= 0? SC2INT(*parsenextc++) : preadbuffer()) + +#ifdef CONFIG_ASH_OPTIMIZE_FOR_SIZE +#define pgetc_macro() pgetc() +static int +pgetc(void) +{ + return pgetc_as_macro(); +} +#else +#define pgetc_macro() pgetc_as_macro() +static int +pgetc(void) +{ + return pgetc_macro(); +} +#endif + + +/* + * Same as pgetc(), but ignores PEOA. + */ +#ifdef CONFIG_ASH_ALIAS +static int pgetc2(void) +{ + int c; + + do { + c = pgetc_macro(); + } while (c == PEOA); + return c; +} +#else +static int pgetc2(void) +{ + return pgetc_macro(); +} +#endif + +/* + * Read a line from the script. + */ + +static char * pfgets(char *line, int len) +{ + char *p = line; + int nleft = len; + int c; + + while (--nleft > 0) { + c = pgetc2(); + if (c == PEOF) { + if (p == line) + return NULL; + break; + } + *p++ = c; + if (c == '\n') + break; + } + *p = '\0'; + return line; +} + + + +#ifdef CONFIG_FEATURE_COMMAND_EDITING +#ifdef CONFIG_ASH_EXPAND_PRMT +static char *cmdedit_prompt; +#else +static const char *cmdedit_prompt; +#endif +static void putprompt(const char *s) +{ +#ifdef CONFIG_ASH_EXPAND_PRMT + free(cmdedit_prompt); + cmdedit_prompt = xstrdup(s); +#else + cmdedit_prompt = s; +#endif +} +#else +static void putprompt(const char *s) +{ + out2str(s); +} +#endif + +static int preadfd(void) +{ + int nr; + char *buf = parsefile->buf; + parsenextc = buf; + +retry: +#ifdef CONFIG_FEATURE_COMMAND_EDITING + if (!iflag || parsefile->fd) + nr = safe_read(parsefile->fd, buf, BUFSIZ - 1); + else { +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + cmdedit_path_lookup = pathval(); +#endif + nr = cmdedit_read_input((char *) cmdedit_prompt, buf); + if(nr == 0) { + /* Ctrl+C presend */ + if(trap[SIGINT]) { + buf[0] = '\n'; + buf[1] = 0; + raise(SIGINT); + return 1; + } + goto retry; + } + if(nr < 0 && errno == 0) { + /* Ctrl+D presend */ + nr = 0; + } + } +#else + nr = safe_read(parsefile->fd, buf, BUFSIZ - 1); +#endif + + if (nr < 0) { + if (parsefile->fd == 0 && errno == EWOULDBLOCK) { + int flags = fcntl(0, F_GETFL, 0); + if (flags >= 0 && flags & O_NONBLOCK) { + flags &=~ O_NONBLOCK; + if (fcntl(0, F_SETFL, flags) >= 0) { + out2str("sh: turning off NDELAY mode\n"); + goto retry; + } + } + } + } + return nr; +} + +/* + * Refill the input buffer and return the next input character: + * + * 1) If a string was pushed back on the input, pop it; + * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading + * from a string so we can't refill the buffer, return EOF. + * 3) If the is more stuff in this buffer, use it else call read to fill it. + * 4) Process input up to the next newline, deleting nul characters. + */ + +int +preadbuffer(void) +{ + char *q; + int more; + char savec; + + while (parsefile->strpush) { +#ifdef CONFIG_ASH_ALIAS + if (parsenleft == -1 && parsefile->strpush->ap && + parsenextc[-1] != ' ' && parsenextc[-1] != '\t') { + return PEOA; + } +#endif + popstring(); + if (--parsenleft >= 0) + return SC2INT(*parsenextc++); + } + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return PEOF; + flushall(); + + more = parselleft; + if (more <= 0) { +again: + if ((more = preadfd()) <= 0) { + parselleft = parsenleft = EOF_NLEFT; + return PEOF; + } + } + + q = parsenextc; + + /* delete nul characters */ + for (;;) { + int c; + + more--; + c = *q; + + if (!c) + memmove(q, q + 1, more); + else { + q++; + if (c == '\n') { + parsenleft = q - parsenextc - 1; + break; + } + } + + if (more <= 0) { + parsenleft = q - parsenextc - 1; + if (parsenleft < 0) + goto again; + break; + } + } + parselleft = more; + + savec = *q; + *q = '\0'; + + if (vflag) { + out2str(parsenextc); + } + + *q = savec; + + return SC2INT(*parsenextc++); +} + +/* + * Undo the last call to pgetc. Only one character may be pushed back. + * PEOF may be pushed back. + */ + +void +pungetc(void) +{ + parsenleft++; + parsenextc--; +} + +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ +void +pushstring(char *s, void *ap) +{ + struct strpush *sp; + size_t len; + + len = strlen(s); + INTOFF; +/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/ + if (parsefile->strpush) { + sp = ckmalloc(sizeof (struct strpush)); + sp->prev = parsefile->strpush; + parsefile->strpush = sp; + } else + sp = parsefile->strpush = &(parsefile->basestrpush); + sp->prevstring = parsenextc; + sp->prevnleft = parsenleft; +#ifdef CONFIG_ASH_ALIAS + sp->ap = (struct alias *)ap; + if (ap) { + ((struct alias *)ap)->flag |= ALIASINUSE; + sp->string = s; + } +#endif + parsenextc = s; + parsenleft = len; + INTON; +} + +void +popstring(void) +{ + struct strpush *sp = parsefile->strpush; + + INTOFF; +#ifdef CONFIG_ASH_ALIAS + if (sp->ap) { + if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') { + checkkwd |= CHKALIAS; + } + if (sp->string != sp->ap->val) { + ckfree(sp->string); + } + sp->ap->flag &= ~ALIASINUSE; + if (sp->ap->flag & ALIASDEAD) { + unalias(sp->ap->name); + } + } +#endif + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; +/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/ + parsefile->strpush = sp->prev; + if (sp != &(parsefile->basestrpush)) + ckfree(sp); + INTON; +} + +/* + * Set the input to take input from a file. If push is set, push the + * old input onto the stack first. + */ + +static int +setinputfile(const char *fname, int flags) +{ + int fd; + int fd2; + + INTOFF; + if ((fd = open(fname, O_RDONLY)) < 0) { + if (flags & INPUT_NOFILE_OK) + goto out; + sh_error("Can't open %s", fname); + } + if (fd < 10) { + fd2 = copyfd(fd, 10); + close(fd); + if (fd2 < 0) + sh_error("Out of file descriptors"); + fd = fd2; + } + setinputfd(fd, flags & INPUT_PUSH_FILE); +out: + INTON; + return fd; +} + + +/* + * Like setinputfile, but takes an open file descriptor. Call this with + * interrupts off. + */ + +static void +setinputfd(int fd, int push) +{ + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); + if (push) { + pushfile(); + parsefile->buf = 0; + } + parsefile->fd = fd; + if (parsefile->buf == NULL) + parsefile->buf = ckmalloc(IBUFSIZ); + parselleft = parsenleft = 0; + plinno = 1; +} + + +/* + * Like setinputfile, but takes input from a string. + */ + +static void +setinputstring(char *string) +{ + INTOFF; + pushfile(); + parsenextc = string; + parsenleft = strlen(string); + parsefile->buf = NULL; + plinno = 1; + INTON; +} + + +/* + * To handle the "." command, a stack of input files is used. Pushfile + * adds a new entry to the stack and popfile restores the previous level. + */ + +static void +pushfile(void) +{ + struct parsefile *pf; + + parsefile->nleft = parsenleft; + parsefile->lleft = parselleft; + parsefile->nextc = parsenextc; + parsefile->linno = plinno; + pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile)); + pf->prev = parsefile; + pf->fd = -1; + pf->strpush = NULL; + pf->basestrpush.prev = NULL; + parsefile = pf; +} + + +static void +popfile(void) +{ + struct parsefile *pf = parsefile; + + INTOFF; + if (pf->fd >= 0) + close(pf->fd); + if (pf->buf) + ckfree(pf->buf); + while (pf->strpush) + popstring(); + parsefile = pf->prev; + ckfree(pf); + parsenleft = parsefile->nleft; + parselleft = parsefile->lleft; + parsenextc = parsefile->nextc; + plinno = parsefile->linno; + INTON; +} + + +/* + * Return to top level. + */ + +static void +popallfiles(void) +{ + while (parsefile != &basepf) + popfile(); +} + + +/* + * Close the file(s) that the shell is reading commands from. Called + * after a fork is done. + */ + +static void +closescript(void) +{ + popallfiles(); + if (parsefile->fd > 0) { + close(parsefile->fd); + parsefile->fd = 0; + } +} + +/* jobs.c */ + +/* mode flags for set_curjob */ +#define CUR_DELETE 2 +#define CUR_RUNNING 1 +#define CUR_STOPPED 0 + +/* mode flags for dowait */ +#define DOWAIT_NORMAL 0 +#define DOWAIT_BLOCK 1 + +/* array of jobs */ +static struct job *jobtab; +/* size of array */ +static unsigned njobs; +#if JOBS +/* pgrp of shell on invocation */ +static int initialpgrp; +static int ttyfd = -1; +#endif +/* current job */ +static struct job *curjob; +/* number of presumed living untracked jobs */ +static int jobless; + +static void set_curjob(struct job *, unsigned); +#if JOBS +static int restartjob(struct job *, int); +static void xtcsetpgrp(int, pid_t); +static char *commandtext(union node *); +static void cmdlist(union node *, int); +static void cmdtxt(union node *); +static void cmdputs(const char *); +static void showpipe(struct job *, FILE *); +#endif +static int sprint_status(char *, int, int); +static void freejob(struct job *); +static struct job *getjob(const char *, int); +static struct job *growjobtab(void); +static void forkchild(struct job *, union node *, int); +static void forkparent(struct job *, union node *, int, pid_t); +static int dowait(int, struct job *); +static int getstatus(struct job *); + +static void +set_curjob(struct job *jp, unsigned mode) +{ + struct job *jp1; + struct job **jpp, **curp; + + /* first remove from list */ + jpp = curp = &curjob; + do { + jp1 = *jpp; + if (jp1 == jp) + break; + jpp = &jp1->prev_job; + } while (1); + *jpp = jp1->prev_job; + + /* Then re-insert in correct position */ + jpp = curp; + switch (mode) { + default: +#if DEBUG + abort(); +#endif + case CUR_DELETE: + /* job being deleted */ + break; + case CUR_RUNNING: + /* newly created job or backgrounded job, + put after all stopped jobs. */ + do { + jp1 = *jpp; +#if JOBS + if (!jp1 || jp1->state != JOBSTOPPED) +#endif + break; + jpp = &jp1->prev_job; + } while (1); + /* FALLTHROUGH */ +#if JOBS + case CUR_STOPPED: +#endif + /* newly stopped job - becomes curjob */ + jp->prev_job = *jpp; + *jpp = jp; + break; + } +} + +#if JOBS +/* + * Turn job control on and off. + * + * Note: This code assumes that the third arg to ioctl is a character + * pointer, which is true on Berkeley systems but not System V. Since + * System V doesn't have job control yet, this isn't a problem now. + * + * Called with interrupts off. + */ + +void +setjobctl(int on) +{ + int fd; + int pgrp; + + if (on == jobctl || rootshell == 0) + return; + if (on) { + int ofd; + ofd = fd = open(_PATH_TTY, O_RDWR); + if (fd < 0) { + fd += 3; + while (!isatty(fd) && --fd >= 0) + ; + } + fd = fcntl(fd, F_DUPFD, 10); + close(ofd); + if (fd < 0) + goto out; + fcntl(fd, F_SETFD, FD_CLOEXEC); + do { /* while we are in the background */ + if ((pgrp = tcgetpgrp(fd)) < 0) { +out: + sh_warnx("can't access tty; job control turned off"); + mflag = on = 0; + goto close; + } + if (pgrp == getpgrp()) + break; + killpg(0, SIGTTIN); + } while (1); + initialpgrp = pgrp; + + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + pgrp = rootpid; + setpgid(0, pgrp); + xtcsetpgrp(fd, pgrp); + } else { + /* turning job control off */ + fd = ttyfd; + pgrp = initialpgrp; + xtcsetpgrp(fd, pgrp); + setpgid(0, pgrp); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); +close: + close(fd); + fd = -1; + } + ttyfd = fd; + jobctl = on; +} + +static int +killcmd(int argc, char **argv) +{ + int signo = -1; + int list = 0; + int i; + pid_t pid; + struct job *jp; + + if (argc <= 1) { +usage: + sh_error( +"Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n" +"kill -l [exitstatus]" + ); + } + + if (**++argv == '-') { + signo = get_signum(*argv + 1); + if (signo < 0) { + int c; + + while ((c = nextopt("ls:")) != '\0') + switch (c) { + default: +#if DEBUG + abort(); +#endif + case 'l': + list = 1; + break; + case 's': + signo = get_signum(optionarg); + if (signo < 0) { + sh_error( + "invalid signal number or name: %s", + optionarg + ); + } + break; + } + argv = argptr; + } else + argv++; + } + + if (!list && signo < 0) + signo = SIGTERM; + + if ((signo < 0 || !*argv) ^ list) { + goto usage; + } + + if (list) { + const char *name; + + if (!*argv) { + for (i = 1; i < NSIG; i++) { + name = get_signame(i); + if (isdigit(*name)) + out1fmt(snlfmt, name); + } + return 0; + } + name = get_signame(signo); + if (isdigit(*name)) + out1fmt(snlfmt, name); + else + sh_error("invalid signal number or exit status: %s", *argptr); + return 0; + } + + i = 0; + do { + if (**argv == '%') { + jp = getjob(*argv, 0); + pid = -jp->ps[0].pid; + } else { + pid = **argv == '-' ? + -number(*argv + 1) : number(*argv); + } + if (kill(pid, signo) != 0) { + sh_warnx("(%d) - %m", pid); + i = 1; + } + } while (*++argv); + + return i; +} +#endif /* JOBS */ + +#if defined(JOBS) || DEBUG +static int +jobno(const struct job *jp) +{ + return jp - jobtab + 1; +} +#endif + +#if JOBS +static int +fgcmd(int argc, char **argv) +{ + struct job *jp; + FILE *out; + int mode; + int retval; + + mode = (**argv == 'f') ? FORK_FG : FORK_BG; + nextopt(nullstr); + argv = argptr; + out = stdout; + do { + jp = getjob(*argv, 1); + if (mode == FORK_BG) { + set_curjob(jp, CUR_RUNNING); + fprintf(out, "[%d] ", jobno(jp)); + } + outstr(jp->ps->cmd, out); + showpipe(jp, out); + retval = restartjob(jp, mode); + } while (*argv && *++argv); + return retval; +} + +static int bgcmd(int, char **) __attribute__((__alias__("fgcmd"))); + + +static int +restartjob(struct job *jp, int mode) +{ + struct procstat *ps; + int i; + int status; + pid_t pgid; + + INTOFF; + if (jp->state == JOBDONE) + goto out; + jp->state = JOBRUNNING; + pgid = jp->ps->pid; + if (mode == FORK_FG) + xtcsetpgrp(ttyfd, pgid); + killpg(pgid, SIGCONT); + ps = jp->ps; + i = jp->nprocs; + do { + if (WIFSTOPPED(ps->status)) { + ps->status = -1; + } + } while (ps++, --i); +out: + status = (mode == FORK_FG) ? waitforjob(jp) : 0; + INTON; + return status; +} +#endif + +static int +sprint_status(char *s, int status, int sigonly) +{ + int col; + int st; + + col = 0; + if (!WIFEXITED(status)) { +#if JOBS + if (WIFSTOPPED(status)) + st = WSTOPSIG(status); + else +#endif + st = WTERMSIG(status); + if (sigonly) { + if (st == SIGINT || st == SIGPIPE) + goto out; +#if JOBS + if (WIFSTOPPED(status)) + goto out; +#endif + } + st &= 0x7f; + col = fmtstr(s, 32, strsignal(st)); + if (WCOREDUMP(status)) { + col += fmtstr(s + col, 16, " (core dumped)"); + } + } else if (!sigonly) { + st = WEXITSTATUS(status); + if (st) + col = fmtstr(s, 16, "Done(%d)", st); + else + col = fmtstr(s, 16, "Done"); + } + +out: + return col; +} + +#if JOBS +static void +showjob(FILE *out, struct job *jp, int mode) +{ + struct procstat *ps; + struct procstat *psend; + int col; + int indent; + char s[80]; + + ps = jp->ps; + + if (mode & SHOW_PGID) { + /* just output process (group) id of pipeline */ + fprintf(out, "%d\n", ps->pid); + return; + } + + col = fmtstr(s, 16, "[%d] ", jobno(jp)); + indent = col; + + if (jp == curjob) + s[col - 2] = '+'; + else if (curjob && jp == curjob->prev_job) + s[col - 2] = '-'; + + if (mode & SHOW_PID) + col += fmtstr(s + col, 16, "%d ", ps->pid); + + psend = ps + jp->nprocs; + + if (jp->state == JOBRUNNING) { + scopy("Running", s + col); + col += strlen("Running"); + } else { + int status = psend[-1].status; +#if JOBS + if (jp->state == JOBSTOPPED) + status = jp->stopstatus; +#endif + col += sprint_status(s + col, status, 0); + } + + goto start; + + do { + /* for each process */ + col = fmtstr(s, 48, " |\n%*c%d ", indent, ' ', ps->pid) - 3; + +start: + fprintf(out, "%s%*c%s", + s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd + ); + if (!(mode & SHOW_PID)) { + showpipe(jp, out); + break; + } + if (++ps == psend) { + outcslow('\n', out); + break; + } + } while (1); + + jp->changed = 0; + + if (jp->state == JOBDONE) { + TRACE(("showjob: freeing job %d\n", jobno(jp))); + freejob(jp); + } +} + + +static int +jobscmd(int argc, char **argv) +{ + int mode, m; + FILE *out; + + mode = 0; + while ((m = nextopt("lp"))) + if (m == 'l') + mode = SHOW_PID; + else + mode = SHOW_PGID; + + out = stdout; + argv = argptr; + if (*argv) + do + showjob(out, getjob(*argv,0), mode); + while (*++argv); + else + showjobs(out, mode); + + return 0; +} + + +/* + * Print a list of jobs. If "change" is nonzero, only print jobs whose + * statuses have changed since the last call to showjobs. + */ + +static void +showjobs(FILE *out, int mode) +{ + struct job *jp; + + TRACE(("showjobs(%x) called\n", mode)); + + /* If not even one one job changed, there is nothing to do */ + while (dowait(DOWAIT_NORMAL, NULL) > 0) + continue; + + for (jp = curjob; jp; jp = jp->prev_job) { + if (!(mode & SHOW_CHANGED) || jp->changed) + showjob(out, jp, mode); + } +} +#endif /* JOBS */ + +/* + * Mark a job structure as unused. + */ + +static void +freejob(struct job *jp) +{ + struct procstat *ps; + int i; + + INTOFF; + for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) { + if (ps->cmd != nullstr) + ckfree(ps->cmd); + } + if (jp->ps != &jp->ps0) + ckfree(jp->ps); + jp->used = 0; + set_curjob(jp, CUR_DELETE); + INTON; +} + + +static int +waitcmd(int argc, char **argv) +{ + struct job *job; + int retval; + struct job *jp; + + EXSIGON(); + + nextopt(nullstr); + retval = 0; + + argv = argptr; + if (!*argv) { + /* wait for all jobs */ + for (;;) { + jp = curjob; + while (1) { + if (!jp) { + /* no running procs */ + goto out; + } + if (jp->state == JOBRUNNING) + break; + jp->waited = 1; + jp = jp->prev_job; + } + dowait(DOWAIT_BLOCK, 0); + } + } + + retval = 127; + do { + if (**argv != '%') { + pid_t pid = number(*argv); + job = curjob; + goto start; + do { + if (job->ps[job->nprocs - 1].pid == pid) + break; + job = job->prev_job; +start: + if (!job) + goto repeat; + } while (1); + } else + job = getjob(*argv, 0); + /* loop until process terminated or stopped */ + while (job->state == JOBRUNNING) + dowait(DOWAIT_BLOCK, 0); + job->waited = 1; + retval = getstatus(job); +repeat: + ; + } while (*++argv); + +out: + return retval; +} + + +/* + * Convert a job name to a job structure. + */ + +static struct job * +getjob(const char *name, int getctl) +{ + struct job *jp; + struct job *found; + const char *err_msg = "No such job: %s"; + unsigned num; + int c; + const char *p; + char *(*match)(const char *, const char *); + + jp = curjob; + p = name; + if (!p) + goto currentjob; + + if (*p != '%') + goto err; + + c = *++p; + if (!c) + goto currentjob; + + if (!p[1]) { + if (c == '+' || c == '%') { +currentjob: + err_msg = "No current job"; + goto check; + } else if (c == '-') { + if (jp) + jp = jp->prev_job; + err_msg = "No previous job"; +check: + if (!jp) + goto err; + goto gotit; + } + } + + if (is_number(p)) { + num = atoi(p); + if (num < njobs) { + jp = jobtab + num - 1; + if (jp->used) + goto gotit; + goto err; + } + } + + match = prefix; + if (*p == '?') { + match = strstr; + p++; + } + + found = 0; + while (1) { + if (!jp) + goto err; + if (match(jp->ps[0].cmd, p)) { + if (found) + goto err; + found = jp; + err_msg = "%s: ambiguous"; + } + jp = jp->prev_job; + } + +gotit: +#if JOBS + err_msg = "job %s not created under job control"; + if (getctl && jp->jobctl == 0) + goto err; +#endif + return jp; +err: + sh_error(err_msg, name); +} + + +/* + * Return a new job structure. + * Called with interrupts off. + */ + +static struct job * +makejob(union node *node, int nprocs) +{ + int i; + struct job *jp; + + for (i = njobs, jp = jobtab ; ; jp++) { + if (--i < 0) { + jp = growjobtab(); + break; + } + if (jp->used == 0) + break; + if (jp->state != JOBDONE || !jp->waited) + continue; +#if JOBS + if (jobctl) + continue; +#endif + freejob(jp); + break; + } + memset(jp, 0, sizeof(*jp)); +#if JOBS + if (jobctl) + jp->jobctl = 1; +#endif + jp->prev_job = curjob; + curjob = jp; + jp->used = 1; + jp->ps = &jp->ps0; + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); + } + TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs, + jobno(jp))); + return jp; +} + +static struct job * +growjobtab(void) +{ + size_t len; + ptrdiff_t offset; + struct job *jp, *jq; + + len = njobs * sizeof(*jp); + jq = jobtab; + jp = ckrealloc(jq, len + 4 * sizeof(*jp)); + + offset = (char *)jp - (char *)jq; + if (offset) { + /* Relocate pointers */ + size_t l = len; + + jq = (struct job *)((char *)jq + l); + while (l) { + l -= sizeof(*jp); + jq--; +#define joff(p) ((struct job *)((char *)(p) + l)) +#define jmove(p) (p) = (void *)((char *)(p) + offset) + if (xlikely(joff(jp)->ps == &jq->ps0)) + jmove(joff(jp)->ps); + if (joff(jp)->prev_job) + jmove(joff(jp)->prev_job); + } + if (curjob) + jmove(curjob); +#undef joff +#undef jmove + } + + njobs += 4; + jobtab = jp; + jp = (struct job *)((char *)jp + len); + jq = jp + 3; + do { + jq->used = 0; + } while (--jq >= jp); + return jp; +} + + +/* + * Fork off a subshell. If we are doing job control, give the subshell its + * own process group. Jp is a job structure that the job is to be added to. + * N is the command that will be evaluated by the child. Both jp and n may + * be NULL. The mode parameter can be one of the following: + * FORK_FG - Fork off a foreground process. + * FORK_BG - Fork off a background process. + * FORK_NOJOB - Like FORK_FG, but don't give the process its own + * process group even if job control is on. + * + * When job control is turned off, background processes have their standard + * input redirected to /dev/null (except for the second and later processes + * in a pipeline). + * + * Called with interrupts off. + */ + +static void forkchild(struct job *jp, union node *n, int mode) +{ + int oldlvl; + + TRACE(("Child shell %d\n", getpid())); + oldlvl = shlvl; + shlvl++; + + closescript(); + clear_traps(); +#if JOBS + /* do job control only in root shell */ + jobctl = 0; + if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) { + pid_t pgrp; + + if (jp->nprocs == 0) + pgrp = getpid(); + else + pgrp = jp->ps[0].pid; + /* This can fail because we are doing it in the parent also */ + (void)setpgid(0, pgrp); + if (mode == FORK_FG) + xtcsetpgrp(ttyfd, pgrp); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + } else +#endif + if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if (jp->nprocs == 0) { + close(0); + if (open(bb_dev_null, O_RDONLY) != 0) + sh_error("Can't open %s", bb_dev_null); + } + } + if (!oldlvl && iflag) { + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); + } + for (jp = curjob; jp; jp = jp->prev_job) + freejob(jp); + jobless = 0; +} + +static void forkparent(struct job *jp, union node *n, int mode, pid_t pid) +{ + TRACE(("In parent shell: child = %d\n", pid)); + if (!jp) { + while (jobless && dowait(DOWAIT_NORMAL, 0) > 0); + jobless++; + return; + } +#if JOBS + if (mode != FORK_NOJOB && jp->jobctl) { + int pgrp; + + if (jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + /* This can fail because we are doing it in the child also */ + (void)setpgid(pid, pgrp); + } +#endif + if (mode == FORK_BG) { + backgndpid = pid; /* set $! */ + set_curjob(jp, CUR_RUNNING); + } + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; +#if JOBS + if (jobctl && n) + ps->cmd = commandtext(n); +#endif + } +} + +static int +forkshell(struct job *jp, union node *n, int mode) +{ + int pid; + + TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode)); + pid = fork(); + if (pid < 0) { + TRACE(("Fork failed, errno=%d", errno)); + if (jp) + freejob(jp); + sh_error("Cannot fork"); + } + if (pid == 0) + forkchild(jp, n, mode); + else + forkparent(jp, n, mode, pid); + return pid; +} + +/* + * Wait for job to finish. + * + * Under job control we have the problem that while a child process is + * running interrupts generated by the user are sent to the child but not + * to the shell. This means that an infinite loop started by an inter- + * active user may be hard to kill. With job control turned off, an + * interactive user may place an interactive program inside a loop. If + * the interactive program catches interrupts, the user doesn't want + * these interrupts to also abort the loop. The approach we take here + * is to have the shell ignore interrupt signals while waiting for a + * foreground process to terminate, and then send itself an interrupt + * signal if the child process was terminated by an interrupt signal. + * Unfortunately, some programs want to do a bit of cleanup and then + * exit on interrupt; unless these processes terminate themselves by + * sending a signal to themselves (instead of calling exit) they will + * confuse this approach. + * + * Called with interrupts off. + */ + +int +waitforjob(struct job *jp) +{ + int st; + + TRACE(("waitforjob(%%%d) called\n", jobno(jp))); + while (jp->state == JOBRUNNING) { + dowait(DOWAIT_BLOCK, jp); + } + st = getstatus(jp); +#if JOBS + if (jp->jobctl) { + xtcsetpgrp(ttyfd, rootpid); + /* + * This is truly gross. + * If we're doing job control, then we did a TIOCSPGRP which + * caused us (the shell) to no longer be in the controlling + * session -- so we wouldn't have seen any ^C/SIGINT. So, we + * intuit from the subprocess exit status whether a SIGINT + * occurred, and if so interrupt ourselves. Yuck. - mycroft + */ + if (jp->sigint) + raise(SIGINT); + } + if (jp->state == JOBDONE) +#endif + freejob(jp); + return st; +} + + +/* + * Do a wait system call. If job control is compiled in, we accept + * stopped processes. If block is zero, we return a value of zero + * rather than blocking. + * + * System V doesn't have a non-blocking wait system call. It does + * have a SIGCLD signal that is sent to a process when one of it's + * children dies. The obvious way to use SIGCLD would be to install + * a handler for SIGCLD which simply bumped a counter when a SIGCLD + * was received, and have waitproc bump another counter when it got + * the status of a process. Waitproc would then know that a wait + * system call would not block if the two counters were different. + * This approach doesn't work because if a process has children that + * have not been waited for, System V will send it a SIGCLD when it + * installs a signal handler for SIGCLD. What this means is that when + * a child exits, the shell will be sent SIGCLD signals continuously + * until is runs out of stack space, unless it does a wait call before + * restoring the signal handler. The code below takes advantage of + * this (mis)feature by installing a signal handler for SIGCLD and + * then checking to see whether it was called. If there are any + * children to be waited for, it will be. + * + * If neither SYSV nor BSD is defined, we don't implement nonblocking + * waits at all. In this case, the user will not be informed when + * a background process until the next time she runs a real program + * (as opposed to running a builtin command or just typing return), + * and the jobs command may give out of date information. + */ + +static int waitproc(int block, int *status) +{ + int flags = 0; + +#if JOBS + if (jobctl) + flags |= WUNTRACED; +#endif + if (block == 0) + flags |= WNOHANG; + return wait3(status, flags, (struct rusage *)NULL); +} + +/* + * Wait for a process to terminate. + */ + +static int +dowait(int block, struct job *job) +{ + int pid; + int status; + struct job *jp; + struct job *thisjob; + int state; + + TRACE(("dowait(%d) called\n", block)); + pid = waitproc(block, &status); + TRACE(("wait returns pid %d, status=%d\n", pid, status)); + if (pid <= 0) + return pid; + INTOFF; + thisjob = NULL; + for (jp = curjob; jp; jp = jp->prev_job) { + struct procstat *sp; + struct procstat *spend; + if (jp->state == JOBDONE) + continue; + state = JOBDONE; + spend = jp->ps + jp->nprocs; + sp = jp->ps; + do { + if (sp->pid == pid) { + TRACE(("Job %d: changing status of proc %d from 0x%x to 0x%x\n", jobno(jp), pid, sp->status, status)); + sp->status = status; + thisjob = jp; + } + if (sp->status == -1) + state = JOBRUNNING; +#if JOBS + if (state == JOBRUNNING) + continue; + if (WIFSTOPPED(sp->status)) { + jp->stopstatus = sp->status; + state = JOBSTOPPED; + } +#endif + } while (++sp < spend); + if (thisjob) + goto gotjob; + } +#if JOBS + if (!WIFSTOPPED(status)) +#endif + + jobless--; + goto out; + +gotjob: + if (state != JOBRUNNING) { + thisjob->changed = 1; + + if (thisjob->state != state) { + TRACE(("Job %d: changing state from %d to %d\n", jobno(thisjob), thisjob->state, state)); + thisjob->state = state; +#if JOBS + if (state == JOBSTOPPED) { + set_curjob(thisjob, CUR_STOPPED); + } +#endif + } + } + +out: + INTON; + + if (thisjob && thisjob == job) { + char s[48 + 1]; + int len; + + len = sprint_status(s, status, 1); + if (len) { + s[len] = '\n'; + s[len + 1] = 0; + out2str(s); + } + } + return pid; +} + + +/* + * return 1 if there are stopped jobs, otherwise 0 + */ + +int +stoppedjobs(void) +{ + struct job *jp; + int retval; + + retval = 0; + if (job_warning) + goto out; + jp = curjob; + if (jp && jp->state == JOBSTOPPED) { + out2str("You have stopped jobs.\n"); + job_warning = 2; + retval++; + } + +out: + return retval; +} + +/* + * Return a string identifying a command (to be printed by the + * jobs command). + */ + +#if JOBS +static char *cmdnextc; + +static char * +commandtext(union node *n) +{ + char *name; + + STARTSTACKSTR(cmdnextc); + cmdtxt(n); + name = stackblock(); + TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n", + name, cmdnextc, cmdnextc)); + return savestr(name); +} + +static void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + const char *p; + char s[2]; + + if (!n) + return; + switch (n->type) { + default: +#if DEBUG + abort(); +#endif + case NPIPE: + lp = n->npipe.cmdlist; + for (;;) { + cmdtxt(lp->n); + lp = lp->next; + if (!lp) + break; + cmdputs(" | "); + } + break; + case NSEMI: + p = "; "; + goto binop; + case NAND: + p = " && "; + goto binop; + case NOR: + p = " || "; +binop: + cmdtxt(n->nbinary.ch1); + cmdputs(p); + n = n->nbinary.ch2; + goto donode; + case NREDIR: + case NBACKGND: + n = n->nredir.n; + goto donode; + case NNOT: + cmdputs("!"); + n = n->nnot.com; +donode: + cmdtxt(n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + n = n->nif.ifpart; + if (n->nif.elsepart) { + cmdtxt(n); + cmdputs("; else "); + n = n->nif.elsepart; + } + p = "; fi"; + goto dotail; + case NSUBSHELL: + cmdputs("("); + n = n->nredir.n; + p = ")"; + goto dotail; + case NWHILE: + p = "while "; + goto until; + case NUNTIL: + p = "until "; +until: + cmdputs(p); + cmdtxt(n->nbinary.ch1); + n = n->nbinary.ch2; + p = "; done"; +dodo: + cmdputs("; do "); +dotail: + cmdtxt(n); + goto dotail2; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in "); + cmdlist(n->nfor.args, 1); + n = n->nfor.body; + p = "; done"; + goto dodo; + case NDEFUN: + cmdputs(n->narg.text); + p = "() { ... }"; + goto dotail2; + case NCMD: + cmdlist(n->ncmd.args, 1); + cmdlist(n->ncmd.redirect, 0); + break; + case NARG: + p = n->narg.text; +dotail2: + cmdputs(p); + break; + case NHERE: + case NXHERE: + p = "<<..."; + goto dotail2; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in "); + for (np = n->ncase.cases; np; np = np->nclist.next) { + cmdtxt(np->nclist.pattern); + cmdputs(") "); + cmdtxt(np->nclist.body); + cmdputs(";; "); + } + p = "esac"; + goto dotail2; + case NTO: + p = ">"; + goto redir; + case NCLOBBER: + p = ">|"; + goto redir; + case NAPPEND: + p = ">>"; + goto redir; + case NTOFD: + p = ">&"; + goto redir; + case NFROM: + p = "<"; + goto redir; + case NFROMFD: + p = "<&"; + goto redir; + case NFROMTO: + p = "<>"; +redir: + s[0] = n->nfile.fd + '0'; + s[1] = '\0'; + cmdputs(s); + cmdputs(p); + if (n->type == NTOFD || n->type == NFROMFD) { + s[0] = n->ndup.dupfd + '0'; + p = s; + goto dotail2; + } else { + n = n->nfile.fname; + goto donode; + } + } +} + +static void +cmdlist(union node *np, int sep) +{ + for (; np; np = np->narg.next) { + if (!sep) + cmdputs(spcstr); + cmdtxt(np); + if (sep && np->narg.next) + cmdputs(spcstr); + } +} + +static void +cmdputs(const char *s) +{ + const char *p, *str; + char c, cc[2] = " "; + char *nextc; + int subtype = 0; + int quoted = 0; + static const char vstype[VSTYPE + 1][4] = { + "", "}", "-", "+", "?", "=", + "%", "%%", "#", "##" + }; + nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc); + p = s; + while ((c = *p++) != 0) { + str = 0; + switch (c) { + case CTLESC: + c = *p++; + break; + case CTLVAR: + subtype = *p++; + if ((subtype & VSTYPE) == VSLENGTH) + str = "${#"; + else + str = "${"; + if (!(subtype & VSQUOTE) != !(quoted & 1)) { + quoted ^= 1; + c = '"'; + } else + goto dostr; + break; + case CTLENDVAR: + str = "\"}" + !(quoted & 1); + quoted >>= 1; + subtype = 0; + goto dostr; + case CTLBACKQ: + str = "$(...)"; + goto dostr; + case CTLBACKQ+CTLQUOTE: + str = "\"$(...)\""; + goto dostr; +#ifdef CONFIG_ASH_MATH_SUPPORT + case CTLARI: + str = "$(("; + goto dostr; + case CTLENDARI: + str = "))"; + goto dostr; +#endif + case CTLQUOTEMARK: + quoted ^= 1; + c = '"'; + break; + case '=': + if (subtype == 0) + break; + if ((subtype & VSTYPE) != VSNORMAL) + quoted <<= 1; + str = vstype[subtype & VSTYPE]; + if (subtype & VSNUL) + c = ':'; + else + goto checkstr; + break; + case '\'': + case '\\': + case '"': + case '$': + /* These can only happen inside quotes */ + cc[0] = c; + str = cc; + c = '\\'; + break; + default: + break; + } + USTPUTC(c, nextc); +checkstr: + if (!str) + continue; +dostr: + while ((c = *str++)) { + USTPUTC(c, nextc); + } + } + if (quoted & 1) { + USTPUTC('"', nextc); + } + *nextc = 0; + cmdnextc = nextc; +} + + +static void +showpipe(struct job *jp, FILE *out) +{ + struct procstat *sp; + struct procstat *spend; + + spend = jp->ps + jp->nprocs; + for (sp = jp->ps + 1; sp < spend; sp++) + fprintf(out, " | %s", sp->cmd); + outcslow('\n', out); + flushall(); +} + +static void +xtcsetpgrp(int fd, pid_t pgrp) +{ + if (tcsetpgrp(fd, pgrp)) + sh_error("Cannot set tty process group (%m)"); +} +#endif /* JOBS */ + +static int +getstatus(struct job *job) { + int status; + int retval; + + status = job->ps[job->nprocs - 1].status; + retval = WEXITSTATUS(status); + if (!WIFEXITED(status)) { +#if JOBS + retval = WSTOPSIG(status); + if (!WIFSTOPPED(status)) +#endif + { + /* XXX: limits number of signals */ + retval = WTERMSIG(status); +#if JOBS + if (retval == SIGINT) + job->sigint = 1; +#endif + } + retval += 128; + } + TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n", + jobno(job), job->nprocs, status, retval)); + return retval; +} + +#ifdef CONFIG_ASH_MAIL +/* mail.c */ + +/* + * Routines to check for mail. (Perhaps make part of main.c?) + */ + +#define MAXMBOXES 10 + +/* times of mailboxes */ +static time_t mailtime[MAXMBOXES]; +/* Set if MAIL or MAILPATH is changed. */ +static int mail_var_path_changed; + + + +/* + * Print appropriate message(s) if mail has arrived. + * If mail_var_path_changed is set, + * then the value of MAIL has mail_var_path_changed, + * so we just update the values. + */ + +static void +chkmail(void) +{ + const char *mpath; + char *p; + char *q; + time_t *mtp; + struct stackmark smark; + struct stat statb; + + setstackmark(&smark); + mpath = mpathset() ? mpathval() : mailval(); + for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) { + p = padvance(&mpath, nullstr); + if (p == NULL) + break; + if (*p == '\0') + continue; + for (q = p ; *q ; q++); +#if DEBUG + if (q[-1] != '/') + abort(); +#endif + q[-1] = '\0'; /* delete trailing '/' */ + if (stat(p, &statb) < 0) { + *mtp = 0; + continue; + } + if (!mail_var_path_changed && statb.st_mtime != *mtp) { + fprintf( + stderr, snlfmt, + pathopt ? pathopt : "you have mail" + ); + } + *mtp = statb.st_mtime; + } + mail_var_path_changed = 0; + popstackmark(&smark); +} + + +static void +changemail(const char *val) +{ + mail_var_path_changed++; +} + +#endif /* CONFIG_ASH_MAIL */ + +/* main.c */ + + +#if PROFILE +static short profile_buf[16384]; +extern int etext(); +#endif + +static int isloginsh; + +static void read_profile(const char *); + +/* + * Main routine. We initialize things, parse the arguments, execute + * profiles if we're a login shell, and then call cmdloop to execute + * commands. The setjmp call sets up the location to jump to when an + * exception occurs. When an exception occurs the variable "state" + * is used to figure out how far we had gotten. + */ + +int +ash_main(int argc, char **argv) +{ + char *shinit; + volatile int state; + struct jmploc jmploc; + struct stackmark smark; + +#ifdef __GLIBC__ + dash_errno = __errno_location(); +#endif + +#if PROFILE + monitor(4, etext, profile_buf, sizeof profile_buf, 50); +#endif + state = 0; + if (setjmp(jmploc.loc)) { + int e; + int s; + + reset(); + + e = exception; + if (e == EXERROR) + exitstatus = 2; + s = state; + if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) + exitshell(); + + if (e == EXINT) { + outcslow('\n', stderr); + } + popstackmark(&smark); + FORCEINTON; /* enable interrupts */ + if (s == 1) + goto state1; + else if (s == 2) + goto state2; + else if (s == 3) + goto state3; + else + goto state4; + } + handler = &jmploc; +#if DEBUG + opentrace(); + trputs("Shell args: "); trargs(argv); +#endif + rootpid = getpid(); + +#ifdef CONFIG_ASH_RANDOM_SUPPORT + rseed = rootpid + ((time_t)time((time_t *)0)); +#endif + init(); + setstackmark(&smark); + procargs(argc, argv); +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY + if ( iflag ) { + const char *hp = lookupvar("HISTFILE"); + + if(hp == NULL ) { + hp = lookupvar("HOME"); + if(hp != NULL) { + char *defhp = concat_path_file(hp, ".ash_history"); + setvar("HISTFILE", defhp, 0); + free(defhp); + } + } + } +#endif + if (argv[0] && argv[0][0] == '-') + isloginsh = 1; + if (isloginsh) { + state = 1; + read_profile("/etc/profile"); +state1: + state = 2; + read_profile(".profile"); + } +state2: + state = 3; + if ( +#ifndef linux + getuid() == geteuid() && getgid() == getegid() && +#endif + iflag + ) { + if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') { + read_profile(shinit); + } + } +state3: + state = 4; + if (minusc) + evalstring(minusc, 0); + + if (sflag || minusc == NULL) { +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY + if ( iflag ) { + const char *hp = lookupvar("HISTFILE"); + + if(hp != NULL ) + load_history ( hp ); + } +#endif +state4: /* XXX ??? - why isn't this before the "if" statement */ + cmdloop(1); + } +#if PROFILE + monitor(0); +#endif +#ifdef GPROF + { + extern void _mcleanup(void); + _mcleanup(); + } +#endif + exitshell(); + /* NOTREACHED */ +} + + +/* + * Read and execute commands. "Top" is nonzero for the top level command + * loop; it turns on prompting if the shell is interactive. + */ + +static int +cmdloop(int top) +{ + union node *n; + struct stackmark smark; + int inter; + int numeof = 0; + + TRACE(("cmdloop(%d) called\n", top)); + for (;;) { + int skip; + + setstackmark(&smark); +#if JOBS + if (jobctl) + showjobs(stderr, SHOW_CHANGED); +#endif + inter = 0; + if (iflag && top) { + inter++; +#ifdef CONFIG_ASH_MAIL + chkmail(); +#endif + } + n = parsecmd(inter); + /* showtree(n); DEBUG */ + if (n == NEOF) { + if (!top || numeof >= 50) + break; + if (!stoppedjobs()) { + if (!Iflag) + break; + out2str("\nUse \"exit\" to leave shell.\n"); + } + numeof++; + } else if (nflag == 0) { + job_warning = (job_warning == 2) ? 1 : 0; + numeof = 0; + evaltree(n, 0); + } + popstackmark(&smark); + skip = evalskip; + + if (skip) { + evalskip = 0; + return skip & SKIPEVAL; + } + } + + return 0; +} + + +/* + * Read /etc/profile or .profile. Return on error. + */ + +static void +read_profile(const char *name) +{ + int skip; + + if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0) + return; + + skip = cmdloop(0); + popfile(); + + if (skip) + exitshell(); +} + + +/* + * Read a file containing shell functions. + */ + +static void +readcmdfile(char *name) +{ + setinputfile(name, INPUT_PUSH_FILE); + cmdloop(0); + popfile(); +} + + +/* + * Take commands from a file. To be compatible we should do a path + * search for the file, which is necessary to find sub-commands. + */ + +static char * find_dot_file(char *name) +{ + char *fullname; + const char *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if (strchr(name, '/')) + return name; + + while ((fullname = padvance(&path, name)) != NULL) { + if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + /* + * Don't bother freeing here, since it will + * be freed by the caller. + */ + return fullname; + } + stunalloc(fullname); + } + + /* not found in the PATH */ + sh_error(not_found_msg, name); + /* NOTREACHED */ +} + +static int dotcmd(int argc, char **argv) +{ + struct strlist *sp; + volatile struct shparam saveparam; + int status = 0; + + for (sp = cmdenviron; sp; sp = sp->next) + setvareq(xstrdup(sp->text), VSTRFIXED | VTEXTFIXED); + + if (argc >= 2) { /* That's what SVR2 does */ + char *fullname; + + fullname = find_dot_file(argv[1]); + + if (argc > 2) { + saveparam = shellparam; + shellparam.malloc = 0; + shellparam.nparam = argc - 2; + shellparam.p = argv + 2; + }; + + setinputfile(fullname, INPUT_PUSH_FILE); + commandname = fullname; + cmdloop(0); + popfile(); + + if (argc > 2) { + freeparam(&shellparam); + shellparam = saveparam; + }; + status = exitstatus; + } + return status; +} + + +static int +exitcmd(int argc, char **argv) +{ + if (stoppedjobs()) + return 0; + if (argc > 1) + exitstatus = number(argv[1]); + exraise(EXEXIT); + /* NOTREACHED */ +} + +#ifdef CONFIG_ASH_BUILTIN_ECHO +static int +echocmd(int argc, char **argv) +{ + return bb_echo(argc, argv); +} +#endif + +#ifdef CONFIG_ASH_BUILTIN_TEST +static int +testcmd(int argc, char **argv) +{ + return bb_test(argc, argv); +} +#endif + +/* memalloc.c */ + +/* + * Same for malloc, realloc, but returns an error when out of space. + */ + +static pointer +ckrealloc(pointer p, size_t nbytes) +{ + p = realloc(p, nbytes); + if (p == NULL) + sh_error(bb_msg_memory_exhausted); + return p; +} + +static pointer +ckmalloc(size_t nbytes) +{ + return ckrealloc(NULL, nbytes); +} + +/* + * Make a copy of a string in safe storage. + */ + +static char * +savestr(const char *s) +{ + char *p = strdup(s); + if (!p) + sh_error(bb_msg_memory_exhausted); + return p; +} + + +/* + * Parse trees for commands are allocated in lifo order, so we use a stack + * to make this more efficient, and also to avoid all sorts of exception + * handling code to handle interrupts in the middle of a parse. + * + * The size 504 was chosen because the Ultrix malloc handles that size + * well. + */ + + +static pointer +stalloc(size_t nbytes) +{ + char *p; + size_t aligned; + + aligned = SHELL_ALIGN(nbytes); + if (aligned > stacknleft) { + size_t len; + size_t blocksize; + struct stack_block *sp; + + blocksize = aligned; + if (blocksize < MINSIZE) + blocksize = MINSIZE; + len = sizeof(struct stack_block) - MINSIZE + blocksize; + if (len < blocksize) + sh_error(bb_msg_memory_exhausted); + INTOFF; + sp = ckmalloc(len); + sp->prev = stackp; + stacknxt = sp->space; + stacknleft = blocksize; + sstrend = stacknxt + blocksize; + stackp = sp; + INTON; + } + p = stacknxt; + stacknxt += aligned; + stacknleft -= aligned; + return p; +} + + +void +stunalloc(pointer p) +{ +#if DEBUG + if (!p || (stacknxt < (char *)p) || ((char *)p < stackp->space)) { + write(2, "stunalloc\n", 10); + abort(); + } +#endif + stacknleft += stacknxt - (char *)p; + stacknxt = p; +} + + +void +setstackmark(struct stackmark *mark) +{ + mark->stackp = stackp; + mark->stacknxt = stacknxt; + mark->stacknleft = stacknleft; + mark->marknext = markp; + markp = mark; +} + + +void +popstackmark(struct stackmark *mark) +{ + struct stack_block *sp; + + INTOFF; + markp = mark->marknext; + while (stackp != mark->stackp) { + sp = stackp; + stackp = sp->prev; + ckfree(sp); + } + stacknxt = mark->stacknxt; + stacknleft = mark->stacknleft; + sstrend = mark->stacknxt + mark->stacknleft; + INTON; +} + + +/* + * When the parser reads in a string, it wants to stick the string on the + * stack and only adjust the stack pointer when it knows how big the + * string is. Stackblock (defined in stack.h) returns a pointer to a block + * of space on top of the stack and stackblocklen returns the length of + * this block. Growstackblock will grow this space by at least one byte, + * possibly moving it (like realloc). Grabstackblock actually allocates the + * part of the block that has been used. + */ + +void +growstackblock(void) +{ + size_t newlen; + + newlen = stacknleft * 2; + if (newlen < stacknleft) + sh_error(bb_msg_memory_exhausted); + if (newlen < 128) + newlen += 128; + + if (stacknxt == stackp->space && stackp != &stackbase) { + struct stack_block *oldstackp; + struct stackmark *xmark; + struct stack_block *sp; + struct stack_block *prevstackp; + size_t grosslen; + + INTOFF; + oldstackp = stackp; + sp = stackp; + prevstackp = sp->prev; + grosslen = newlen + sizeof(struct stack_block) - MINSIZE; + sp = ckrealloc((pointer)sp, grosslen); + sp->prev = prevstackp; + stackp = sp; + stacknxt = sp->space; + stacknleft = newlen; + sstrend = sp->space + newlen; + + /* + * Stack marks pointing to the start of the old block + * must be relocated to point to the new block + */ + xmark = markp; + while (xmark != NULL && xmark->stackp == oldstackp) { + xmark->stackp = stackp; + xmark->stacknxt = stacknxt; + xmark->stacknleft = stacknleft; + xmark = xmark->marknext; + } + INTON; + } else { + char *oldspace = stacknxt; + int oldlen = stacknleft; + char *p = stalloc(newlen); + + /* free the space we just allocated */ + stacknxt = memcpy(p, oldspace, oldlen); + stacknleft += newlen; + } +} + +static void grabstackblock(size_t len) +{ + len = SHELL_ALIGN(len); + stacknxt += len; + stacknleft -= len; +} + +/* + * The following routines are somewhat easier to use than the above. + * The user declares a variable of type STACKSTR, which may be declared + * to be a register. The macro STARTSTACKSTR initializes things. Then + * the user uses the macro STPUTC to add characters to the string. In + * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is + * grown as necessary. When the user is done, she can just leave the + * string there and refer to it using stackblock(). Or she can allocate + * the space for it using grabstackstr(). If it is necessary to allow + * someone else to use the stack temporarily and then continue to grow + * the string, the user should use grabstack to allocate the space, and + * then call ungrabstr(p) to return to the previous mode of operation. + * + * USTPUTC is like STPUTC except that it doesn't check for overflow. + * CHECKSTACKSPACE can be called before USTPUTC to ensure that there + * is space for at least one character. + */ + +void * +growstackstr(void) +{ + size_t len = stackblocksize(); + if (herefd >= 0 && len >= 1024) { + full_write(herefd, stackblock(), len); + return stackblock(); + } + growstackblock(); + return stackblock() + len; +} + +/* + * Called from CHECKSTRSPACE. + */ + +char * +makestrspace(size_t newlen, char *p) +{ + size_t len = p - stacknxt; + size_t size = stackblocksize(); + + for (;;) { + size_t nleft; + + size = stackblocksize(); + nleft = size - len; + if (nleft >= newlen) + break; + growstackblock(); + } + return stackblock() + len; +} + +char * +stnputs(const char *s, size_t n, char *p) +{ + p = makestrspace(n, p); + p = mempcpy(p, s, n); + return p; +} + +char * +stputs(const char *s, char *p) +{ + return stnputs(s, strlen(s), p); +} + +/* mystring.c */ + +/* + * String functions. + * + * number(s) Convert a string of digits to an integer. + * is_number(s) Return true if s is a string of digits. + */ + +/* + * prefix -- see if pfx is a prefix of string. + */ + +char * +prefix(const char *string, const char *pfx) +{ + while (*pfx) { + if (*pfx++ != *string++) + return 0; + } + return (char *) string; +} + + +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ + +int +number(const char *s) +{ + + if (! is_number(s)) + sh_error(illnum, s); + return atoi(s); +} + + +/* + * Check for a valid number. This should be elsewhere. + */ + +int +is_number(const char *p) +{ + do { + if (! is_digit(*p)) + return 0; + } while (*++p != '\0'); + return 1; +} + + +/* + * Produce a possibly single quoted string suitable as input to the shell. + * The return string is allocated on the stack. + */ + +char * +single_quote(const char *s) { + char *p; + + STARTSTACKSTR(p); + + do { + char *q; + size_t len; + + len = strchrnul(s, '\'') - s; + + q = p = makestrspace(len + 3, p); + + *q++ = '\''; + q = mempcpy(q, s, len); + *q++ = '\''; + s += len; + + STADJUST(q - p, p); + + len = strspn(s, "'"); + if (!len) + break; + + q = p = makestrspace(len + 3, p); + + *q++ = '"'; + q = mempcpy(q, s, len); + *q++ = '"'; + s += len; + + STADJUST(q - p, p); + } while (*s); + + USTPUTC(0, p); + + return stackblock(); +} + +/* + * Like strdup but works with the ash stack. + */ + +char * +sstrdup(const char *p) +{ + size_t len = strlen(p) + 1; + return memcpy(stalloc(len), p, len); +} + + +static void +calcsize(union node *n) +{ + if (n == NULL) + return; + funcblocksize += nodesize[n->type]; + switch (n->type) { + case NCMD: + calcsize(n->ncmd.redirect); + calcsize(n->ncmd.args); + calcsize(n->ncmd.assign); + break; + case NPIPE: + sizenodelist(n->npipe.cmdlist); + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + calcsize(n->nredir.redirect); + calcsize(n->nredir.n); + break; + case NAND: + case NOR: + case NSEMI: + case NWHILE: + case NUNTIL: + calcsize(n->nbinary.ch2); + calcsize(n->nbinary.ch1); + break; + case NIF: + calcsize(n->nif.elsepart); + calcsize(n->nif.ifpart); + calcsize(n->nif.test); + break; + case NFOR: + funcstringsize += strlen(n->nfor.var) + 1; + calcsize(n->nfor.body); + calcsize(n->nfor.args); + break; + case NCASE: + calcsize(n->ncase.cases); + calcsize(n->ncase.expr); + break; + case NCLIST: + calcsize(n->nclist.body); + calcsize(n->nclist.pattern); + calcsize(n->nclist.next); + break; + case NDEFUN: + case NARG: + sizenodelist(n->narg.backquote); + funcstringsize += strlen(n->narg.text) + 1; + calcsize(n->narg.next); + break; + case NTO: + case NCLOBBER: + case NFROM: + case NFROMTO: + case NAPPEND: + calcsize(n->nfile.fname); + calcsize(n->nfile.next); + break; + case NTOFD: + case NFROMFD: + calcsize(n->ndup.vname); + calcsize(n->ndup.next); + break; + case NHERE: + case NXHERE: + calcsize(n->nhere.doc); + calcsize(n->nhere.next); + break; + case NNOT: + calcsize(n->nnot.com); + break; + }; +} + + +static void +sizenodelist(struct nodelist *lp) +{ + while (lp) { + funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); + calcsize(lp->n); + lp = lp->next; + } +} + + +static union node * +copynode(union node *n) +{ + union node *new; + + if (n == NULL) + return NULL; + new = funcblock; + funcblock = (char *) funcblock + nodesize[n->type]; + switch (n->type) { + case NCMD: + new->ncmd.redirect = copynode(n->ncmd.redirect); + new->ncmd.args = copynode(n->ncmd.args); + new->ncmd.assign = copynode(n->ncmd.assign); + break; + case NPIPE: + new->npipe.cmdlist = copynodelist(n->npipe.cmdlist); + new->npipe.backgnd = n->npipe.backgnd; + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + new->nredir.redirect = copynode(n->nredir.redirect); + new->nredir.n = copynode(n->nredir.n); + break; + case NAND: + case NOR: + case NSEMI: + case NWHILE: + case NUNTIL: + new->nbinary.ch2 = copynode(n->nbinary.ch2); + new->nbinary.ch1 = copynode(n->nbinary.ch1); + break; + case NIF: + new->nif.elsepart = copynode(n->nif.elsepart); + new->nif.ifpart = copynode(n->nif.ifpart); + new->nif.test = copynode(n->nif.test); + break; + case NFOR: + new->nfor.var = nodesavestr(n->nfor.var); + new->nfor.body = copynode(n->nfor.body); + new->nfor.args = copynode(n->nfor.args); + break; + case NCASE: + new->ncase.cases = copynode(n->ncase.cases); + new->ncase.expr = copynode(n->ncase.expr); + break; + case NCLIST: + new->nclist.body = copynode(n->nclist.body); + new->nclist.pattern = copynode(n->nclist.pattern); + new->nclist.next = copynode(n->nclist.next); + break; + case NDEFUN: + case NARG: + new->narg.backquote = copynodelist(n->narg.backquote); + new->narg.text = nodesavestr(n->narg.text); + new->narg.next = copynode(n->narg.next); + break; + case NTO: + case NCLOBBER: + case NFROM: + case NFROMTO: + case NAPPEND: + new->nfile.fname = copynode(n->nfile.fname); + new->nfile.fd = n->nfile.fd; + new->nfile.next = copynode(n->nfile.next); + break; + case NTOFD: + case NFROMFD: + new->ndup.vname = copynode(n->ndup.vname); + new->ndup.dupfd = n->ndup.dupfd; + new->ndup.fd = n->ndup.fd; + new->ndup.next = copynode(n->ndup.next); + break; + case NHERE: + case NXHERE: + new->nhere.doc = copynode(n->nhere.doc); + new->nhere.fd = n->nhere.fd; + new->nhere.next = copynode(n->nhere.next); + break; + case NNOT: + new->nnot.com = copynode(n->nnot.com); + break; + }; + new->type = n->type; + return new; +} + + +static struct nodelist * +copynodelist(struct nodelist *lp) +{ + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = funcblock; + funcblock = (char *) funcblock + + SHELL_ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n); + lp = lp->next; + lpp = &(*lpp)->next; + } + *lpp = NULL; + return start; +} + + +static char * +nodesavestr(char *s) +{ + char *rtn = funcstring; + + funcstring = stpcpy(funcstring, s) + 1; + return rtn; +} + + +/* + * Free a parse tree. + */ + +static void +freefunc(struct funcnode *f) +{ + if (f && --f->count < 0) + ckfree(f); +} + + +static void options(int); +static void setoption(int, int); + + +/* + * Process the shell command line arguments. + */ + +void +procargs(int argc, char **argv) +{ + int i; + const char *xminusc; + char **xargv; + + xargv = argv; + arg0 = xargv[0]; + if (argc > 0) + xargv++; + for (i = 0; i < NOPTS; i++) + optlist[i] = 2; + argptr = xargv; + options(1); + xargv = argptr; + xminusc = minusc; + if (*xargv == NULL) { + if (xminusc) + sh_error(bb_msg_requires_arg, "-c"); + sflag = 1; + } + if (iflag == 2 && sflag == 1 && isatty(0) && isatty(1)) + iflag = 1; + if (mflag == 2) + mflag = iflag; + for (i = 0; i < NOPTS; i++) + if (optlist[i] == 2) + optlist[i] = 0; +#if DEBUG == 2 + debug = 1; +#endif + /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + if (xminusc) { + minusc = *xargv++; + if (*xargv) + goto setarg0; + } else if (!sflag) { + setinputfile(*xargv, 0); +setarg0: + arg0 = *xargv++; + commandname = arg0; + } + + shellparam.p = xargv; +#ifdef CONFIG_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */ + while (*xargv) { + shellparam.nparam++; + xargv++; + } + optschanged(); +} + + +void +optschanged(void) +{ +#if DEBUG + opentrace(); +#endif + setinteractive(iflag); + setjobctl(mflag); + setvimode(viflag); +} + +static void minus_o(char *name, int val) +{ + int i; + + if (name == NULL) { + out1str("Current option settings\n"); + for (i = 0; i < NOPTS; i++) + out1fmt("%-16s%s\n", optnames(i), + optlist[i] ? "on" : "off"); + } else { + for (i = 0; i < NOPTS; i++) + if (equal(name, optnames(i))) { + optlist[i] = val; + return; + } + sh_error("Illegal option -o %s", name); + } +} + +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + */ + +static void +options(int cmdline) +{ + char *p; + int val; + int c; + + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + argptr++; + if ((c = *p++) == '-') { + val = 1; + if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) { + if (!cmdline) { + /* "-" means turn off -x and -v */ + if (p[0] == '\0') + xflag = vflag = 0; + /* "--" means reset params */ + else if (*argptr == NULL) + setparam(argptr); + } + break; /* "-" or "--" terminates options */ + } + } else if (c == '+') { + val = 0; + } else { + argptr--; + break; + } + while ((c = *p++) != '\0') { + if (c == 'c' && cmdline) { + minusc = p; /* command is after shell args*/ + } else if (c == 'o') { + minus_o(*argptr, val); + if (*argptr) + argptr++; + } else if (cmdline && (c == '-')) { // long options + if (strcmp(p, "login") == 0) + isloginsh = 1; + break; + } else { + setoption(c, val); + } + } + } +} + + +static void +setoption(int flag, int val) +{ + int i; + + for (i = 0; i < NOPTS; i++) + if (optletters(i) == flag) { + optlist[i] = val; + return; + } + sh_error("Illegal option -%c", flag); + /* NOTREACHED */ +} + + + +/* + * Set the shell parameters. + */ + +void +setparam(char **argv) +{ + char **newparam; + char **ap; + int nparam; + + for (nparam = 0 ; argv[nparam] ; nparam++); + ap = newparam = ckmalloc((nparam + 1) * sizeof *ap); + while (*argv) { + *ap++ = savestr(*argv++); + } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = nparam; + shellparam.p = newparam; +#ifdef CONFIG_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif +} + + +/* + * Free the list of positional parameters. + */ + +void +freeparam(volatile struct shparam *param) +{ + char **ap; + + if (param->malloc) { + for (ap = param->p ; *ap ; ap++) + ckfree(*ap); + ckfree(param->p); + } +} + + + +/* + * The shift builtin command. + */ + +int +shiftcmd(int argc, char **argv) +{ + int n; + char **ap1, **ap2; + + n = 1; + if (argc > 1) + n = number(argv[1]); + if (n > shellparam.nparam) + sh_error("can't shift that many"); + INTOFF; + shellparam.nparam -= n; + for (ap1 = shellparam.p ; --n >= 0 ; ap1++) { + if (shellparam.malloc) + ckfree(*ap1); + } + ap2 = shellparam.p; + while ((*ap2++ = *ap1++) != NULL); +#ifdef CONFIG_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + INTON; + return 0; +} + + + +/* + * The set command builtin. + */ + +int +setcmd(int argc, char **argv) +{ + if (argc == 1) + return showvars(nullstr, 0, VUNSET); + INTOFF; + options(0); + optschanged(); + if (*argptr != NULL) { + setparam(argptr); + } + INTON; + return 0; +} + + +#ifdef CONFIG_ASH_GETOPTS +static void +getoptsreset(const char *value) +{ + shellparam.optind = number(value); + shellparam.optoff = -1; +} +#endif + +#ifdef CONFIG_LOCALE_SUPPORT +static void change_lc_all(const char *value) +{ + if (value != 0 && *value != 0) + setlocale(LC_ALL, value); +} + +static void change_lc_ctype(const char *value) +{ + if (value != 0 && *value != 0) + setlocale(LC_CTYPE, value); +} + +#endif + +#ifdef CONFIG_ASH_RANDOM_SUPPORT +/* Roughly copied from bash.. */ +static void change_random(const char *value) +{ + if(value == NULL) { + /* "get", generate */ + char buf[16]; + + rseed = rseed * 1103515245 + 12345; + sprintf(buf, "%d", (unsigned int)((rseed & 32767))); + /* set without recursion */ + setvar(vrandom.text, buf, VNOFUNC); + vrandom.flags &= ~VNOFUNC; + } else { + /* set/reset */ + rseed = strtoul(value, (char **)NULL, 10); + } +} +#endif + + +#ifdef CONFIG_ASH_GETOPTS +static int +getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff) +{ + char *p, *q; + char c = '?'; + int done = 0; + int err = 0; + char s[12]; + char **optnext; + + if(*param_optind < 1) + return 1; + optnext = optfirst + *param_optind - 1; + + if (*param_optind <= 1 || *optoff < 0 || strlen(optnext[-1]) < *optoff) + p = NULL; + else + p = optnext[-1] + *optoff; + if (p == NULL || *p == '\0') { + /* Current word is done, advance */ + p = *optnext; + if (p == NULL || *p != '-' || *++p == '\0') { +atend: + p = NULL; + done = 1; + goto out; + } + optnext++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + goto atend; + } + + c = *p++; + for (q = optstr; *q != c; ) { + if (*q == '\0') { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + err |= setvarsafe("OPTARG", s, 0); + } else { + fprintf(stderr, "Illegal option -%c\n", c); + (void) unsetvar("OPTARG"); + } + c = '?'; + goto out; + } + if (*++q == ':') + q++; + } + + if (*++q == ':') { + if (*p == '\0' && (p = *optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + err |= setvarsafe("OPTARG", s, 0); + c = ':'; + } else { + fprintf(stderr, "No arg for -%c option\n", c); + (void) unsetvar("OPTARG"); + c = '?'; + } + goto out; + } + + if (p == *optnext) + optnext++; + err |= setvarsafe("OPTARG", p, 0); + p = NULL; + } else + err |= setvarsafe("OPTARG", nullstr, 0); + +out: + *optoff = p ? p - *(optnext - 1) : -1; + *param_optind = optnext - optfirst + 1; + fmtstr(s, sizeof(s), "%d", *param_optind); + err |= setvarsafe("OPTIND", s, VNOFUNC); + s[0] = c; + s[1] = '\0'; + err |= setvarsafe(optvar, s, 0); + if (err) { + *param_optind = 1; + *optoff = -1; + flushall(); + exraise(EXERROR); + } + return done; +} + +/* + * The getopts builtin. Shellparam.optnext points to the next argument + * to be processed. Shellparam.optptr points to the next character to + * be processed in the current argument. If shellparam.optnext is NULL, + * then it's the first time getopts has been called. + */ + +int +getoptscmd(int argc, char **argv) +{ + char **optbase; + + if (argc < 3) + sh_error("Usage: getopts optstring var [arg]"); + else if (argc == 3) { + optbase = shellparam.p; + if (shellparam.optind > shellparam.nparam + 1) { + shellparam.optind = 1; + shellparam.optoff = -1; + } + } + else { + optbase = &argv[3]; + if (shellparam.optind > argc - 2) { + shellparam.optind = 1; + shellparam.optoff = -1; + } + } + + return getopts(argv[1], argv[2], optbase, &shellparam.optind, + &shellparam.optoff); +} +#endif /* CONFIG_ASH_GETOPTS */ + +/* + * XXX - should get rid of. have all builtins use getopt(3). the + * library getopt must have the BSD extension static variable "optreset" + * otherwise it can't be used within the shell safely. + * + * Standard option processing (a la getopt) for builtin routines. The + * only argument that is passed to nextopt is the option string; the + * other arguments are unnecessary. It return the character, or '\0' on + * end of input. + */ + +static int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; + + if ((p = optptr) == NULL || *p == '\0') { + p = *argptr; + if (p == NULL || *p != '-' || *++p == '\0') + return '\0'; + argptr++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + return '\0'; + } + c = *p++; + for (q = optstring ; *q != c ; ) { + if (*q == '\0') + sh_error("Illegal option -%c", c); + if (*++q == ':') + q++; + } + if (*++q == ':') { + if (*p == '\0' && (p = *argptr++) == NULL) + sh_error("No arg for -%c option", c); + optionarg = p; + p = NULL; + } + optptr = p; + return c; +} + + +/* output.c */ + +void +outstr(const char *p, FILE *file) +{ + INTOFF; + fputs(p, file); + INTON; +} + +void +flushall(void) +{ + INTOFF; + fflush(stdout); + fflush(stderr); + INTON; +} + +void +flusherr(void) +{ + INTOFF; + fflush(stderr); + INTON; +} + +static void +outcslow(int c, FILE *dest) +{ + INTOFF; + putc(c, dest); + fflush(dest); + INTON; +} + + +static int +out1fmt(const char *fmt, ...) +{ + va_list ap; + int r; + + INTOFF; + va_start(ap, fmt); + r = vprintf(fmt, ap); + va_end(ap); + INTON; + return r; +} + + +int +fmtstr(char *outbuf, size_t length, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + INTOFF; + ret = vsnprintf(outbuf, length, fmt, ap); + va_end(ap); + INTON; + return ret; +} + + + +/* parser.c */ + + +/* + * Shell command parser. + */ + +#define EOFMARKLEN 79 + + +struct heredoc { + struct heredoc *next; /* next here document in list */ + union node *here; /* redirection node */ + char *eofmark; /* string indicating end of input */ + int striptabs; /* if set, strip leading tabs */ +}; + + + +static struct heredoc *heredoclist; /* list of here documents to read */ + + +static union node *list(int); +static union node *andor(void); +static union node *pipeline(void); +static union node *command(void); +static union node *simplecmd(void); +static union node *makename(void); +static void parsefname(void); +static void parseheredoc(void); +static char peektoken(void); +static int readtoken(void); +static int xxreadtoken(void); +static int readtoken1(int firstc, int syntax, char *eofmark, int striptabs); +static int noexpand(char *); +static void synexpect(int) ATTRIBUTE_NORETURN; +static void synerror(const char *) ATTRIBUTE_NORETURN; +static void setprompt(int); + + + + +/* + * Read and parse a command. Returns NEOF on end of file. (NULL is a + * valid parse tree indicating a blank line.) + */ + +union node * +parsecmd(int interact) +{ + int t; + + tokpushback = 0; + doprompt = interact; + if (doprompt) + setprompt(doprompt); + needprompt = 0; + t = readtoken(); + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + tokpushback++; + return list(1); +} + + +static union node * +list(int nlflag) +{ + union node *n1, *n2, *n3; + int tok; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (nlflag == 2 && peektoken()) + return NULL; + n1 = NULL; + for (;;) { + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2->type == NPIPE) { + n2->npipe.backgnd = 1; + } else { + if (n2->type != NREDIR) { + n3 = stalloc(sizeof(struct nredir)); + n3->nredir.n = n2; + n3->nredir.redirect = NULL; + n2 = n3; + } + n2->type = NBACKGND; + } + } + if (n1 == NULL) { + n1 = n2; + } + else { + n3 = (union node *)stalloc(sizeof (struct nbinary)); + n3->type = NSEMI; + n3->nbinary.ch1 = n1; + n3->nbinary.ch2 = n2; + n1 = n3; + } + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* fall through */ + case TNL: + if (tok == TNL) { + parseheredoc(); + if (nlflag == 1) + return n1; + } else { + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (peektoken()) + return n1; + break; + case TEOF: + if (heredoclist) + parseheredoc(); + else + pungetc(); /* push back EOF on input */ + return n1; + default: + if (nlflag == 1) + synexpect(-1); + tokpushback++; + return n1; + } + } +} + + + +static union node * +andor(void) +{ + union node *n1, *n2, *n3; + int t; + + n1 = pipeline(); + for (;;) { + if ((t = readtoken()) == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback++; + return n1; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + n2 = pipeline(); + n3 = (union node *)stalloc(sizeof (struct nbinary)); + n3->type = t; + n3->nbinary.ch1 = n1; + n3->nbinary.ch2 = n2; + n1 = n3; + } +} + + + +static union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate; + + negate = 0; + TRACE(("pipeline: entered\n")); + if (readtoken() == TNOT) { + negate = !negate; + checkkwd = CHKKWD | CHKALIAS; + } else + tokpushback++; + n1 = command(); + if (readtoken() == TPIPE) { + pipenode = (union node *)stalloc(sizeof (struct npipe)); + pipenode->type = NPIPE; + pipenode->npipe.backgnd = 0; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + pipenode->npipe.cmdlist = lp; + lp->n = n1; + do { + prev = lp; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + checkkwd = CHKNL | CHKKWD | CHKALIAS; + lp->n = command(); + prev->next = lp; + } while (readtoken() == TPIPE); + lp->next = NULL; + n1 = pipenode; + } + tokpushback++; + if (negate) { + n2 = (union node *)stalloc(sizeof (struct nnot)); + n2->type = NNOT; + n2->nnot.com = n1; + return n2; + } else + return n1; +} + + + +static union node * +command(void) +{ + union node *n1, *n2; + union node *ap, **app; + union node *cp, **cpp; + union node *redir, **rpp; + union node **rpp2; + int t; + + redir = NULL; + rpp2 = &redir; + + switch (readtoken()) { + default: + synexpect(-1); + /* NOTREACHED */ + case TIF: + n1 = (union node *)stalloc(sizeof (struct nif)); + n1->type = NIF; + n1->nif.test = list(0); + if (readtoken() != TTHEN) + synexpect(TTHEN); + n1->nif.ifpart = list(0); + n2 = n1; + while (readtoken() == TELIF) { + n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif)); + n2 = n2->nif.elsepart; + n2->type = NIF; + n2->nif.test = list(0); + if (readtoken() != TTHEN) + synexpect(TTHEN); + n2->nif.ifpart = list(0); + } + if (lasttoken == TELSE) + n2->nif.elsepart = list(0); + else { + n2->nif.elsepart = NULL; + tokpushback++; + } + t = TFI; + break; + case TWHILE: + case TUNTIL: { + int got; + n1 = (union node *)stalloc(sizeof (struct nbinary)); + n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL; + n1->nbinary.ch1 = list(0); + if ((got=readtoken()) != TDO) { +TRACE(("expecting DO got %s %s\n", tokname(got), got == TWORD ? wordtext : "")); + synexpect(TDO); + } + n1->nbinary.ch2 = list(0); + t = TDONE; + break; + } + case TFOR: + if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) + synerror("Bad for loop variable"); + n1 = (union node *)stalloc(sizeof (struct nfor)); + n1->type = NFOR; + n1->nfor.var = wordtext; + checkkwd = CHKKWD | CHKALIAS; + if (readtoken() == TIN) { + app = ≈ + while (readtoken() == TWORD) { + n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = wordtext; + n2->narg.backquote = backquotelist; + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + synexpect(-1); + } else { + n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = (char *)dolatstr; + n2->narg.backquote = NULL; + n2->narg.next = NULL; + n1->nfor.args = n2; + /* + * Newline or semicolon here is optional (but note + * that the original Bourne shell only allowed NL). + */ + if (lasttoken != TNL && lasttoken != TSEMI) + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (readtoken() != TDO) + synexpect(TDO); + n1->nfor.body = list(0); + t = TDONE; + break; + case TCASE: + n1 = (union node *)stalloc(sizeof (struct ncase)); + n1->type = NCASE; + if (readtoken() != TWORD) + synexpect(TWORD); + n1->ncase.expr = n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = wordtext; + n2->narg.backquote = backquotelist; + n2->narg.next = NULL; + do { + checkkwd = CHKKWD | CHKALIAS; + } while (readtoken() == TNL); + if (lasttoken != TIN) + synexpect(TIN); + cpp = &n1->ncase.cases; +next_case: + checkkwd = CHKNL | CHKKWD; + t = readtoken(); + while(t != TESAC) { + if (lasttoken == TLP) + readtoken(); + *cpp = cp = (union node *)stalloc(sizeof (struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + for (;;) { + *app = ap = (union node *)stalloc(sizeof (struct narg)); + ap->type = NARG; + ap->narg.text = wordtext; + ap->narg.backquote = backquotelist; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + ap->narg.next = NULL; + if (lasttoken != TRP) + synexpect(TRP); + cp->nclist.body = list(2); + + cpp = &cp->nclist.next; + + checkkwd = CHKNL | CHKKWD; + if ((t = readtoken()) != TESAC) { + if (t != TENDCASE) + synexpect(TENDCASE); + else + goto next_case; + } + } + *cpp = NULL; + goto redir; + case TLP: + n1 = (union node *)stalloc(sizeof (struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + n1->nredir.redirect = NULL; + t = TRP; + break; + case TBEGIN: + n1 = list(0); + t = TEND; + break; + case TWORD: + case TREDIR: + tokpushback++; + return simplecmd(); + } + + if (readtoken() != t) + synexpect(t); + +redir: + /* Now check for redirection which may follow command */ + checkkwd = CHKKWD | CHKALIAS; + rpp = rpp2; + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + *rpp = NULL; + if (redir) { + if (n1->type != NSUBSHELL) { + n2 = (union node *)stalloc(sizeof (struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; + } + n1->nredir.redirect = redir; + } + + return n1; +} + + +static union node * +simplecmd(void) { + union node *args, **app; + union node *n = NULL; + union node *vars, **vpp; + union node **rpp, *redir; + int savecheckkwd; + + args = NULL; + app = &args; + vars = NULL; + vpp = &vars; + redir = NULL; + rpp = &redir; + + savecheckkwd = CHKALIAS; + for (;;) { + checkkwd = savecheckkwd; + switch (readtoken()) { + case TWORD: + n = (union node *)stalloc(sizeof (struct narg)); + n->type = NARG; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + if (savecheckkwd && isassignment(wordtext)) { + *vpp = n; + vpp = &n->narg.next; + } else { + *app = n; + app = &n->narg.next; + savecheckkwd = 0; + } + break; + case TREDIR: + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + break; + case TLP: + if ( + args && app == &args->narg.next && + !vars && !redir + ) { + struct builtincmd *bcmd; + const char *name; + + /* We have a function */ + if (readtoken() != TRP) + synexpect(TRP); + name = n->narg.text; + if ( + !goodname(name) || ( + (bcmd = find_builtin(name)) && + IS_BUILTIN_SPECIAL(bcmd) + ) + ) + synerror("Bad function name"); + n->type = NDEFUN; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + n->narg.next = command(); + return n; + } + /* fall through */ + default: + tokpushback++; + goto out; + } + } +out: + *app = NULL; + *vpp = NULL; + *rpp = NULL; + n = (union node *)stalloc(sizeof (struct ncmd)); + n->type = NCMD; + n->ncmd.args = args; + n->ncmd.assign = vars; + n->ncmd.redirect = redir; + return n; +} + +static union node * +makename(void) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct narg)); + n->type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + return n; +} + +void fixredir(union node *n, const char *text, int err) +{ + TRACE(("Fix redir %s %d\n", text, err)); + if (!err) + n->ndup.vname = NULL; + + if (is_digit(text[0]) && text[1] == '\0') + n->ndup.dupfd = digit_val(text[0]); + else if (text[0] == '-' && text[1] == '\0') + n->ndup.dupfd = -1; + else { + + if (err) + synerror("Bad fd number"); + else + n->ndup.vname = makename(); + } +} + + +static void +parsefname(void) +{ + union node *n = redirnode; + + if (readtoken() != TWORD) + synexpect(-1); + if (n->type == NHERE) { + struct heredoc *here = heredoc; + struct heredoc *p; + int i; + + if (quoteflag == 0) + n->type = NXHERE; + TRACE(("Here document %d\n", n->type)); + if (! noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN) + synerror("Illegal eof marker for << redirection"); + rmescapes(wordtext); + here->eofmark = wordtext; + here->next = NULL; + if (heredoclist == NULL) + heredoclist = here; + else { + for (p = heredoclist ; p->next ; p = p->next); + p->next = here; + } + } else if (n->type == NTOFD || n->type == NFROMFD) { + fixredir(n, wordtext, 0); + } else { + n->nfile.fname = makename(); + } +} + + +/* + * Input any here documents. + */ + +static void +parseheredoc(void) +{ + struct heredoc *here; + union node *n; + + here = heredoclist; + heredoclist = 0; + + while (here) { + if (needprompt) { + setprompt(2); + } + readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, + here->eofmark, here->striptabs); + n = (union node *)stalloc(sizeof (struct narg)); + n->narg.type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + here->here->nhere.doc = n; + here = here->next; + } +} + +static char peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback++; + return tokname_array[t][0]; +} + +static int +readtoken(void) +{ + int t; +#if DEBUG + int alreadyseen = tokpushback; +#endif + +#ifdef CONFIG_ASH_ALIAS +top: +#endif + + t = xxreadtoken(); + + /* + * eat newlines + */ + if (checkkwd & CHKNL) { + while (t == TNL) { + parseheredoc(); + t = xxreadtoken(); + } + } + + if (t != TWORD || quoteflag) { + goto out; + } + + /* + * check for keywords + */ + if (checkkwd & CHKKWD) { + const char *const *pp; + + if ((pp = findkwd(wordtext))) { + lasttoken = t = pp - tokname_array; + TRACE(("keyword %s recognized\n", tokname(t))); + goto out; + } + } + + if (checkkwd & CHKALIAS) { +#ifdef CONFIG_ASH_ALIAS + struct alias *ap; + if ((ap = lookupalias(wordtext, 1)) != NULL) { + if (*ap->val) { + pushstring(ap->val, ap); + } + goto top; + } +#endif + } +out: + checkkwd = 0; +#if DEBUG + if (!alreadyseen) + TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); + else + TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); +#endif + return t; +} + + +/* + * Read the next input token. + * If the token is a word, we set backquotelist to the list of cmds in + * backquotes. We set quoteflag to true if any part of the word was + * quoted. + * If the token is TREDIR, then we set redirnode to a structure containing + * the redirection. + * In all cases, the variable startlinno is set to the number of the line + * on which the token starts. + * + * [Change comment: here documents and internal procedures] + * [Readtoken shouldn't have any arguments. Perhaps we should make the + * word parsing code into a separate routine. In this case, readtoken + * doesn't need to have any internal procedures, but parseword does. + * We could also make parseoperator in essence the main routine, and + * have parseword (readtoken1?) handle both words and redirection.] + */ + +#define NEW_xxreadtoken +#ifdef NEW_xxreadtoken + +/* singles must be first! */ +static const char xxreadtoken_chars[7] = { '\n', '(', ')', '&', '|', ';', 0 }; + +static const char xxreadtoken_tokens[] = { + TNL, TLP, TRP, /* only single occurrence allowed */ + TBACKGND, TPIPE, TSEMI, /* if single occurrence */ + TEOF, /* corresponds to trailing nul */ + TAND, TOR, TENDCASE, /* if double occurrence */ +}; + +#define xxreadtoken_doubles \ + (sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars)) +#define xxreadtoken_singles \ + (sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1) + +static int xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + + if ((c != ' ') && (c != '\t') +#ifdef CONFIG_ASH_ALIAS + && (c != PEOA) +#endif + ) { + if (c == '#') { + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + } else if (c == '\\') { + if (pgetc() != '\n') { + pungetc(); + goto READTOKEN1; + } + startlinno = ++plinno; + if (doprompt) + setprompt(2); + } else { + const char *p + = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1; + + if (c != PEOF) { + if (c == '\n') { + plinno++; + needprompt = doprompt; + } + + p = strchr(xxreadtoken_chars, c); + if (p == NULL) { + READTOKEN1: + return readtoken1(c, BASESYNTAX, (char *) NULL, 0); + } + + if (p - xxreadtoken_chars >= xxreadtoken_singles) { + if (pgetc() == *p) { /* double occurrence? */ + p += xxreadtoken_doubles + 1; + } else { + pungetc(); + } + } + } + + return lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars]; + } + } + } +} + + +#else +#define RETURN(token) return lasttoken = token + +static int +xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + switch (c) { + case ' ': case '\t': +#ifdef CONFIG_ASH_ALIAS + case PEOA: +#endif + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + continue; + case '\\': + if (pgetc() == '\n') { + startlinno = ++plinno; + if (doprompt) + setprompt(2); + continue; + } + pungetc(); + goto breakloop; + case '\n': + plinno++; + needprompt = doprompt; + RETURN(TNL); + case PEOF: + RETURN(TEOF); + case '&': + if (pgetc() == '&') + RETURN(TAND); + pungetc(); + RETURN(TBACKGND); + case '|': + if (pgetc() == '|') + RETURN(TOR); + pungetc(); + RETURN(TPIPE); + case ';': + if (pgetc() == ';') + RETURN(TENDCASE); + pungetc(); + RETURN(TSEMI); + case '(': + RETURN(TLP); + case ')': + RETURN(TRP); + default: + goto breakloop; + } + } +breakloop: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); +#undef RETURN +} +#endif /* NEW_xxreadtoken */ + + +/* + * If eofmark is NULL, read a word or a redirection symbol. If eofmark + * is not NULL, read a here document. In the latter case, eofmark is the + * word which marks the end of the document and striptabs is true if + * leading tabs should be stripped from the document. The argument firstc + * is the first character of the input token or document. + * + * Because C does not have internal subroutines, I have simulated them + * using goto's to implement the subroutine linkage. The following macros + * will run code that appears at the end of readtoken1. + */ + +#define CHECKEND() {goto checkend; checkend_return:;} +#define PARSEREDIR() {goto parseredir; parseredir_return:;} +#define PARSESUB() {goto parsesub; parsesub_return:;} +#define PARSEBACKQOLD() {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;} +#define PARSEBACKQNEW() {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;} +#define PARSEARITH() {goto parsearith; parsearith_return:;} + +static int +readtoken1(int firstc, int syntax, char *eofmark, int striptabs) +{ + int c = firstc; + char *out; + int len; + char line[EOFMARKLEN + 1]; + struct nodelist *bqlist = 0; + int quotef = 0; + int dblquote = 0; + int varnest = 0; /* levels of variables expansion */ + int arinest = 0; /* levels of arithmetic expansion */ + int parenlevel = 0; /* levels of parens in arithmetic */ + int dqvarnest = 0; /* levels of variables expansion within double quotes */ + int oldstyle = 0; + int prevsyntax = 0; /* syntax before arithmetic */ +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &out; + (void) "ef; + (void) &dblquote; + (void) &varnest; + (void) &arinest; + (void) &parenlevel; + (void) &dqvarnest; + (void) &oldstyle; + (void) &prevsyntax; + (void) &syntax; +#endif + + startlinno = plinno; + dblquote = 0; + if (syntax == DQSYNTAX) + dblquote = 1; + quotef = 0; + bqlist = NULL; + varnest = 0; + arinest = 0; + parenlevel = 0; + dqvarnest = 0; + + STARTSTACKSTR(out); + loop: { /* for each line, until end of word */ + CHECKEND(); /* set c to PEOF if at end of here document */ + for (;;) { /* until end of line or end of word */ + CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ + switch(SIT(c, syntax)) { + case CNL: /* '\n' */ + if (syntax == BASESYNTAX) + goto endword; /* exit outer loop */ + USTPUTC(c, out); + plinno++; + if (doprompt) + setprompt(2); + c = pgetc(); + goto loop; /* continue outer loop */ + case CWORD: + USTPUTC(c, out); + break; + case CCTL: + if (eofmark == NULL || dblquote) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + break; + case CBACK: /* backslash */ + c = pgetc2(); + if (c == PEOF) { + USTPUTC(CTLESC, out); + USTPUTC('\\', out); + pungetc(); + } else if (c == '\n') { + if (doprompt) + setprompt(2); + } else { + if (dblquote && + c != '\\' && c != '`' && + c != '$' && ( + c != '"' || + eofmark != NULL) + ) { + USTPUTC(CTLESC, out); + USTPUTC('\\', out); + } + if (SIT(c, SQSYNTAX) == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + quotef++; + } + break; + case CSQUOTE: + syntax = SQSYNTAX; +quotemark: + if (eofmark == NULL) { + USTPUTC(CTLQUOTEMARK, out); + } + break; + case CDQUOTE: + syntax = DQSYNTAX; + dblquote = 1; + goto quotemark; + case CENDQUOTE: + if (eofmark != NULL && arinest == 0 && + varnest == 0) { + USTPUTC(c, out); + } else { + if (dqvarnest == 0) { + syntax = BASESYNTAX; + dblquote = 0; + } + quotef++; + goto quotemark; + } + break; + case CVAR: /* '$' */ + PARSESUB(); /* parse substitution */ + break; + case CENDVAR: /* '}' */ + if (varnest > 0) { + varnest--; + if (dqvarnest > 0) { + dqvarnest--; + } + USTPUTC(CTLENDVAR, out); + } else { + USTPUTC(c, out); + } + break; +#ifdef CONFIG_ASH_MATH_SUPPORT + case CLP: /* '(' in arithmetic */ + parenlevel++; + USTPUTC(c, out); + break; + case CRP: /* ')' in arithmetic */ + if (parenlevel > 0) { + USTPUTC(c, out); + --parenlevel; + } else { + if (pgetc() == ')') { + if (--arinest == 0) { + USTPUTC(CTLENDARI, out); + syntax = prevsyntax; + if (syntax == DQSYNTAX) + dblquote = 1; + else + dblquote = 0; + } else + USTPUTC(')', out); + } else { + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); + USTPUTC(')', out); + } + } + break; +#endif + case CBQUOTE: /* '`' */ + PARSEBACKQOLD(); + break; + case CENDFILE: + goto endword; /* exit outer loop */ + case CIGN: + break; + default: + if (varnest == 0) + goto endword; /* exit outer loop */ +#ifdef CONFIG_ASH_ALIAS + if (c != PEOA) +#endif + USTPUTC(c, out); + + } + c = pgetc_macro(); + } + } +endword: +#ifdef CONFIG_ASH_MATH_SUPPORT + if (syntax == ARISYNTAX) + synerror("Missing '))'"); +#endif + if (syntax != BASESYNTAX && ! parsebackquote && eofmark == NULL) + synerror("Unterminated quoted string"); + if (varnest != 0) { + startlinno = plinno; + /* { */ + synerror("Missing '}'"); + } + USTPUTC('\0', out); + len = out - (char *)stackblock(); + out = stackblock(); + if (eofmark == NULL) { + if ((c == '>' || c == '<') + && quotef == 0 + && len <= 2 + && (*out == '\0' || is_digit(*out))) { + PARSEREDIR(); + return lasttoken = TREDIR; + } else { + pungetc(); + } + } + quoteflag = quotef; + backquotelist = bqlist; + grabstackblock(len); + wordtext = out; + return lasttoken = TWORD; +/* end of readtoken routine */ + + + +/* + * Check to see whether we are at the end of the here document. When this + * is called, c is set to the first character of the next input line. If + * we are at the end of the here document, this routine sets the c to PEOF. + */ + +checkend: { + if (eofmark) { +#ifdef CONFIG_ASH_ALIAS + if (c == PEOA) { + c = pgetc2(); + } +#endif + if (striptabs) { + while (c == '\t') { + c = pgetc2(); + } + } + if (c == *eofmark) { + if (pfgets(line, sizeof line) != NULL) { + char *p, *q; + + p = line; + for (q = eofmark + 1 ; *q && *p == *q ; p++, q++); + if (*p == '\n' && *q == '\0') { + c = PEOF; + plinno++; + needprompt = doprompt; + } else { + pushstring(line, NULL); + } + } + } + } + goto checkend_return; +} + + +/* + * Parse a redirection operator. The variable "out" points to a string + * specifying the fd to be redirected. The variable "c" contains the + * first character of the redirection operator. + */ + +parseredir: { + char fd = *out; + union node *np; + + np = (union node *)stalloc(sizeof (struct nfile)); + if (c == '>') { + np->nfile.fd = 1; + c = pgetc(); + if (c == '>') + np->type = NAPPEND; + else if (c == '|') + np->type = NCLOBBER; + else if (c == '&') + np->type = NTOFD; + else { + np->type = NTO; + pungetc(); + } + } else { /* c == '<' */ + np->nfile.fd = 0; + switch (c = pgetc()) { + case '<': + if (sizeof (struct nfile) != sizeof (struct nhere)) { + np = (union node *)stalloc(sizeof (struct nhere)); + np->nfile.fd = 0; + } + np->type = NHERE; + heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc)); + heredoc->here = np; + if ((c = pgetc()) == '-') { + heredoc->striptabs = 1; + } else { + heredoc->striptabs = 0; + pungetc(); + } + break; + + case '&': + np->type = NFROMFD; + break; + + case '>': + np->type = NFROMTO; + break; + + default: + np->type = NFROM; + pungetc(); + break; + } + } + if (fd != '\0') + np->nfile.fd = digit_val(fd); + redirnode = np; + goto parseredir_return; +} + + +/* + * Parse a substitution. At this point, we have read the dollar sign + * and nothing else. + */ + +parsesub: { + int subtype; + int typeloc; + int flags; + char *p; + static const char types[] = "}-+?="; + + c = pgetc(); + if ( + c <= PEOA_OR_PEOF || + (c != '(' && c != '{' && !is_name(c) && !is_special(c)) + ) { + USTPUTC('$', out); + pungetc(); + } else if (c == '(') { /* $(command) or $((arith)) */ + if (pgetc() == '(') { +#ifdef CONFIG_ASH_MATH_SUPPORT + PARSEARITH(); +#else + synerror("We unsupport $((arith))"); +#endif + } else { + pungetc(); + PARSEBACKQNEW(); + } + } else { + USTPUTC(CTLVAR, out); + typeloc = out - (char *)stackblock(); + USTPUTC(VSNORMAL, out); + subtype = VSNORMAL; + if (c == '{') { + c = pgetc(); + if (c == '#') { + if ((c = pgetc()) == '}') + c = '#'; + else + subtype = VSLENGTH; + } + else + subtype = 0; + } + if (c > PEOA_OR_PEOF && is_name(c)) { + do { + STPUTC(c, out); + c = pgetc(); + } while (c > PEOA_OR_PEOF && is_in_name(c)); + } else if (is_digit(c)) { + do { + STPUTC(c, out); + c = pgetc(); + } while (is_digit(c)); + } + else if (is_special(c)) { + USTPUTC(c, out); + c = pgetc(); + } + else +badsub: synerror("Bad substitution"); + + STPUTC('=', out); + flags = 0; + if (subtype == 0) { + switch (c) { + case ':': + flags = VSNUL; + c = pgetc(); + /*FALLTHROUGH*/ + default: + p = strchr(types, c); + if (p == NULL) + goto badsub; + subtype = p - types + VSNORMAL; + break; + case '%': + case '#': + { + int cc = c; + subtype = c == '#' ? VSTRIMLEFT : + VSTRIMRIGHT; + c = pgetc(); + if (c == cc) + subtype++; + else + pungetc(); + break; + } + } + } else { + pungetc(); + } + if (dblquote || arinest) + flags |= VSQUOTE; + *((char *)stackblock() + typeloc) = subtype | flags; + if (subtype != VSNORMAL) { + varnest++; + if (dblquote || arinest) { + dqvarnest++; + } + } + } + goto parsesub_return; +} + + +/* + * Called to parse command substitutions. Newstyle is set if the command + * is enclosed inside $(...); nlpp is a pointer to the head of the linked + * list of commands (passed by reference), and savelen is the number of + * characters on the top of the stack which must be preserved. + */ + +parsebackq: { + struct nodelist **nlpp; + int savepbq; + union node *n; + char *volatile str; + struct jmploc jmploc; + struct jmploc *volatile savehandler; + size_t savelen; + int saveprompt = 0; +#ifdef __GNUC__ + (void) &saveprompt; +#endif + + savepbq = parsebackquote; + if (setjmp(jmploc.loc)) { + if (str) + ckfree(str); + parsebackquote = 0; + handler = savehandler; + longjmp(handler->loc, 1); + } + INTOFF; + str = NULL; + savelen = out - (char *)stackblock(); + if (savelen > 0) { + str = ckmalloc(savelen); + memcpy(str, stackblock(), savelen); + } + savehandler = handler; + handler = &jmploc; + INTON; + if (oldstyle) { + /* We must read until the closing backquote, giving special + treatment to some slashes, and then push the string and + reread it as input, interpreting it normally. */ + char *pout; + int pc; + size_t psavelen; + char *pstr; + + + STARTSTACKSTR(pout); + for (;;) { + if (needprompt) { + setprompt(2); + } + switch (pc = pgetc()) { + case '`': + goto done; + + case '\\': + if ((pc = pgetc()) == '\n') { + plinno++; + if (doprompt) + setprompt(2); + /* + * If eating a newline, avoid putting + * the newline into the new character + * stream (via the STPUTC after the + * switch). + */ + continue; + } + if (pc != '\\' && pc != '`' && pc != '$' + && (!dblquote || pc != '"')) + STPUTC('\\', pout); + if (pc > PEOA_OR_PEOF) { + break; + } + /* fall through */ + + case PEOF: +#ifdef CONFIG_ASH_ALIAS + case PEOA: +#endif + startlinno = plinno; + synerror("EOF in backquote substitution"); + + case '\n': + plinno++; + needprompt = doprompt; + break; + + default: + break; + } + STPUTC(pc, pout); + } +done: + STPUTC('\0', pout); + psavelen = pout - (char *)stackblock(); + if (psavelen > 0) { + pstr = grabstackstr(pout); + setinputstring(pstr); + } + } + nlpp = &bqlist; + while (*nlpp) + nlpp = &(*nlpp)->next; + *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + (*nlpp)->next = NULL; + parsebackquote = oldstyle; + + if (oldstyle) { + saveprompt = doprompt; + doprompt = 0; + } + + n = list(2); + + if (oldstyle) + doprompt = saveprompt; + else { + if (readtoken() != TRP) + synexpect(TRP); + } + + (*nlpp)->n = n; + if (oldstyle) { + /* + * Start reading from old file again, ignoring any pushed back + * tokens left from the backquote parsing + */ + popfile(); + tokpushback = 0; + } + while (stackblocksize() <= savelen) + growstackblock(); + STARTSTACKSTR(out); + if (str) { + memcpy(out, str, savelen); + STADJUST(savelen, out); + INTOFF; + ckfree(str); + str = NULL; + INTON; + } + parsebackquote = savepbq; + handler = savehandler; + if (arinest || dblquote) + USTPUTC(CTLBACKQ | CTLQUOTE, out); + else + USTPUTC(CTLBACKQ, out); + if (oldstyle) + goto parsebackq_oldreturn; + else + goto parsebackq_newreturn; +} + +#ifdef CONFIG_ASH_MATH_SUPPORT +/* + * Parse an arithmetic expansion (indicate start of one and set state) + */ +parsearith: { + + if (++arinest == 1) { + prevsyntax = syntax; + syntax = ARISYNTAX; + USTPUTC(CTLARI, out); + if (dblquote) + USTPUTC('"',out); + else + USTPUTC(' ',out); + } else { + /* + * we collapse embedded arithmetic expansion to + * parenthesis, which should be equivalent + */ + USTPUTC('(', out); + } + goto parsearith_return; +} +#endif + +} /* end of readtoken */ + + + +/* + * Returns true if the text contains nothing to expand (no dollar signs + * or backquotes). + */ + +static int +noexpand(char *text) +{ + char *p; + char c; + + p = text; + while ((c = *p++) != '\0') { + if (c == CTLQUOTEMARK) + continue; + if (c == CTLESC) + p++; + else if (SIT(c, BASESYNTAX) == CCTL) + return 0; + } + return 1; +} + + +/* + * Return of a legal variable name (a letter or underscore followed by zero or + * more letters, underscores, and digits). + */ + +static char * +endofname(const char *name) +{ + char *p; + + p = (char *) name; + if (! is_name(*p)) + return p; + while (*++p) { + if (! is_in_name(*p)) + break; + } + return p; +} + + +/* + * Called when an unexpected token is read during the parse. The argument + * is the token that is expected, or -1 if more than one type of token can + * occur at this point. + */ + +static void synexpect(int token) +{ + char msg[64]; + int l; + + l = sprintf(msg, "%s unexpected", tokname(lasttoken)); + if (token >= 0) + sprintf(msg + l, " (expecting %s)", tokname(token)); + synerror(msg); + /* NOTREACHED */ +} + +static void +synerror(const char *msg) +{ + sh_error("Syntax error: %s", msg); + /* NOTREACHED */ +} + + +/* + * called by editline -- any expansions to the prompt + * should be added here. + */ + +#ifdef CONFIG_ASH_EXPAND_PRMT +static const char * +expandstr(const char *ps) +{ + union node n; + + /* XXX Fix (char *) cast. */ + setinputstring((char *)ps); + readtoken1(pgetc(), DQSYNTAX, nullstr, 0); + popfile(); + + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.backquote = backquotelist; + + expandarg(&n, NULL, 0); + return stackblock(); +} +#endif + +static void setprompt(int whichprompt) +{ + const char *prompt; +#ifdef CONFIG_ASH_EXPAND_PRMT + struct stackmark smark; +#endif + + needprompt = 0; + + switch (whichprompt) { + case 1: + prompt = ps1val(); + break; + case 2: + prompt = ps2val(); + break; + default: /* 0 */ + prompt = nullstr; + } +#ifdef CONFIG_ASH_EXPAND_PRMT + setstackmark(&smark); + stalloc(stackblocksize()); +#endif + putprompt(expandstr(prompt)); +#ifdef CONFIG_ASH_EXPAND_PRMT + popstackmark(&smark); +#endif +} + + +static const char *const *findkwd(const char *s) +{ + return bsearch(s, tokname_array + KWDOFFSET, + (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET, + sizeof(const char *), pstrcmp); +} + +/* redir.c */ + +/* + * Code for dealing with input/output redirection. + */ + +#define EMPTY -2 /* marks an unused slot in redirtab */ +#ifndef PIPE_BUF +# define PIPESIZE 4096 /* amount of buffering in a pipe */ +#else +# define PIPESIZE PIPE_BUF +#endif + +/* + * Open a file in noclobber mode. + * The code was copied from bash. + */ +static int noclobberopen(const char *fname) +{ + int r, fd; + struct stat finfo, finfo2; + + /* + * If the file exists and is a regular file, return an error + * immediately. + */ + r = stat(fname, &finfo); + if (r == 0 && S_ISREG(finfo.st_mode)) { + errno = EEXIST; + return -1; + } + + /* + * If the file was not present (r != 0), make sure we open it + * exclusively so that if it is created before we open it, our open + * will fail. Make sure that we do not truncate an existing file. + * Note that we don't turn on O_EXCL unless the stat failed -- if the + * file was not a regular file, we leave O_EXCL off. + */ + if (r != 0) + return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); + fd = open(fname, O_WRONLY|O_CREAT, 0666); + + /* If the open failed, return the file descriptor right away. */ + if (fd < 0) + return fd; + + /* + * OK, the open succeeded, but the file may have been changed from a + * non-regular file to a regular file between the stat and the open. + * We are assuming that the O_EXCL open handles the case where FILENAME + * did not exist and is symlinked to an existing file between the stat + * and open. + */ + + /* + * If we can open it and fstat the file descriptor, and neither check + * revealed that it was a regular file, and the file has not been + * replaced, return the file descriptor. + */ + if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) && + finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino) + return fd; + + /* The file has been replaced. badness. */ + close(fd); + errno = EEXIST; + return -1; +} + +/* + * Handle here documents. Normally we fork off a process to write the + * data to a pipe. If the document is short, we can stuff the data in + * the pipe without forking. + */ + +static int openhere(union node *redir) +{ + int pip[2]; + size_t len = 0; + + if (pipe(pip) < 0) + sh_error("Pipe call failed"); + if (redir->type == NHERE) { + len = strlen(redir->nhere.doc->narg.text); + if (len <= PIPESIZE) { + full_write(pip[1], redir->nhere.doc->narg.text, len); + goto out; + } + } + if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { + close(pip[0]); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGHUP, SIG_IGN); +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + signal(SIGPIPE, SIG_DFL); + if (redir->type == NHERE) + full_write(pip[1], redir->nhere.doc->narg.text, len); + else + expandhere(redir->nhere.doc, pip[1]); + _exit(0); + } +out: + close(pip[1]); + return pip[0]; +} + +static int +openredirect(union node *redir) +{ + char *fname; + int f; + + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDONLY)) < 0) + goto eopen; + break; + case NFROMTO: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666)) < 0) + goto ecreate; + break; + case NTO: + /* Take care of noclobber mode. */ + if (Cflag) { + fname = redir->nfile.expfname; + if ((f = noclobberopen(fname)) < 0) + goto ecreate; + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) + goto ecreate; + break; + case NAPPEND: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0) + goto ecreate; + break; + default: +#if DEBUG + abort(); +#endif + /* Fall through to eliminate warning. */ + case NTOFD: + case NFROMFD: + f = -1; + break; + case NHERE: + case NXHERE: + f = openhere(redir); + break; + } + + return f; +ecreate: + sh_error("cannot create %s: %s", fname, errmsg(errno, E_CREAT)); +eopen: + sh_error("cannot open %s: %s", fname, errmsg(errno, E_OPEN)); +} + +static void dupredirect(union node *redir, int f) +{ + int fd = redir->nfile.fd; + + if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + copyfd(redir->ndup.dupfd, fd); + } + return; + } + + if (f != fd) { + copyfd(f, fd); + close(f); + } + return; +} + +/* + * Process a list of redirection commands. If the REDIR_PUSH flag is set, + * old file descriptors are stashed away so that the redirection can be + * undone by calling popredir. If the REDIR_BACKQ flag is set, then the + * standard output, and the standard error if it becomes a duplicate of + * stdout, is saved in memory. + */ + +static void +redirect(union node *redir, int flags) +{ + union node *n; + struct redirtab *sv; + int i; + int fd; + int newfd; + int *p; + nullredirs++; + if (!redir) { + return; + } + sv = NULL; + INTOFF; + if (flags & REDIR_PUSH) { + struct redirtab *q; + q = ckmalloc(sizeof (struct redirtab)); + q->next = redirlist; + redirlist = q; + q->nullredirs = nullredirs - 1; + for (i = 0 ; i < 10 ; i++) + q->renamed[i] = EMPTY; + nullredirs = 0; + sv = q; + } + n = redir; + do { + fd = n->nfile.fd; + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) && + n->ndup.dupfd == fd) + continue; /* redirect from/to same file descriptor */ + + newfd = openredirect(n); + if (fd == newfd) + continue; + if (sv && *(p = &sv->renamed[fd]) == EMPTY) { + i = fcntl(fd, F_DUPFD, 10); + + if (i == -1) { + i = errno; + if (i != EBADF) { + close(newfd); + errno = i; + sh_error("%d: %m", fd); + /* NOTREACHED */ + } + } else { + *p = i; + close(fd); + } + } else { + close(fd); + } + dupredirect(n, newfd); + } while ((n = n->nfile.next)); + INTON; + if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0) + preverrout_fd = sv->renamed[2]; +} + + +/* + * Undo the effects of the last redirection. + */ + +void +popredir(int drop) +{ + struct redirtab *rp; + int i; + + if (--nullredirs >= 0) + return; + INTOFF; + rp = redirlist; + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] != EMPTY) { + if (!drop) { + close(i); + copyfd(rp->renamed[i], i); + } + close(rp->renamed[i]); + } + } + redirlist = rp->next; + nullredirs = rp->nullredirs; + ckfree(rp); + INTON; +} + +/* + * Undo all redirections. Called on error or interrupt. + */ + +/* + * Discard all saved file descriptors. + */ + +void +clearredir(int drop) +{ + for (;;) { + nullredirs = 0; + if (!redirlist) + break; + popredir(drop); + } +} + + +/* + * Copy a file descriptor to be >= to. Returns -1 + * if the source file descriptor is closed, EMPTY if there are no unused + * file descriptors left. + */ + +int +copyfd(int from, int to) +{ + int newfd; + + newfd = fcntl(from, F_DUPFD, to); + if (newfd < 0) { + if (errno == EMFILE) + return EMPTY; + else + sh_error("%d: %m", from); + } + return newfd; +} + + +int +redirectsafe(union node *redir, int flags) +{ + int err; + volatile int saveint; + struct jmploc *volatile savehandler = handler; + struct jmploc jmploc; + + SAVEINT(saveint); + if (!(err = setjmp(jmploc.loc) * 2)) { + handler = &jmploc; + redirect(redir, flags); + } + handler = savehandler; + if (err && exception != EXERROR) + longjmp(handler->loc, 1); + RESTOREINT(saveint); + return err; +} + +/* show.c */ + +#if DEBUG +static void shtree(union node *, int, char *, FILE*); +static void shcmd(union node *, FILE *); +static void sharg(union node *, FILE *); +static void indent(int, char *, FILE *); +static void trstring(char *); + + +void +showtree(union node *n) +{ + trputs("showtree called\n"); + shtree(n, 1, NULL, stdout); +} + + +static void +shtree(union node *n, int ind, char *pfx, FILE *fp) +{ + struct nodelist *lp; + const char *s; + + if (n == NULL) + return; + + indent(ind, pfx, fp); + switch(n->type) { + case NSEMI: + s = "; "; + goto binop; + case NAND: + s = " && "; + goto binop; + case NOR: + s = " || "; +binop: + shtree(n->nbinary.ch1, ind, NULL, fp); + /* if (ind < 0) */ + fputs(s, fp); + shtree(n->nbinary.ch2, ind, NULL, fp); + break; + case NCMD: + shcmd(n, fp); + if (ind >= 0) + putc('\n', fp); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + shcmd(lp->n, fp); + if (lp->next) + fputs(" | ", fp); + } + if (n->npipe.backgnd) + fputs(" &", fp); + if (ind >= 0) + putc('\n', fp); + break; + default: + fprintf(fp, "<node type %d>", n->type); + if (ind >= 0) + putc('\n', fp); + break; + } +} + + +static void +shcmd(union node *cmd, FILE *fp) +{ + union node *np; + int first; + const char *s; + int dftfd; + + first = 1; + for (np = cmd->ncmd.args ; np ; np = np->narg.next) { + if (! first) + putchar(' '); + sharg(np, fp); + first = 0; + } + for (np = cmd->ncmd.redirect ; np ; np = np->nfile.next) { + if (! first) + putchar(' '); + switch (np->nfile.type) { + case NTO: s = ">"; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NAPPEND: s = ">>"; dftfd = 1; break; + case NTOFD: s = ">&"; dftfd = 1; break; + case NFROM: s = "<"; dftfd = 0; break; + case NFROMFD: s = "<&"; dftfd = 0; break; + case NFROMTO: s = "<>"; dftfd = 0; break; + default: s = "*error*"; dftfd = 0; break; + } + if (np->nfile.fd != dftfd) + fprintf(fp, "%d", np->nfile.fd); + fputs(s, fp); + if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { + fprintf(fp, "%d", np->ndup.dupfd); + } else { + sharg(np->nfile.fname, fp); + } + first = 0; + } +} + + + +static void +sharg(union node *arg, FILE *fp) +{ + char *p; + struct nodelist *bqlist; + int subtype; + + if (arg->type != NARG) { + out1fmt("<node type %d>\n", arg->type); + abort(); + } + bqlist = arg->narg.backquote; + for (p = arg->narg.text ; *p ; p++) { + switch (*p) { + case CTLESC: + putc(*++p, fp); + break; + case CTLVAR: + putc('$', fp); + putc('{', fp); + subtype = *++p; + if (subtype == VSLENGTH) + putc('#', fp); + + while (*p != '=') + putc(*p++, fp); + + if (subtype & VSNUL) + putc(':', fp); + + switch (subtype & VSTYPE) { + case VSNORMAL: + putc('}', fp); + break; + case VSMINUS: + putc('-', fp); + break; + case VSPLUS: + putc('+', fp); + break; + case VSQUESTION: + putc('?', fp); + break; + case VSASSIGN: + putc('=', fp); + break; + case VSTRIMLEFT: + putc('#', fp); + break; + case VSTRIMLEFTMAX: + putc('#', fp); + putc('#', fp); + break; + case VSTRIMRIGHT: + putc('%', fp); + break; + case VSTRIMRIGHTMAX: + putc('%', fp); + putc('%', fp); + break; + case VSLENGTH: + break; + default: + out1fmt("<subtype %d>", subtype); + } + break; + case CTLENDVAR: + putc('}', fp); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + putc('$', fp); + putc('(', fp); + shtree(bqlist->n, -1, NULL, fp); + putc(')', fp); + break; + default: + putc(*p, fp); + break; + } + } +} + + +static void +indent(int amount, char *pfx, FILE *fp) +{ + int i; + + for (i = 0 ; i < amount ; i++) { + if (pfx && i == amount - 1) + fputs(pfx, fp); + putc('\t', fp); + } +} + + + +/* + * Debugging stuff. + */ + + +FILE *tracefile; + + +void +trputc(int c) +{ + if (debug != 1) + return; + putc(c, tracefile); +} + +void +trace(const char *fmt, ...) +{ + va_list va; + + if (debug != 1) + return; + va_start(va, fmt); + (void) vfprintf(tracefile, fmt, va); + va_end(va); +} + +void +tracev(const char *fmt, va_list va) +{ + if (debug != 1) + return; + (void) vfprintf(tracefile, fmt, va); +} + + +void +trputs(const char *s) +{ + if (debug != 1) + return; + fputs(s, tracefile); +} + + +static void +trstring(char *s) +{ + char *p; + char c; + + if (debug != 1) + return; + putc('"', tracefile); + for (p = s ; *p ; p++) { + switch (*p) { + case '\n': c = 'n'; goto backslash; + case '\t': c = 't'; goto backslash; + case '\r': c = 'r'; goto backslash; + case '"': c = '"'; goto backslash; + case '\\': c = '\\'; goto backslash; + case CTLESC: c = 'e'; goto backslash; + case CTLVAR: c = 'v'; goto backslash; + case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; + case CTLBACKQ: c = 'q'; goto backslash; + case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; +backslash: putc('\\', tracefile); + putc(c, tracefile); + break; + default: + if (*p >= ' ' && *p <= '~') + putc(*p, tracefile); + else { + putc('\\', tracefile); + putc(*p >> 6 & 03, tracefile); + putc(*p >> 3 & 07, tracefile); + putc(*p & 07, tracefile); + } + break; + } + } + putc('"', tracefile); +} + + +void +trargs(char **ap) +{ + if (debug != 1) + return; + while (*ap) { + trstring(*ap++); + if (*ap) + putc(' ', tracefile); + else + putc('\n', tracefile); + } +} + + +void +opentrace(void) +{ + char s[100]; +#ifdef O_APPEND + int flags; +#endif + + if (debug != 1) { + if (tracefile) + fflush(tracefile); + /* leave open because libedit might be using it */ + return; + } + scopy("./trace", s); + if (tracefile) { + if (!freopen(s, "a", tracefile)) { + fprintf(stderr, "Can't re-open %s\n", s); + debug = 0; + return; + } + } else { + if ((tracefile = fopen(s, "a")) == NULL) { + fprintf(stderr, "Can't open %s\n", s); + debug = 0; + return; + } + } +#ifdef O_APPEND + if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0) + fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); +#endif + setlinebuf(tracefile); + fputs("\nTracing started.\n", tracefile); +} +#endif /* DEBUG */ + + +/* trap.c */ + +/* + * Sigmode records the current value of the signal handlers for the various + * modes. A value of zero means that the current handler is not known. + * S_HARD_IGN indicates that the signal was ignored on entry to the shell, + */ + +#define S_DFL 1 /* default signal handling (SIG_DFL) */ +#define S_CATCH 2 /* signal is caught */ +#define S_IGN 3 /* signal is ignored (SIG_IGN) */ +#define S_HARD_IGN 4 /* signal is ignored permenantly */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + + + +/* + * The trap builtin. + */ + +int +trapcmd(int argc, char **argv) +{ + char *action; + char **ap; + int signo; + + nextopt(nullstr); + ap = argptr; + if (!*ap) { + for (signo = 0 ; signo < NSIG ; signo++) { + if (trap[signo] != NULL) { + const char *sn; + + sn = get_signame(signo); + out1fmt("trap -- %s %s\n", + single_quote(trap[signo]), sn); + } + } + return 0; + } + if (!ap[1]) + action = NULL; + else + action = *ap++; + while (*ap) { + if ((signo = get_signum(*ap)) < 0) + sh_error("%s: bad trap", *ap); + INTOFF; + if (action) { + if (action[0] == '-' && action[1] == '\0') + action = NULL; + else + action = savestr(action); + } + if (trap[signo]) + ckfree(trap[signo]); + trap[signo] = action; + if (signo != 0) + setsignal(signo); + INTON; + ap++; + } + return 0; +} + + +/* + * Clear traps on a fork. + */ + +void +clear_traps(void) +{ + char **tp; + + for (tp = trap ; tp < &trap[NSIG] ; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INTOFF; + ckfree(*tp); + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + INTON; + } + } +} + + +/* + * Set the signal handler for the specified signal. The routine figures + * out what it should be set to. + */ + +void +setsignal(int signo) +{ + int action; + char *t, tsig; + struct sigaction act; + + if ((t = trap[signo]) == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + if (rootshell && action == S_DFL) { + switch (signo) { + case SIGINT: + if (iflag || minusc || sflag == 0) + action = S_CATCH; + break; + case SIGQUIT: +#if DEBUG + if (debug) + break; +#endif + /* FALLTHROUGH */ + case SIGTERM: + if (iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (mflag) + action = S_IGN; + break; +#endif + } + } + + t = &sigmode[signo - 1]; + tsig = *t; + if (tsig == 0) { + /* + * current setting unknown + */ + if (sigaction(signo, 0, &act) == -1) { + /* + * Pretend it worked; maybe we should give a warning + * here, but other shells don't. We don't alter + * sigmode, so that we retry every time. + */ + return; + } + if (act.sa_handler == SIG_IGN) { + if (mflag && (signo == SIGTSTP || + signo == SIGTTIN || signo == SIGTTOU)) { + tsig = S_IGN; /* don't hard ignore these */ + } else + tsig = S_HARD_IGN; + } else { + tsig = S_RESET; /* force to be set */ + } + } + if (tsig == S_HARD_IGN || tsig == action) + return; + switch (action) { + case S_CATCH: + act.sa_handler = onsig; + break; + case S_IGN: + act.sa_handler = SIG_IGN; + break; + default: + act.sa_handler = SIG_DFL; + } + *t = action; + act.sa_flags = 0; + sigfillset(&act.sa_mask); + sigaction(signo, &act, 0); +} + +/* + * Ignore a signal. + */ + +void +ignoresig(int signo) +{ + if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) { + signal(signo, SIG_IGN); + } + sigmode[signo - 1] = S_HARD_IGN; +} + + +/* + * Signal handler. + */ + +void +onsig(int signo) +{ + gotsig[signo - 1] = 1; + pendingsigs = signo; + + if (exsig || (signo == SIGINT && !trap[SIGINT])) { + if (!suppressint) + onint(); + intpending = 1; + } +} + + +/* + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. + */ + +int +dotrap(void) +{ + char *p; + char *q; + int i; + int savestatus; + int skip = 0; + + savestatus = exitstatus; + pendingsigs = 0; + xbarrier(); + + for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) { + if (!*q) + continue; + *q = 0; + + p = trap[i + 1]; + if (!p) + continue; + skip = evalstring(p, SKIPEVAL); + exitstatus = savestatus; + if (skip) + break; + } + + return skip; +} + + +/* + * Controls whether the shell is interactive or not. + */ + +void +setinteractive(int on) +{ + static int is_interactive; + + if (++on == is_interactive) + return; + is_interactive = on; + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET + if(is_interactive > 1) { + /* Looks like they want an interactive shell */ + static int do_banner; + + if(!do_banner) { + out1fmt( + "\n\n%s Built-in shell (ash)\n" + "Enter 'help' for a list of built-in commands.\n\n", + BB_BANNER); + do_banner++; + } + } +#endif +} + + +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET +/*** List the available builtins ***/ + +static int helpcmd(int argc, char **argv) +{ + int col, i; + + out1fmt("\nBuilt-in commands:\n-------------------\n"); + for (col = 0, i = 0; i < NUMBUILTINS; i++) { + col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), + builtincmd[i].name + 1); + if (col > 60) { + out1fmt("\n"); + col = 0; + } + } +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + { + extern const struct BB_applet applets[]; + extern const size_t NUM_APPLETS; + + for (i = 0; i < NUM_APPLETS; i++) { + + col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), applets[i].name); + if (col > 60) { + out1fmt("\n"); + col = 0; + } + } + } +#endif + out1fmt("\n\n"); + return EXIT_SUCCESS; +} +#endif /* CONFIG_FEATURE_SH_EXTRA_QUIET */ + +/* + * Called to exit the shell. + */ + +void +exitshell(void) +{ + struct jmploc loc; + char *p; + int status; + + status = exitstatus; + TRACE(("pid %d, exitshell(%d)\n", getpid(), status)); + if (setjmp(loc.loc)) { + if (exception == EXEXIT) + _exit(exitstatus); + goto out; + } + handler = &loc; + if ((p = trap[0])) { + trap[0] = NULL; + evalstring(p, 0); + } + flushall(); + setjobctl(0); +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY + if (iflag && rootshell) { + const char *hp = lookupvar("HISTFILE"); + + if(hp != NULL ) + save_history ( hp ); + } +#endif +out: + _exit(status); + /* NOTREACHED */ +} + +/* var.c */ + +static struct var *vartab[VTABSIZE]; + +static int vpcmp(const void *, const void *); +static struct var **findvar(struct var **, const char *); + +/* + * Initialize the variable symbol tables and import the environment + */ + + +#ifdef CONFIG_ASH_GETOPTS +/* + * Safe version of setvar, returns 1 on success 0 on failure. + */ + +int +setvarsafe(const char *name, const char *val, int flags) +{ + int err; + volatile int saveint; + struct jmploc *volatile savehandler = handler; + struct jmploc jmploc; + + SAVEINT(saveint); + if (setjmp(jmploc.loc)) + err = 1; + else { + handler = &jmploc; + setvar(name, val, flags); + err = 0; + } + handler = savehandler; + RESTOREINT(saveint); + return err; +} +#endif + +/* + * Set the value of a variable. The flags argument is ored with the + * flags of the variable. If val is NULL, the variable is unset. + */ + +static void +setvar(const char *name, const char *val, int flags) +{ + char *p, *q; + size_t namelen; + char *nameeq; + size_t vallen; + + q = endofname(name); + p = strchrnul(q, '='); + namelen = p - name; + if (!namelen || p != q) + sh_error("%.*s: bad variable name", namelen, name); + vallen = 0; + if (val == NULL) { + flags |= VUNSET; + } else { + vallen = strlen(val); + } + INTOFF; + p = mempcpy(nameeq = ckmalloc(namelen + vallen + 2), name, namelen); + if (val) { + *p++ = '='; + p = mempcpy(p, val, vallen); + } + *p = '\0'; + setvareq(nameeq, flags | VNOSAVE); + INTON; +} + + +/* + * Same as setvar except that the variable and value are passed in + * the first argument as name=value. Since the first argument will + * be actually stored in the table, it should not be a string that + * will go away. + * Called with interrupts off. + */ + +void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + + vpp = hashvar(s); + flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); + vp = *findvar(vpp, s); + if (vp) { + if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) { + const char *n; + + if (flags & VNOSAVE) + free(s); + n = vp->text; + sh_error("%.*s: is read only", strchrnul(n, '=') - n, n); + } + + if (flags & VNOSET) + return; + + if (vp->func && (flags & VNOFUNC) == 0) + (*vp->func)(strchrnul(s, '=') + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + + flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); + } else { + if (flags & VNOSET) + return; + /* not found */ + vp = ckmalloc(sizeof (*vp)); + vp->next = *vpp; + vp->func = NULL; + *vpp = vp; + } + if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE))) + s = savestr(s); + vp->text = s; + vp->flags = flags; +} + + +/* + * Process a linked list of variable assignments. + */ + +static void +listsetvar(struct strlist *list_set_var, int flags) +{ + struct strlist *lp = list_set_var; + + if (!lp) + return; + INTOFF; + do { + setvareq(lp->text, flags); + } while ((lp = lp->next)); + INTON; +} + + +/* + * Find the value of a variable. Returns NULL if not set. + */ + +static char * +lookupvar(const char *name) +{ + struct var *v; + + if ((v = *findvar(hashvar(name), name))) { +#ifdef DYNAMIC_VAR + /* + * Dynamic variables are implemented roughly the same way they are + * in bash. Namely, they're "special" so long as they aren't unset. + * As soon as they're unset, they're no longer dynamic, and dynamic + * lookup will no longer happen at that point. -- PFM. + */ + if((v->flags & VDYNAMIC)) + (*v->func)(NULL); +#endif + if(!(v->flags & VUNSET)) + return strchrnul(v->text, '=') + 1; + } + + return NULL; +} + + +/* + * Search the environment of a builtin command. + */ + +static char * +bltinlookup(const char *name) +{ + struct strlist *sp; + + for (sp = cmdenviron ; sp ; sp = sp->next) { + if (varequal(sp->text, name)) + return strchrnul(sp->text, '=') + 1; + } + return lookupvar(name); +} + + +/* + * Generate a list of variables satisfying the given conditions. + */ + +static char ** +listvars(int on, int off, char ***end) +{ + struct var **vpp; + struct var *vp; + char **ep; + int mask; + + STARTSTACKSTR(ep); + vpp = vartab; + mask = on | off; + do { + for (vp = *vpp ; vp ; vp = vp->next) + if ((vp->flags & mask) == on) { + if (ep == stackstrend()) + ep = growstackstr(); + *ep++ = (char *) vp->text; + } + } while (++vpp < vartab + VTABSIZE); + if (ep == stackstrend()) + ep = growstackstr(); + if (end) + *end = ep; + *ep++ = NULL; + return grabstackstr(ep); +} + + +/* + * POSIX requires that 'set' (but not export or readonly) output the + * variables in lexicographic order - by the locale's collating order (sigh). + * Maybe we could keep them in an ordered balanced binary tree + * instead of hashed lists. + * For now just roll 'em through qsort for printing... + */ + +static int +showvars(const char *sep_prefix, int on, int off) +{ + const char *sep; + char **ep, **epend; + + ep = listvars(on, off, &epend); + qsort(ep, epend - ep, sizeof(char *), vpcmp); + + sep = *sep_prefix ? spcstr : sep_prefix; + + for (; ep < epend; ep++) { + const char *p; + const char *q; + + p = strchrnul(*ep, '='); + q = nullstr; + if (*p) + q = single_quote(++p); + + out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q); + } + + return 0; +} + + + +/* + * The export and readonly commands. + */ + +static int +exportcmd(int argc, char **argv) +{ + struct var *vp; + char *name; + const char *p; + char **aptr; + int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT; + int notp; + + notp = nextopt("p") - 'p'; + if (notp && ((name = *(aptr = argptr)))) { + do { + if ((p = strchr(name, '=')) != NULL) { + p++; + } else { + if ((vp = *findvar(hashvar(name), name))) { + vp->flags |= flag; + continue; + } + } + setvar(name, p, flag); + } while ((name = *++aptr) != NULL); + } else { + showvars(argv[0], flag, 0); + } + return 0; +} + + +/* + * Make a variable a local variable. When a variable is made local, it's + * value and flags are saved in a localvar structure. The saved values + * will be restored when the shell function returns. We handle the name + * "-" as a special case. + */ + +static void mklocal(char *name) +{ + struct localvar *lvp; + struct var **vpp; + struct var *vp; + + INTOFF; + lvp = ckmalloc(sizeof (struct localvar)); + if (name[0] == '-' && name[1] == '\0') { + char *p; + p = ckmalloc(sizeof(optlist)); + lvp->text = memcpy(p, optlist, sizeof(optlist)); + vp = NULL; + } else { + char *eq; + + vpp = hashvar(name); + vp = *findvar(vpp, name); + eq = strchr(name, '='); + if (vp == NULL) { + if (eq) + setvareq(name, VSTRFIXED); + else + setvar(name, NULL, VSTRFIXED); + vp = *vpp; /* the new variable */ + lvp->flags = VUNSET; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (eq) + setvareq(name, 0); + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INTON; +} + +/* + * The "local" command. + */ + +static int +localcmd(int argc, char **argv) +{ + char *name; + + argv = argptr; + while ((name = *argv++) != NULL) { + mklocal(name); + } + return 0; +} + + +/* + * Called after a function returns. + * Interrupts must be off. + */ + +static void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + TRACE(("poplocalvar %s", vp ? vp->text : "-")); + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof(optlist)); + ckfree(lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + unsetvar(vp->text); + } else { + if (vp->func) + (*vp->func)(strchrnul(lvp->text, '=') + 1); + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + } + ckfree(lvp); + } +} + + +/* + * The unset builtin command. We unset the function before we unset the + * variable to allow a function to be unset when there is a readonly variable + * with the same name. + */ + +int +unsetcmd(int argc, char **argv) +{ + char **ap; + int i; + int flag = 0; + int ret = 0; + + while ((i = nextopt("vf")) != '\0') { + flag = i; + } + + for (ap = argptr; *ap ; ap++) { + if (flag != 'f') { + i = unsetvar(*ap); + ret |= i; + if (!(i & 2)) + continue; + } + if (flag != 'v') + unsetfunc(*ap); + } + return ret & 1; +} + + +/* + * Unset the specified variable. + */ + +int +unsetvar(const char *s) +{ + struct var **vpp; + struct var *vp; + int retval; + + vpp = findvar(hashvar(s), s); + vp = *vpp; + retval = 2; + if (vp) { + int flags = vp->flags; + + retval = 1; + if (flags & VREADONLY) + goto out; +#ifdef DYNAMIC_VAR + vp->flags &= ~VDYNAMIC; +#endif + if (flags & VUNSET) + goto ok; + if ((flags & VSTRFIXED) == 0) { + INTOFF; + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + *vpp = vp->next; + ckfree(vp); + INTON; + } else { + setvar(s, 0, 0); + vp->flags &= ~VEXPORT; + } +ok: + retval = 0; + } + +out: + return retval; +} + + + +/* + * Find the appropriate entry in the hash table from the name. + */ + +static struct var ** +hashvar(const char *p) +{ + unsigned int hashval; + + hashval = ((unsigned char) *p) << 4; + while (*p && *p != '=') + hashval += (unsigned char) *p++; + return &vartab[hashval % VTABSIZE]; +} + + + +/* + * Compares two strings up to the first = or '\0'. The first + * string must be terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ + +int +varcmp(const char *p, const char *q) +{ + int c, d; + + while ((c = *p) == (d = *q)) { + if (!c || c == '=') + goto out; + p++; + q++; + } + if (c == '=') + c = 0; + if (d == '=') + d = 0; +out: + return c - d; +} + +static int +vpcmp(const void *a, const void *b) +{ + return varcmp(*(const char **)a, *(const char **)b); +} + +static struct var ** +findvar(struct var **vpp, const char *name) +{ + for (; *vpp; vpp = &(*vpp)->next) { + if (varequal((*vpp)->text, name)) { + break; + } + } + return vpp; +} +/* setmode.c */ + +#include <sys/times.h> + +static const unsigned char timescmd_str[] = { + ' ', offsetof(struct tms, tms_utime), + '\n', offsetof(struct tms, tms_stime), + ' ', offsetof(struct tms, tms_cutime), + '\n', offsetof(struct tms, tms_cstime), + 0 +}; + +static int timescmd(int ac, char **av) +{ + long int clk_tck, s, t; + const unsigned char *p; + struct tms buf; + + clk_tck = sysconf(_SC_CLK_TCK); + times(&buf); + + p = timescmd_str; + do { + t = *(clock_t *)(((char *) &buf) + p[1]); + s = t / clk_tck; + out1fmt("%ldm%ld.%.3lds%c", + s/60, s%60, + ((t - s * clk_tck) * 1000) / clk_tck, + p[0]); + } while (*(p += 2)); + + return 0; +} + +#ifdef CONFIG_ASH_MATH_SUPPORT +static arith_t +dash_arith(const char *s) +{ + arith_t result; + int errcode = 0; + + INTOFF; + result = arith(s, &errcode); + if (errcode < 0) { + if (errcode == -3) + sh_error("exponent less than 0"); + else if (errcode == -2) + sh_error("divide by zero"); + else if (errcode == -5) + sh_error("expression recursion loop detected"); + else + synerror(s); + } + INTON; + + return result; +} + + +/* + * The let builtin. partial stolen from GNU Bash, the Bourne Again SHell. + * Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. + * + * Copyright (C) 2003 Vladimir Oleynik <dzo@simtreas.ru> + */ + +static int +letcmd(int argc, char **argv) +{ + char **ap; + arith_t i = 0; + + ap = argv + 1; + if(!*ap) + sh_error("expression expected"); + for (ap = argv + 1; *ap; ap++) { + i = dash_arith(*ap); + } + + return !i; +} +#endif /* CONFIG_ASH_MATH_SUPPORT */ + +/* miscbltin.c */ + +/* + * Miscellaneous builtins. + */ + +#undef rflag + +#ifdef __GLIBC__ +#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 1 +typedef enum __rlimit_resource rlim_t; +#endif +#endif + + +/* + * The read builtin. The -e option causes backslashes to escape the + * following character. + * + * This uses unbuffered input, which may be avoidable in some cases. + */ + +static int +readcmd(int argc, char **argv) +{ + char **ap; + int backslash; + char c; + int rflag; + char *prompt; + const char *ifs; + char *p; + int startword; + int status; + int i; +#if defined(CONFIG_ASH_READ_NCHARS) + int nch_flag = 0; + int nchars = 0; + int silent = 0; + struct termios tty, old_tty; +#endif +#if defined(CONFIG_ASH_READ_TIMEOUT) + fd_set set; + struct timeval ts; + + ts.tv_sec = ts.tv_usec = 0; +#endif + + rflag = 0; + prompt = NULL; +#if defined(CONFIG_ASH_READ_NCHARS) && defined(CONFIG_ASH_READ_TIMEOUT) + while ((i = nextopt("p:rt:n:s")) != '\0') +#elif defined(CONFIG_ASH_READ_NCHARS) + while ((i = nextopt("p:rn:s")) != '\0') +#elif defined(CONFIG_ASH_READ_TIMEOUT) + while ((i = nextopt("p:rt:")) != '\0') +#else + while ((i = nextopt("p:r")) != '\0') +#endif + { + switch(i) { + case 'p': + prompt = optionarg; + break; +#if defined(CONFIG_ASH_READ_NCHARS) + case 'n': + nchars = strtol(optionarg, &p, 10); + if (*p) + sh_error("invalid count"); + nch_flag = (nchars > 0); + break; + case 's': + silent = 1; + break; +#endif +#if defined(CONFIG_ASH_READ_TIMEOUT) + case 't': + ts.tv_sec = strtol(optionarg, &p, 10); + ts.tv_usec = 0; + if (*p == '.') { + char *p2; + if (*++p) { + int scale; + ts.tv_usec = strtol(p, &p2, 10); + if (*p2) + sh_error("invalid timeout"); + scale = p2 - p; + /* normalize to usec */ + if (scale > 6) + sh_error("invalid timeout"); + while (scale++ < 6) + ts.tv_usec *= 10; + } + } else if (*p) { + sh_error("invalid timeout"); + } + if ( ! ts.tv_sec && ! ts.tv_usec) + sh_error("invalid timeout"); + break; +#endif + case 'r': + rflag = 1; + break; + default: + break; + } + } + if (prompt && isatty(0)) { + out2str(prompt); + } + if (*(ap = argptr) == NULL) + sh_error("arg count"); + if ((ifs = bltinlookup("IFS")) == NULL) + ifs = defifs; +#if defined(CONFIG_ASH_READ_NCHARS) + if (nch_flag || silent) { + tcgetattr(0, &tty); + old_tty = tty; + if (nch_flag) { + tty.c_lflag &= ~ICANON; + tty.c_cc[VMIN] = nchars; + } + if (silent) { + tty.c_lflag &= ~(ECHO|ECHOK|ECHONL); + + } + tcsetattr(0, TCSANOW, &tty); + } +#endif +#if defined(CONFIG_ASH_READ_TIMEOUT) + if (ts.tv_sec || ts.tv_usec) { + FD_ZERO (&set); + FD_SET (0, &set); + + i = select (FD_SETSIZE, &set, NULL, NULL, &ts); + if (!i) { +#if defined(CONFIG_ASH_READ_NCHARS) + if (nch_flag) + tcsetattr(0, TCSANOW, &old_tty); +#endif + return 1; + } + } +#endif + status = 0; + startword = 1; + backslash = 0; + STARTSTACKSTR(p); +#if defined(CONFIG_ASH_READ_NCHARS) + while (!nch_flag || nchars--) +#else + for (;;) +#endif + { + if (read(0, &c, 1) != 1) { + status = 1; + break; + } + if (c == '\0') + continue; + if (backslash) { + backslash = 0; + if (c != '\n') + goto put; + continue; + } + if (!rflag && c == '\\') { + backslash++; + continue; + } + if (c == '\n') + break; + if (startword && *ifs == ' ' && strchr(ifs, c)) { + continue; + } + startword = 0; + if (ap[1] != NULL && strchr(ifs, c) != NULL) { + STACKSTRNUL(p); + setvar(*ap, stackblock(), 0); + ap++; + startword = 1; + STARTSTACKSTR(p); + } else { +put: + STPUTC(c, p); + } + } +#if defined(CONFIG_ASH_READ_NCHARS) + if (nch_flag || silent) + tcsetattr(0, TCSANOW, &old_tty); +#endif + + STACKSTRNUL(p); + /* Remove trailing blanks */ + while ((char *)stackblock() <= --p && strchr(ifs, *p) != NULL) + *p = '\0'; + setvar(*ap, stackblock(), 0); + while (*++ap != NULL) + setvar(*ap, nullstr, 0); + return status; +} + + +static int umaskcmd(int argc, char **argv) +{ + static const char permuser[3] = "ugo"; + static const char permmode[3] = "rwx"; + static const short int permmask[] = { + S_IRUSR, S_IWUSR, S_IXUSR, + S_IRGRP, S_IWGRP, S_IXGRP, + S_IROTH, S_IWOTH, S_IXOTH + }; + + char *ap; + mode_t mask; + int i; + int symbolic_mode = 0; + + while (nextopt("S") != '\0') { + symbolic_mode = 1; + } + + INTOFF; + mask = umask(0); + umask(mask); + INTON; + + if ((ap = *argptr) == NULL) { + if (symbolic_mode) { + char buf[18]; + char *p = buf; + + for (i = 0; i < 3; i++) { + int j; + + *p++ = permuser[i]; + *p++ = '='; + for (j = 0; j < 3; j++) { + if ((mask & permmask[3 * i + j]) == 0) { + *p++ = permmode[j]; + } + } + *p++ = ','; + } + *--p = 0; + puts(buf); + } else { + out1fmt("%.4o\n", mask); + } + } else { + if (is_digit((unsigned char) *ap)) { + mask = 0; + do { + if (*ap >= '8' || *ap < '0') + sh_error(illnum, argv[1]); + mask = (mask << 3) + (*ap - '0'); + } while (*++ap != '\0'); + umask(mask); + } else { + mask = ~mask & 0777; + if (!bb_parse_mode(ap, &mask)) { + sh_error("Illegal mode: %s", ap); + } + umask(~mask & 0777); + } + } + return 0; +} + +/* + * ulimit builtin + * + * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and + * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with + * ash by J.T. Conklin. + * + * Public domain. + */ + +struct limits { + const char *name; + int cmd; + int factor; /* multiply by to get rlim_{cur,max} values */ + char option; +}; + +static const struct limits limits[] = { +#ifdef RLIMIT_CPU + { "time(seconds)", RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file(blocks)", RLIMIT_FSIZE, 512, 'f' }, +#endif +#ifdef RLIMIT_DATA + { "data(kbytes)", RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack(kbytes)", RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_CORE + { "coredump(blocks)", RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_RSS + { "memory(kbytes)", RLIMIT_RSS, 1024, 'm' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "locked memory(kbytes)", RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_NPROC + { "process", RLIMIT_NPROC, 1, 'p' }, +#endif +#ifdef RLIMIT_NOFILE + { "nofiles", RLIMIT_NOFILE, 1, 'n' }, +#endif +#ifdef RLIMIT_AS + { "vmemory(kbytes)", RLIMIT_AS, 1024, 'v' }, +#endif +#ifdef RLIMIT_LOCKS + { "locks", RLIMIT_LOCKS, 1, 'w' }, +#endif + { (char *) 0, 0, 0, '\0' } +}; + +enum limtype { SOFT = 0x1, HARD = 0x2 }; + +static void printlim(enum limtype how, const struct rlimit *limit, + const struct limits *l) +{ + rlim_t val; + + val = limit->rlim_max; + if (how & SOFT) + val = limit->rlim_cur; + + if (val == RLIM_INFINITY) + out1fmt("unlimited\n"); + else { + val /= l->factor; + out1fmt("%lld\n", (long long) val); + } +} + +int +ulimitcmd(int argc, char **argv) +{ + int c; + rlim_t val = 0; + enum limtype how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; + struct rlimit limit; + + what = 'f'; + while ((optc = nextopt("HSa" +#ifdef RLIMIT_CPU + "t" +#endif +#ifdef RLIMIT_FSIZE + "f" +#endif +#ifdef RLIMIT_DATA + "d" +#endif +#ifdef RLIMIT_STACK + "s" +#endif +#ifdef RLIMIT_CORE + "c" +#endif +#ifdef RLIMIT_RSS + "m" +#endif +#ifdef RLIMIT_MEMLOCK + "l" +#endif +#ifdef RLIMIT_NPROC + "p" +#endif +#ifdef RLIMIT_NOFILE + "n" +#endif +#ifdef RLIMIT_AS + "v" +#endif +#ifdef RLIMIT_LOCKS + "w" +#endif + )) != '\0') + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = 1; + break; + default: + what = optc; + } + + for (l = limits; l->option != what; l++) + ; + + set = *argptr ? 1 : 0; + if (set) { + char *p = *argptr; + + if (all || argptr[1]) + sh_error("too many arguments"); + if (strncmp(p, "unlimited\n", 9) == 0) + val = RLIM_INFINITY; + else { + val = (rlim_t) 0; + + while ((c = *p++) >= '0' && c <= '9') + { + val = (val * 10) + (long)(c - '0'); + if (val < (rlim_t) 0) + break; + } + if (c) + sh_error("bad number"); + val *= l->factor; + } + } + if (all) { + for (l = limits; l->name; l++) { + getrlimit(l->cmd, &limit); + out1fmt("%-20s ", l->name); + printlim(how, &limit, l); + } + return 0; + } + + getrlimit(l->cmd, &limit); + if (set) { + if (how & HARD) + limit.rlim_max = val; + if (how & SOFT) + limit.rlim_cur = val; + if (setrlimit(l->cmd, &limit) < 0) + sh_error("error setting limit (%m)"); + } else { + printlim(how, &limit, l); + } + return 0; +} + + +#ifdef CONFIG_ASH_MATH_SUPPORT + +/* Copyright (c) 2001 Aaron Lehmann <aaronl@vitelus.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* This is my infix parser/evaluator. It is optimized for size, intended + * as a replacement for yacc-based parsers. However, it may well be faster + * than a comparable parser written in yacc. The supported operators are + * listed in #defines below. Parens, order of operations, and error handling + * are supported. This code is thread safe. The exact expression format should + * be that which POSIX specifies for shells. */ + +/* The code uses a simple two-stack algorithm. See + * http://www.onthenet.com.au/~grahamis/int2008/week02/lect02.html + * for a detailed explanation of the infix-to-postfix algorithm on which + * this is based (this code differs in that it applies operators immediately + * to the stack instead of adding them to a queue to end up with an + * expression). */ + +/* To use the routine, call it with an expression string and error return + * pointer */ + +/* + * Aug 24, 2001 Manuel Novoa III + * + * Reduced the generated code size by about 30% (i386) and fixed several bugs. + * + * 1) In arith_apply(): + * a) Cached values of *numptr and &(numptr[-1]). + * b) Removed redundant test for zero denominator. + * + * 2) In arith(): + * a) Eliminated redundant code for processing operator tokens by moving + * to a table-based implementation. Also folded handling of parens + * into the table. + * b) Combined all 3 loops which called arith_apply to reduce generated + * code size at the cost of speed. + * + * 3) The following expressions were treated as valid by the original code: + * 1() , 0! , 1 ( *3 ) . + * These bugs have been fixed by internally enclosing the expression in + * parens and then checking that all binary ops and right parens are + * preceded by a valid expression (NUM_TOKEN). + * + * Note: It may be desirable to replace Aaron's test for whitespace with + * ctype's isspace() if it is used by another busybox applet or if additional + * whitespace chars should be considered. Look below the "#include"s for a + * precompiler test. + */ + +/* + * Aug 26, 2001 Manuel Novoa III + * + * Return 0 for null expressions. Pointed out by Vladimir Oleynik. + * + * Merge in Aaron's comments previously posted to the busybox list, + * modified slightly to take account of my changes to the code. + * + */ + +/* + * (C) 2003 Vladimir Oleynik <dzo@simtreas.ru> + * + * - allow access to variable, + * used recursive find value indirection (c=2*2; a="c"; $((a+=2)) produce 6) + * - realize assign syntax (VAR=expr, +=, *= etc) + * - realize exponentiation (** operator) + * - realize comma separated - expr, expr + * - realise ++expr --expr expr++ expr-- + * - realise expr ? expr : expr (but, second expr calculate always) + * - allow hexadecimal and octal numbers + * - was restored loses XOR operator + * - remove one goto label, added three ;-) + * - protect $((num num)) as true zero expr (Manuel`s error) + * - always use special isspace(), see comment from bash ;-) + */ + + +#define arith_isspace(arithval) \ + (arithval == ' ' || arithval == '\n' || arithval == '\t') + + +typedef unsigned char operator; + +/* An operator's token id is a bit of a bitfield. The lower 5 bits are the + * precedence, and 3 high bits are an ID unique across operators of that + * precedence. The ID portion is so that multiple operators can have the + * same precedence, ensuring that the leftmost one is evaluated first. + * Consider * and /. */ + +#define tok_decl(prec,id) (((id)<<5)|(prec)) +#define PREC(op) ((op) & 0x1F) + +#define TOK_LPAREN tok_decl(0,0) + +#define TOK_COMMA tok_decl(1,0) + +#define TOK_ASSIGN tok_decl(2,0) +#define TOK_AND_ASSIGN tok_decl(2,1) +#define TOK_OR_ASSIGN tok_decl(2,2) +#define TOK_XOR_ASSIGN tok_decl(2,3) +#define TOK_PLUS_ASSIGN tok_decl(2,4) +#define TOK_MINUS_ASSIGN tok_decl(2,5) +#define TOK_LSHIFT_ASSIGN tok_decl(2,6) +#define TOK_RSHIFT_ASSIGN tok_decl(2,7) + +#define TOK_MUL_ASSIGN tok_decl(3,0) +#define TOK_DIV_ASSIGN tok_decl(3,1) +#define TOK_REM_ASSIGN tok_decl(3,2) + +/* all assign is right associativity and precedence eq, but (7+3)<<5 > 256 */ +#define convert_prec_is_assing(prec) do { if(prec == 3) prec = 2; } while(0) + +/* conditional is right associativity too */ +#define TOK_CONDITIONAL tok_decl(4,0) +#define TOK_CONDITIONAL_SEP tok_decl(4,1) + +#define TOK_OR tok_decl(5,0) + +#define TOK_AND tok_decl(6,0) + +#define TOK_BOR tok_decl(7,0) + +#define TOK_BXOR tok_decl(8,0) + +#define TOK_BAND tok_decl(9,0) + +#define TOK_EQ tok_decl(10,0) +#define TOK_NE tok_decl(10,1) + +#define TOK_LT tok_decl(11,0) +#define TOK_GT tok_decl(11,1) +#define TOK_GE tok_decl(11,2) +#define TOK_LE tok_decl(11,3) + +#define TOK_LSHIFT tok_decl(12,0) +#define TOK_RSHIFT tok_decl(12,1) + +#define TOK_ADD tok_decl(13,0) +#define TOK_SUB tok_decl(13,1) + +#define TOK_MUL tok_decl(14,0) +#define TOK_DIV tok_decl(14,1) +#define TOK_REM tok_decl(14,2) + +/* exponent is right associativity */ +#define TOK_EXPONENT tok_decl(15,1) + +/* For now unary operators. */ +#define UNARYPREC 16 +#define TOK_BNOT tok_decl(UNARYPREC,0) +#define TOK_NOT tok_decl(UNARYPREC,1) + +#define TOK_UMINUS tok_decl(UNARYPREC+1,0) +#define TOK_UPLUS tok_decl(UNARYPREC+1,1) + +#define PREC_PRE (UNARYPREC+2) + +#define TOK_PRE_INC tok_decl(PREC_PRE, 0) +#define TOK_PRE_DEC tok_decl(PREC_PRE, 1) + +#define PREC_POST (UNARYPREC+3) + +#define TOK_POST_INC tok_decl(PREC_POST, 0) +#define TOK_POST_DEC tok_decl(PREC_POST, 1) + +#define SPEC_PREC (UNARYPREC+4) + +#define TOK_NUM tok_decl(SPEC_PREC, 0) +#define TOK_RPAREN tok_decl(SPEC_PREC, 1) + +#define NUMPTR (*numstackptr) + +static int tok_have_assign(operator op) +{ + operator prec = PREC(op); + + convert_prec_is_assing(prec); + return (prec == PREC(TOK_ASSIGN) || + prec == PREC_PRE || prec == PREC_POST); +} + +static int is_right_associativity(operator prec) +{ + return (prec == PREC(TOK_ASSIGN) || prec == PREC(TOK_EXPONENT) || + prec == PREC(TOK_CONDITIONAL)); +} + + +typedef struct ARITCH_VAR_NUM { + arith_t val; + arith_t contidional_second_val; + char contidional_second_val_initialized; + char *var; /* if NULL then is regular number, + else is variable name */ +} v_n_t; + + +typedef struct CHK_VAR_RECURSIVE_LOOPED { + const char *var; + struct CHK_VAR_RECURSIVE_LOOPED *next; +} chk_var_recursive_looped_t; + +static chk_var_recursive_looped_t *prev_chk_var_recursive; + + +static int arith_lookup_val(v_n_t *t) +{ + if(t->var) { + const char * p = lookupvar(t->var); + + if(p) { + int errcode; + + /* recursive try as expression */ + chk_var_recursive_looped_t *cur; + chk_var_recursive_looped_t cur_save; + + for(cur = prev_chk_var_recursive; cur; cur = cur->next) { + if(strcmp(cur->var, t->var) == 0) { + /* expression recursion loop detected */ + return -5; + } + } + /* save current lookuped var name */ + cur = prev_chk_var_recursive; + cur_save.var = t->var; + cur_save.next = cur; + prev_chk_var_recursive = &cur_save; + + t->val = arith (p, &errcode); + /* restore previous ptr after recursiving */ + prev_chk_var_recursive = cur; + return errcode; + } else { + /* allow undefined var as 0 */ + t->val = 0; + } + } + return 0; +} + +/* "applying" a token means performing it on the top elements on the integer + * stack. For a unary operator it will only change the top element, but a + * binary operator will pop two arguments and push a result */ +static int arith_apply(operator op, v_n_t *numstack, v_n_t **numstackptr) +{ + v_n_t *numptr_m1; + arith_t numptr_val, rez; + int ret_arith_lookup_val; + + if (NUMPTR == numstack) goto err; /* There is no operator that can work + without arguments */ + numptr_m1 = NUMPTR - 1; + + /* check operand is var with noninteger value */ + ret_arith_lookup_val = arith_lookup_val(numptr_m1); + if(ret_arith_lookup_val) + return ret_arith_lookup_val; + + rez = numptr_m1->val; + if (op == TOK_UMINUS) + rez *= -1; + else if (op == TOK_NOT) + rez = !rez; + else if (op == TOK_BNOT) + rez = ~rez; + else if (op == TOK_POST_INC || op == TOK_PRE_INC) + rez++; + else if (op == TOK_POST_DEC || op == TOK_PRE_DEC) + rez--; + else if (op != TOK_UPLUS) { + /* Binary operators */ + + /* check and binary operators need two arguments */ + if (numptr_m1 == numstack) goto err; + + /* ... and they pop one */ + --NUMPTR; + numptr_val = rez; + if (op == TOK_CONDITIONAL) { + if(! numptr_m1->contidional_second_val_initialized) { + /* protect $((expr1 ? expr2)) without ": expr" */ + goto err; + } + rez = numptr_m1->contidional_second_val; + } else if(numptr_m1->contidional_second_val_initialized) { + /* protect $((expr1 : expr2)) without "expr ? " */ + goto err; + } + numptr_m1 = NUMPTR - 1; + if(op != TOK_ASSIGN) { + /* check operand is var with noninteger value for not '=' */ + ret_arith_lookup_val = arith_lookup_val(numptr_m1); + if(ret_arith_lookup_val) + return ret_arith_lookup_val; + } + if (op == TOK_CONDITIONAL) { + numptr_m1->contidional_second_val = rez; + } + rez = numptr_m1->val; + if (op == TOK_BOR || op == TOK_OR_ASSIGN) + rez |= numptr_val; + else if (op == TOK_OR) + rez = numptr_val || rez; + else if (op == TOK_BAND || op == TOK_AND_ASSIGN) + rez &= numptr_val; + else if (op == TOK_BXOR || op == TOK_XOR_ASSIGN) + rez ^= numptr_val; + else if (op == TOK_AND) + rez = rez && numptr_val; + else if (op == TOK_EQ) + rez = (rez == numptr_val); + else if (op == TOK_NE) + rez = (rez != numptr_val); + else if (op == TOK_GE) + rez = (rez >= numptr_val); + else if (op == TOK_RSHIFT || op == TOK_RSHIFT_ASSIGN) + rez >>= numptr_val; + else if (op == TOK_LSHIFT || op == TOK_LSHIFT_ASSIGN) + rez <<= numptr_val; + else if (op == TOK_GT) + rez = (rez > numptr_val); + else if (op == TOK_LT) + rez = (rez < numptr_val); + else if (op == TOK_LE) + rez = (rez <= numptr_val); + else if (op == TOK_MUL || op == TOK_MUL_ASSIGN) + rez *= numptr_val; + else if (op == TOK_ADD || op == TOK_PLUS_ASSIGN) + rez += numptr_val; + else if (op == TOK_SUB || op == TOK_MINUS_ASSIGN) + rez -= numptr_val; + else if (op == TOK_ASSIGN || op == TOK_COMMA) + rez = numptr_val; + else if (op == TOK_CONDITIONAL_SEP) { + if (numptr_m1 == numstack) { + /* protect $((expr : expr)) without "expr ? " */ + goto err; + } + numptr_m1->contidional_second_val_initialized = op; + numptr_m1->contidional_second_val = numptr_val; + } + else if (op == TOK_CONDITIONAL) { + rez = rez ? + numptr_val : numptr_m1->contidional_second_val; + } + else if(op == TOK_EXPONENT) { + if(numptr_val < 0) + return -3; /* exponent less than 0 */ + else { + arith_t c = 1; + + if(numptr_val) + while(numptr_val--) + c *= rez; + rez = c; + } + } + else if(numptr_val==0) /* zero divisor check */ + return -2; + else if (op == TOK_DIV || op == TOK_DIV_ASSIGN) + rez /= numptr_val; + else if (op == TOK_REM || op == TOK_REM_ASSIGN) + rez %= numptr_val; + } + if(tok_have_assign(op)) { + char buf[32]; + + if(numptr_m1->var == NULL) { + /* Hmm, 1=2 ? */ + goto err; + } + /* save to shell variable */ +#ifdef CONFIG_ASH_MATH_SUPPORT_64 + snprintf(buf, sizeof(buf), "%lld", arith_t_type rez); +#else + snprintf(buf, sizeof(buf), "%ld", arith_t_type rez); +#endif + setvar(numptr_m1->var, buf, 0); + /* after saving, make previous value for v++ or v-- */ + if(op == TOK_POST_INC) + rez--; + else if(op == TOK_POST_DEC) + rez++; + } + numptr_m1->val = rez; + /* protect geting var value, is number now */ + numptr_m1->var = NULL; + return 0; + err: + return -1; +} + +/* longest must first */ +static const char op_tokens[] = { + '<','<','=',0, TOK_LSHIFT_ASSIGN, + '>','>','=',0, TOK_RSHIFT_ASSIGN, + '<','<', 0, TOK_LSHIFT, + '>','>', 0, TOK_RSHIFT, + '|','|', 0, TOK_OR, + '&','&', 0, TOK_AND, + '!','=', 0, TOK_NE, + '<','=', 0, TOK_LE, + '>','=', 0, TOK_GE, + '=','=', 0, TOK_EQ, + '|','=', 0, TOK_OR_ASSIGN, + '&','=', 0, TOK_AND_ASSIGN, + '*','=', 0, TOK_MUL_ASSIGN, + '/','=', 0, TOK_DIV_ASSIGN, + '%','=', 0, TOK_REM_ASSIGN, + '+','=', 0, TOK_PLUS_ASSIGN, + '-','=', 0, TOK_MINUS_ASSIGN, + '-','-', 0, TOK_POST_DEC, + '^','=', 0, TOK_XOR_ASSIGN, + '+','+', 0, TOK_POST_INC, + '*','*', 0, TOK_EXPONENT, + '!', 0, TOK_NOT, + '<', 0, TOK_LT, + '>', 0, TOK_GT, + '=', 0, TOK_ASSIGN, + '|', 0, TOK_BOR, + '&', 0, TOK_BAND, + '*', 0, TOK_MUL, + '/', 0, TOK_DIV, + '%', 0, TOK_REM, + '+', 0, TOK_ADD, + '-', 0, TOK_SUB, + '^', 0, TOK_BXOR, + /* uniq */ + '~', 0, TOK_BNOT, + ',', 0, TOK_COMMA, + '?', 0, TOK_CONDITIONAL, + ':', 0, TOK_CONDITIONAL_SEP, + ')', 0, TOK_RPAREN, + '(', 0, TOK_LPAREN, + 0 +}; +/* ptr to ")" */ +#define endexpression &op_tokens[sizeof(op_tokens)-7] + + +static arith_t arith (const char *expr, int *perrcode) +{ + char arithval; /* Current character under analysis */ + operator lasttok, op; + operator prec; + + const char *p = endexpression; + int errcode; + + size_t datasizes = strlen(expr) + 2; + + /* Stack of integers */ + /* The proof that there can be no more than strlen(startbuf)/2+1 integers + * in any given correct or incorrect expression is left as an exercise to + * the reader. */ + v_n_t *numstack = alloca(((datasizes)/2)*sizeof(v_n_t)), + *numstackptr = numstack; + /* Stack of operator tokens */ + operator *stack = alloca((datasizes) * sizeof(operator)), + *stackptr = stack; + + *stackptr++ = lasttok = TOK_LPAREN; /* start off with a left paren */ + *perrcode = errcode = 0; + + while(1) { + if ((arithval = *expr) == 0) { + if (p == endexpression) { + /* Null expression. */ + return 0; + } + + /* This is only reached after all tokens have been extracted from the + * input stream. If there are still tokens on the operator stack, they + * are to be applied in order. At the end, there should be a final + * result on the integer stack */ + + if (expr != endexpression + 1) { + /* If we haven't done so already, */ + /* append a closing right paren */ + expr = endexpression; + /* and let the loop process it. */ + continue; + } + /* At this point, we're done with the expression. */ + if (numstackptr != numstack+1) { + /* ... but if there isn't, it's bad */ + err: + return (*perrcode = -1); + } + if(numstack->var) { + /* expression is $((var)) only, lookup now */ + errcode = arith_lookup_val(numstack); + } + ret: + *perrcode = errcode; + return numstack->val; + } else { + /* Continue processing the expression. */ + if (arith_isspace(arithval)) { + /* Skip whitespace */ + goto prologue; + } + if((p = endofname(expr)) != expr) { + size_t var_name_size = (p-expr) + 1; /* trailing zero */ + + numstackptr->var = alloca(var_name_size); + safe_strncpy(numstackptr->var, expr, var_name_size); + expr = p; + num: + numstackptr->contidional_second_val_initialized = 0; + numstackptr++; + lasttok = TOK_NUM; + continue; + } else if (is_digit(arithval)) { + numstackptr->var = NULL; +#ifdef CONFIG_ASH_MATH_SUPPORT_64 + numstackptr->val = strtoll(expr, (char **) &expr, 0); +#else + numstackptr->val = strtol(expr, (char **) &expr, 0); +#endif + goto num; + } + for(p = op_tokens; ; p++) { + const char *o; + + if(*p == 0) { + /* strange operator not found */ + goto err; + } + for(o = expr; *p && *o == *p; p++) + o++; + if(! *p) { + /* found */ + expr = o - 1; + break; + } + /* skip tail uncompared token */ + while(*p) + p++; + /* skip zero delim */ + p++; + } + op = p[1]; + + /* post grammar: a++ reduce to num */ + if(lasttok == TOK_POST_INC || lasttok == TOK_POST_DEC) + lasttok = TOK_NUM; + + /* Plus and minus are binary (not unary) _only_ if the last + * token was as number, or a right paren (which pretends to be + * a number, since it evaluates to one). Think about it. + * It makes sense. */ + if (lasttok != TOK_NUM) { + switch(op) { + case TOK_ADD: + op = TOK_UPLUS; + break; + case TOK_SUB: + op = TOK_UMINUS; + break; + case TOK_POST_INC: + op = TOK_PRE_INC; + break; + case TOK_POST_DEC: + op = TOK_PRE_DEC; + break; + } + } + /* We don't want a unary operator to cause recursive descent on the + * stack, because there can be many in a row and it could cause an + * operator to be evaluated before its argument is pushed onto the + * integer stack. */ + /* But for binary operators, "apply" everything on the operator + * stack until we find an operator with a lesser priority than the + * one we have just extracted. */ + /* Left paren is given the lowest priority so it will never be + * "applied" in this way. + * if associativity is right and priority eq, applied also skip + */ + prec = PREC(op); + if ((prec > 0 && prec < UNARYPREC) || prec == SPEC_PREC) { + /* not left paren or unary */ + if (lasttok != TOK_NUM) { + /* binary op must be preceded by a num */ + goto err; + } + while (stackptr != stack) { + if (op == TOK_RPAREN) { + /* The algorithm employed here is simple: while we don't + * hit an open paren nor the bottom of the stack, pop + * tokens and apply them */ + if (stackptr[-1] == TOK_LPAREN) { + --stackptr; + /* Any operator directly after a */ + lasttok = TOK_NUM; + /* close paren should consider itself binary */ + goto prologue; + } + } else { + operator prev_prec = PREC(stackptr[-1]); + + convert_prec_is_assing(prec); + convert_prec_is_assing(prev_prec); + if (prev_prec < prec) + break; + /* check right assoc */ + if(prev_prec == prec && is_right_associativity(prec)) + break; + } + errcode = arith_apply(*--stackptr, numstack, &numstackptr); + if(errcode) goto ret; + } + if (op == TOK_RPAREN) { + goto err; + } + } + + /* Push this operator to the stack and remember it. */ + *stackptr++ = lasttok = op; + + prologue: + ++expr; + } + } +} +#endif /* CONFIG_ASH_MATH_SUPPORT */ + + +#if DEBUG +const char *applet_name = "debug stuff usage"; +int main(int argc, char **argv) +{ + return ash_main(argc, argv); +} +#endif + +/*- + * Copyright (c) 1989, 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/shell/bbsh.c b/shell/bbsh.c new file mode 100644 index 000000000..99e4f61fb --- /dev/null +++ b/shell/bbsh.c @@ -0,0 +1,222 @@ +/* vi: set ts=4 : + * + * bbsh - busybox shell + * + * Copyright 2006 Rob Landley <rob@landley.net> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +// A section of code that gets repeatedly or conditionally executed is stored +// as a string and parsed each time it's run. + + + +// Wheee, debugging. + +// Terminal control +#define ENABLE_BBSH_TTY 0 + +// &, fg, bg, jobs. (ctrl-z with tty.) +#define ENABLE_BBSH_JOBCTL 0 + +// Flow control (if, while, for, functions { }) +#define ENABLE_BBSH_FLOWCTL 0 + +#define ENABLE_BBSH_ENVVARS 0 // Environment variable support + +// Local and synthetic variables, fancy prompts, set, $?, etc. +#define ENABLE_BBSH_LOCALVARS 0 + +// Pipes and redirects: | > < >> << && || & () ; +#define ENABLE_BBSH_PIPES 0 + +/* Fun: + + echo `echo hello#comment " woot` and more +*/ + +#include "busybox.h" + +// A single executable, its arguments, and other information we know about it. +#define BBSH_FLAG_EXIT 1 +#define BBSH_FLAG_SUSPEND 2 +#define BBSH_FLAG_PIPE 4 +#define BBSH_FLAG_AND 8 +#define BBSH_FLAG_OR 16 +#define BBSH_FLAG_AMP 32 +#define BBSH_FLAG_SEMI 64 +#define BBSH_FLAG_PAREN 128 + +// What we know about a single process. +struct command { + struct command *next; + int flags; // exit, suspend, && || + int pid; // pid (or exit code) + int argc; + char *argv[0]; +}; + +// A collection of processes piped into/waiting on each other. +struct pipeline { + struct pipeline *next; + int job_id; + struct command *cmd; + char *cmdline; + int cmdlinelen; +}; + +static void free_list(void *list, void (*freeit)(void *data)) +{ + while(list) { + void **next = (void **)list; + void *list_next = *next; + freeit(list); + free(list); + list = list_next; + } +} + +// Parse one word from the command line, appending one or more argv[] entries +// to struct command. Handles environment variable substitution and +// substrings. Returns pointer to next used byte, or NULL if it +// hit an ending token. +static char *parse_word(char *start, struct command **cmd) +{ + char *end; + + // Detect end of line (and truncate line at comment) + if (ENABLE_BBSH_PIPES && strchr("><&|(;", *start)) return 0; + + // Grab next word. (Add dequote and envvar logic here) + end = start; + while (*end && !isspace(*end)) end++; + (*cmd)->argv[(*cmd)->argc++] = xstrndup(start, end-start); + + // Allocate more space if there's no room for NULL terminator. + + if (!((*cmd)->argc & 7)) + *cmd = xrealloc(*cmd, + sizeof(struct command) + ((*cmd)->argc+8)*sizeof(char *)); + (*cmd)->argv[(*cmd)->argc] = 0; + return end; +} + +// Parse a line of text into a pipeline. +// Returns a pointer to the next line. + +static char *parse_pipeline(char *cmdline, struct pipeline *line) +{ + struct command **cmd = &(line->cmd); + char *start = line->cmdline = cmdline; + + if (!cmdline) return 0; + + if (ENABLE_BBSH_JOBCTL) line->cmdline = cmdline; + + // Parse command into argv[] + for (;;) { + char *end; + + // Skip leading whitespace and detect end of line. + start = skip_whitespace(start); + if (!*start || *start=='#') { + if (ENABLE_BBSH_JOBCTL) line->cmdlinelen = start-cmdline; + return 0; + } + + // Allocate next command structure if necessary + if (!*cmd) *cmd = xzalloc(sizeof(struct command)+8*sizeof(char *)); + + // Parse next argument and add the results to argv[] + end = parse_word(start, cmd); + + // If we hit the end of this command, how did it end? + if (!end) { + if (ENABLE_BBSH_PIPES && *start) { + if (*start==';') { + start++; + break; + } + // handle | & < > >> << || && + } + break; + } + start = end; + } + + if (ENABLE_BBSH_JOBCTL) line->cmdlinelen = start-cmdline; + + return start; +} + +// Execute the commands in a pipeline +static int run_pipeline(struct pipeline *line) +{ + struct command *cmd = line->cmd; + if (!cmd || !cmd->argc) return 0; + + // Handle local commands. This is totally fake and plastic. + if (cmd->argc==2 && !strcmp(cmd->argv[0],"cd")) + chdir(cmd->argv[1]); + else if(!strcmp(cmd->argv[0],"exit")) + exit(cmd->argc>1 ? atoi(cmd->argv[1]) : 0); + else { + int status; + pid_t pid=fork(); + if(!pid) { + run_applet_by_name(cmd->argv[0],cmd->argc,cmd->argv); + execvp(cmd->argv[0],cmd->argv); + printf("No %s",cmd->argv[0]); + exit(1); + } else waitpid(pid, &status, 0); + } + + return 0; +} + +static void free_cmd(void *data) +{ + struct command *cmd=(struct command *)data; + + while(cmd->argc) free(cmd->argv[--cmd->argc]); +} + + +static void handle(char *command) +{ + struct pipeline line; + char *start = command; + + for (;;) { + memset(&line,0,sizeof(struct pipeline)); + start = parse_pipeline(start, &line); + if (!line.cmd) break; + + run_pipeline(&line); + free_list(line.cmd, free_cmd); + } +} + +int bbsh_main(int argc, char *argv[]) +{ + char *command=NULL; + FILE *f; + + getopt32(argc, argv, "c:", &command); + + f = argv[optind] ? xfopen(argv[optind],"r") : NULL; + if (command) handle(command); + else { + unsigned cmdlen=0; + for (;;) { + if(!f) putchar('$'); + if(1 > getline(&command, &cmdlen,f ? : stdin)) break; + + handle(command); + } + if (ENABLE_FEATURE_CLEAN_UP) free(command); + } + + return 1; +} diff --git a/shell/cmdedit.c b/shell/cmdedit.c new file mode 100644 index 000000000..ceaa2e885 --- /dev/null +++ b/shell/cmdedit.c @@ -0,0 +1,1924 @@ +/* vi: set sw=4 ts=4: */ +/* + * Termios command line History and Editing. + * + * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license. + * Written by: Vladimir Oleynik <dzo@simtreas.ru> + * + * Used ideas: + * Adam Rogoyski <rogoyski@cs.utexas.edu> + * Dave Cinege <dcinege@psychosis.com> + * Jakub Jelinek (c) 1995 + * Erik Andersen <andersen@codepoet.org> (Majorly adjusted for busybox) + * + * This code is 'as is' with no warranty. + * + * + */ + +/* + Usage and Known bugs: + Terminal key codes are not extensive, and more will probably + need to be added. This version was created on Debian GNU/Linux 2.x. + Delete, Backspace, Home, End, and the arrow keys were tested + to work in an Xterm and console. Ctrl-A also works as Home. + Ctrl-E also works as End. + + Small bugs (simple effect): + - not true viewing if terminal size (x*y symbols) less + size (prompt + editor`s line + 2 symbols) + - not true viewing if length prompt less terminal width + */ + + +#include "busybox.h" +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <ctype.h> +#include <signal.h> +#include <limits.h> + +#include "cmdedit.h" + + +#ifdef CONFIG_LOCALE_SUPPORT +#define Isprint(c) isprint((c)) +#else +#define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') ) +#endif + +#ifdef TEST + +/* pretect redefined for test */ +#undef CONFIG_FEATURE_COMMAND_EDITING +#undef CONFIG_FEATURE_COMMAND_TAB_COMPLETION +#undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION +#undef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT +#undef CONFIG_FEATURE_CLEAN_UP + +#define CONFIG_FEATURE_COMMAND_EDITING +#define CONFIG_FEATURE_COMMAND_TAB_COMPLETION +#define CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION +#define CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT +#define CONFIG_FEATURE_CLEAN_UP + +#endif /* TEST */ + +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION +#include <dirent.h> +#include <sys/stat.h> +#endif + +#ifdef CONFIG_FEATURE_COMMAND_EDITING + +#if defined(CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(CONFIG_FEATURE_SH_FANCY_PROMPT) +#define CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR +#endif + +/* Maximum length of the linked list for the command line history */ +#ifndef CONFIG_FEATURE_COMMAND_HISTORY +#define MAX_HISTORY 15 +#else +#define MAX_HISTORY (CONFIG_FEATURE_COMMAND_HISTORY + 0) +#endif + +#if MAX_HISTORY > 0 +static char *history[MAX_HISTORY+1]; /* history + current */ +/* saved history lines */ +static int n_history; +/* current pointer to history line */ +static int cur_history; +#endif + +#include <termios.h> +#define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp) +#define getTermSettings(fd,argp) tcgetattr(fd, argp); + +/* Current termio and the previous termio before starting sh */ +static struct termios initial_settings, new_settings; + + +static +volatile int cmdedit_termw = 80; /* actual terminal width */ +static +volatile int handlers_sets = 0; /* Set next bites: */ + +enum { + SET_ATEXIT = 1, /* when atexit() has been called + and get euid,uid,gid to fast compare */ + SET_WCHG_HANDLERS = 2, /* winchg signal handler */ + SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */ +}; + + +static int cmdedit_x; /* real x terminal position */ +static int cmdedit_y; /* pseudoreal y terminal position */ +static int cmdedit_prmt_len; /* lenght prompt without colores string */ + +static int cursor; /* required global for signal handler */ +static int len; /* --- "" - - "" - -"- --""-- --""--- */ +static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */ +static +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT + const +#endif +char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */ + +#ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR +static char *user_buf = ""; +static char *home_pwd_buf = ""; +static int my_euid; +#endif + +#ifdef CONFIG_FEATURE_SH_FANCY_PROMPT +static char *hostname_buf; +static int num_ok_lines = 1; +#endif + + +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + +#ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR +static int my_euid; +#endif + +static int my_uid; +static int my_gid; + +#endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */ + +static void cmdedit_setwidth(int w, int redraw_flg); + +static void win_changed(int nsig) +{ + static sighandler_t previous_SIGWINCH_handler; /* for reset */ + + /* emulate || signal call */ + if (nsig == -SIGWINCH || nsig == SIGWINCH) { + int width = 0; + get_terminal_width_height(0, &width, NULL); + cmdedit_setwidth(width, nsig == SIGWINCH); + } + /* Unix not all standart in recall signal */ + + if (nsig == -SIGWINCH) /* save previous handler */ + previous_SIGWINCH_handler = signal(SIGWINCH, win_changed); + else if (nsig == SIGWINCH) /* signaled called handler */ + signal(SIGWINCH, win_changed); /* set for next call */ + else /* nsig == 0 */ + /* set previous handler */ + signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */ +} + +static void cmdedit_reset_term(void) +{ + if ((handlers_sets & SET_RESET_TERM) != 0) { +/* sparc and other have broken termios support: use old termio handling. */ + setTermSettings(STDIN_FILENO, (void *) &initial_settings); + handlers_sets &= ~SET_RESET_TERM; + } + if ((handlers_sets & SET_WCHG_HANDLERS) != 0) { + /* reset SIGWINCH handler to previous (default) */ + win_changed(0); + handlers_sets &= ~SET_WCHG_HANDLERS; + } + fflush(stdout); +} + + +/* special for recount position for scroll and remove terminal margin effect */ +static void cmdedit_set_out_char(int next_char) +{ + + int c = (int)((unsigned char) command_ps[cursor]); + + if (c == 0) + c = ' '; /* destroy end char? */ +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT + if (!Isprint(c)) { /* Inverse put non-printable characters */ + if (c >= 128) + c -= 128; + if (c < ' ') + c += '@'; + if (c == 127) + c = '?'; + printf("\033[7m%c\033[0m", c); + } else +#endif + if (initial_settings.c_lflag & ECHO) putchar(c); + if (++cmdedit_x >= cmdedit_termw) { + /* terminal is scrolled down */ + cmdedit_y++; + cmdedit_x = 0; + + if (!next_char) + next_char = ' '; + /* destroy "(auto)margin" */ + putchar(next_char); + putchar('\b'); + } + cursor++; +} + +/* Move to end line. Bonus: rewrite line from cursor */ +static void input_end(void) +{ + while (cursor < len) + cmdedit_set_out_char(0); +} + +/* Go to the next line */ +static void goto_new_line(void) +{ + input_end(); + if (cmdedit_x) + putchar('\n'); +} + + +static void out1str(const char *s) +{ + if ( s ) + fputs(s, stdout); +} + +static void beep(void) +{ + putchar('\007'); +} + +/* Move back one character */ +/* special for slow terminal */ +static void input_backward(int num) +{ + if (num > cursor) + num = cursor; + cursor -= num; /* new cursor (in command, not terminal) */ + + if (cmdedit_x >= num) { /* no to up line */ + cmdedit_x -= num; + if (num < 4) + while (num-- > 0) + putchar('\b'); + else + printf("\033[%dD", num); + } else { + int count_y; + + if (cmdedit_x) { + putchar('\r'); /* back to first terminal pos. */ + num -= cmdedit_x; /* set previous backward */ + } + count_y = 1 + num / cmdedit_termw; + printf("\033[%dA", count_y); + cmdedit_y -= count_y; + /* require forward after uping */ + cmdedit_x = cmdedit_termw * count_y - num; + printf("\033[%dC", cmdedit_x); /* set term cursor */ + } +} + +static void put_prompt(void) +{ + out1str(cmdedit_prompt); + cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */ + cursor = 0; + cmdedit_y = 0; /* new quasireal y */ +} + +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT +static void parse_prompt(const char *prmt_ptr) +{ + cmdedit_prompt = prmt_ptr; + cmdedit_prmt_len = strlen(prmt_ptr); + put_prompt(); +} +#else +static void parse_prompt(const char *prmt_ptr) +{ + int prmt_len = 0; + size_t cur_prmt_len = 0; + char flg_not_length = '['; + char *prmt_mem_ptr = xzalloc(1); + char *pwd_buf = xgetcwd(0); + char buf2[PATH_MAX + 1]; + char buf[2]; + char c; + char *pbuf; + + if (!pwd_buf) { + pwd_buf=(char *)bb_msg_unknown; + } + + while (*prmt_ptr) { + pbuf = buf; + pbuf[1] = 0; + c = *prmt_ptr++; + if (c == '\\') { + const char *cp = prmt_ptr; + int l; + + c = bb_process_escape_sequence(&prmt_ptr); + if(prmt_ptr==cp) { + if (*cp == 0) + break; + c = *prmt_ptr++; + switch (c) { +#ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR + case 'u': + pbuf = user_buf; + break; +#endif + case 'h': + pbuf = hostname_buf; + if (pbuf == 0) { + pbuf = xzalloc(256); + if (gethostname(pbuf, 255) < 0) { + strcpy(pbuf, "?"); + } else { + char *s = strchr(pbuf, '.'); + + if (s) + *s = 0; + } + hostname_buf = pbuf; + } + break; + case '$': + c = my_euid == 0 ? '#' : '$'; + break; +#ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR + case 'w': + pbuf = pwd_buf; + l = strlen(home_pwd_buf); + if (home_pwd_buf[0] != 0 && + strncmp(home_pwd_buf, pbuf, l) == 0 && + (pbuf[l]=='/' || pbuf[l]=='\0') && + strlen(pwd_buf+l)<PATH_MAX) { + pbuf = buf2; + *pbuf = '~'; + strcpy(pbuf+1, pwd_buf+l); + } + break; +#endif + case 'W': + pbuf = pwd_buf; + cp = strrchr(pbuf,'/'); + if ( (cp != NULL) && (cp != pbuf) ) + pbuf += (cp-pbuf)+1; + break; + case '!': + snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines); + break; + case 'e': case 'E': /* \e \E = \033 */ + c = '\033'; + break; + case 'x': case 'X': + for (l = 0; l < 3;) { + int h; + buf2[l++] = *prmt_ptr; + buf2[l] = 0; + h = strtol(buf2, &pbuf, 16); + if (h > UCHAR_MAX || (pbuf - buf2) < l) { + l--; + break; + } + prmt_ptr++; + } + buf2[l] = 0; + c = (char)strtol(buf2, 0, 16); + if(c==0) + c = '?'; + pbuf = buf; + break; + case '[': case ']': + if (c == flg_not_length) { + flg_not_length = flg_not_length == '[' ? ']' : '['; + continue; + } + break; + } + } + } + if(pbuf == buf) + *pbuf = c; + cur_prmt_len = strlen(pbuf); + prmt_len += cur_prmt_len; + if (flg_not_length != ']') + cmdedit_prmt_len += cur_prmt_len; + prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf); + } + if(pwd_buf!=(char *)bb_msg_unknown) + free(pwd_buf); + cmdedit_prompt = prmt_mem_ptr; + put_prompt(); +} +#endif + + +/* draw prompt, editor line, and clear tail */ +static void redraw(int y, int back_cursor) +{ + if (y > 0) /* up to start y */ + printf("\033[%dA", y); + putchar('\r'); + put_prompt(); + input_end(); /* rewrite */ + printf("\033[J"); /* destroy tail after cursor */ + input_backward(back_cursor); +} + +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI +#define DELBUFSIZ 128 +static char *delbuf; /* a (malloced) place to store deleted characters */ +static char *delp; +static char newdelflag; /* whether delbuf should be reused yet */ +#endif + +/* Delete the char in front of the cursor, optionally saving it + * for later putback */ +static void input_delete(int save) +{ + int j = cursor; + + if (j == len) + return; + +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI + if (save) { + if (newdelflag) { + if (!delbuf) + delbuf = malloc(DELBUFSIZ); + /* safe if malloc fails */ + delp = delbuf; + newdelflag = 0; + } + if (delbuf && (delp - delbuf < DELBUFSIZ)) + *delp++ = command_ps[j]; + } +#endif + + strcpy(command_ps + j, command_ps + j + 1); + len--; + input_end(); /* rewrite new line */ + cmdedit_set_out_char(0); /* destroy end char */ + input_backward(cursor - j); /* back to old pos cursor */ +} + +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI +static void put(void) +{ + int ocursor, j = delp - delbuf; + if (j == 0) + return; + ocursor = cursor; + /* open hole and then fill it */ + memmove(command_ps + cursor + j, command_ps + cursor, len - cursor + 1); + strncpy(command_ps + cursor, delbuf, j); + len += j; + input_end(); /* rewrite new line */ + input_backward(cursor-ocursor-j+1); /* at end of new text */ +} +#endif + +/* Delete the char in back of the cursor */ +static void input_backspace(void) +{ + if (cursor > 0) { + input_backward(1); + input_delete(0); + } +} + + +/* Move forward one character */ +static void input_forward(void) +{ + if (cursor < len) + cmdedit_set_out_char(command_ps[cursor + 1]); +} + +static void cmdedit_setwidth(int w, int redraw_flg) +{ + cmdedit_termw = cmdedit_prmt_len + 2; + if (w <= cmdedit_termw) { + cmdedit_termw = cmdedit_termw % w; + } + if (w > cmdedit_termw) { + cmdedit_termw = w; + + if (redraw_flg) { + /* new y for current cursor */ + int new_y = (cursor + cmdedit_prmt_len) / w; + + /* redraw */ + redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor); + fflush(stdout); + } + } +} + +static void cmdedit_init(void) +{ + cmdedit_prmt_len = 0; + if ((handlers_sets & SET_WCHG_HANDLERS) == 0) { + /* emulate usage handler to set handler and call yours work */ + win_changed(-SIGWINCH); + handlers_sets |= SET_WCHG_HANDLERS; + } + + if ((handlers_sets & SET_ATEXIT) == 0) { +#ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR + struct passwd *entry; + + my_euid = geteuid(); + entry = getpwuid(my_euid); + if (entry) { + user_buf = xstrdup(entry->pw_name); + home_pwd_buf = xstrdup(entry->pw_dir); + } +#endif + +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + +#ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR + my_euid = geteuid(); +#endif + my_uid = getuid(); + my_gid = getgid(); +#endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */ + handlers_sets |= SET_ATEXIT; + atexit(cmdedit_reset_term); /* be sure to do this only once */ + } +} + +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + +static char **matches; +static int num_matches; +static char *add_char_to_match; + +static void add_match(char *matched, int add_char) +{ + int nm = num_matches; + int nm1 = nm + 1; + + matches = xrealloc(matches, nm1 * sizeof(char *)); + add_char_to_match = xrealloc(add_char_to_match, nm1); + matches[nm] = matched; + add_char_to_match[nm] = (char)add_char; + num_matches++; +} + +static int is_execute(const struct stat *st) +{ + if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) || + (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) || + (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) || + (st->st_mode & S_IXOTH)) return TRUE; + return FALSE; +} + +#ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION + +static void username_tab_completion(char *ud, char *with_shash_flg) +{ + struct passwd *entry; + int userlen; + + ud++; /* ~user/... to user/... */ + userlen = strlen(ud); + + if (with_shash_flg) { /* "~/..." or "~user/..." */ + char *sav_ud = ud - 1; + char *home = 0; + char *temp; + + if (*ud == '/') { /* "~/..." */ + home = home_pwd_buf; + } else { + /* "~user/..." */ + temp = strchr(ud, '/'); + *temp = 0; /* ~user\0 */ + entry = getpwnam(ud); + *temp = '/'; /* restore ~user/... */ + ud = temp; + if (entry) + home = entry->pw_dir; + } + if (home) { + if ((userlen + strlen(home) + 1) < BUFSIZ) { + char temp2[BUFSIZ]; /* argument size */ + + /* /home/user/... */ + sprintf(temp2, "%s%s", home, ud); + strcpy(sav_ud, temp2); + } + } + } else { + /* "~[^/]*" */ + setpwent(); + + while ((entry = getpwent()) != NULL) { + /* Null usernames should result in all users as possible completions. */ + if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) { + add_match(xasprintf("~%s", entry->pw_name), '/'); + } + } + + endpwent(); + } +} +#endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */ + +enum { + FIND_EXE_ONLY = 0, + FIND_DIR_ONLY = 1, + FIND_FILE_ONLY = 2, +}; + +#ifdef CONFIG_ASH +const char *cmdedit_path_lookup; +#else +#define cmdedit_path_lookup getenv("PATH") +#endif + +static int path_parse(char ***p, int flags) +{ + int npth; + const char *tmp; + const char *pth; + + /* if not setenv PATH variable, to search cur dir "." */ + if (flags != FIND_EXE_ONLY || (pth = cmdedit_path_lookup) == 0 || + /* PATH=<empty> or PATH=:<empty> */ + *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) { + return 1; + } + + tmp = pth; + npth = 0; + + for (;;) { + npth++; /* count words is + 1 count ':' */ + tmp = strchr(tmp, ':'); + if (tmp) { + if (*++tmp == 0) + break; /* :<empty> */ + } else + break; + } + + *p = xmalloc(npth * sizeof(char *)); + + tmp = pth; + (*p)[0] = xstrdup(tmp); + npth = 1; /* count words is + 1 count ':' */ + + for (;;) { + tmp = strchr(tmp, ':'); + if (tmp) { + (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */ + if (*++tmp == 0) + break; /* :<empty> */ + } else + break; + (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */ + } + + return npth; +} + +static char *add_quote_for_spec_chars(char *found, int add) +{ + int l = 0; + char *s = xmalloc((strlen(found) + 1) * 2); + + while (*found) { + if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found)) + s[l++] = '\\'; + s[l++] = *found++; + } + if(add) + s[l++] = (char)add; + s[l] = 0; + return s; +} + +static void exe_n_cwd_tab_completion(char *command, int type) +{ + DIR *dir; + struct dirent *next; + char dirbuf[BUFSIZ]; + struct stat st; + char *path1[1]; + char **paths = path1; + int npaths; + int i; + char *found; + char *pfind = strrchr(command, '/'); + + path1[0] = "."; + + if (pfind == NULL) { + /* no dir, if flags==EXE_ONLY - get paths, else "." */ + npaths = path_parse(&paths, type); + pfind = command; + } else { + /* with dir */ + /* save for change */ + strcpy(dirbuf, command); + /* set dir only */ + dirbuf[(pfind - command) + 1] = 0; +#ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION + if (dirbuf[0] == '~') /* ~/... or ~user/... */ + username_tab_completion(dirbuf, dirbuf); +#endif + /* "strip" dirname in command */ + pfind++; + + paths[0] = dirbuf; + npaths = 1; /* only 1 dir */ + } + + for (i = 0; i < npaths; i++) { + + dir = opendir(paths[i]); + if (!dir) /* Don't print an error */ + continue; + + while ((next = readdir(dir)) != NULL) { + char *str_found = next->d_name; + int add_chr = 0; + + /* matched ? */ + if (strncmp(str_found, pfind, strlen(pfind))) + continue; + /* not see .name without .match */ + if (*str_found == '.' && *pfind == 0) { + if (*paths[i] == '/' && paths[i][1] == 0 + && str_found[1] == 0) str_found = ""; /* only "/" */ + else + continue; + } + found = concat_path_file(paths[i], str_found); + /* hmm, remover in progress? */ + if (stat(found, &st) < 0) + goto cont; + /* find with dirs ? */ + if (paths[i] != dirbuf) + strcpy(found, next->d_name); /* only name */ + if (S_ISDIR(st.st_mode)) { + /* name is directory */ + char *e = found + strlen(found) - 1; + + add_chr = '/'; + if(*e == '/') + *e = '\0'; + } else { + /* not put found file if search only dirs for cd */ + if (type == FIND_DIR_ONLY) + goto cont; + if (type == FIND_FILE_ONLY || + (type == FIND_EXE_ONLY && is_execute(&st))) + add_chr = ' '; + } + /* Add it to the list */ + add_match(found, add_chr); + continue; +cont: + free(found); + } + closedir(dir); + } + if (paths != path1) { + free(paths[0]); /* allocated memory only in first member */ + free(paths); + } +} + + +#define QUOT (UCHAR_MAX+1) + +#define collapse_pos(is, in) { \ + memmove(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \ + memmove(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); } + +static int find_match(char *matchBuf, int *len_with_quotes) +{ + int i, j; + int command_mode; + int c, c2; + int int_buf[BUFSIZ + 1]; + int pos_buf[BUFSIZ + 1]; + + /* set to integer dimension characters and own positions */ + for (i = 0;; i++) { + int_buf[i] = (int) ((unsigned char) matchBuf[i]); + if (int_buf[i] == 0) { + pos_buf[i] = -1; /* indicator end line */ + break; + } else + pos_buf[i] = i; + } + + /* mask \+symbol and convert '\t' to ' ' */ + for (i = j = 0; matchBuf[i]; i++, j++) + if (matchBuf[i] == '\\') { + collapse_pos(j, j + 1); + int_buf[j] |= QUOT; + i++; +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT + if (matchBuf[i] == '\t') /* algorithm equivalent */ + int_buf[j] = ' ' | QUOT; +#endif + } +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT + else if (matchBuf[i] == '\t') + int_buf[j] = ' '; +#endif + + /* mask "symbols" or 'symbols' */ + c2 = 0; + for (i = 0; int_buf[i]; i++) { + c = int_buf[i]; + if (c == '\'' || c == '"') { + if (c2 == 0) + c2 = c; + else { + if (c == c2) + c2 = 0; + else + int_buf[i] |= QUOT; + } + } else if (c2 != 0 && c != '$') + int_buf[i] |= QUOT; + } + + /* skip commands with arguments if line have commands delimiters */ + /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */ + for (i = 0; int_buf[i]; i++) { + c = int_buf[i]; + c2 = int_buf[i + 1]; + j = i ? int_buf[i - 1] : -1; + command_mode = 0; + if (c == ';' || c == '&' || c == '|') { + command_mode = 1 + (c == c2); + if (c == '&') { + if (j == '>' || j == '<') + command_mode = 0; + } else if (c == '|' && j == '>') + command_mode = 0; + } + if (command_mode) { + collapse_pos(0, i + command_mode); + i = -1; /* hack incremet */ + } + } + /* collapse `command...` */ + for (i = 0; int_buf[i]; i++) + if (int_buf[i] == '`') { + for (j = i + 1; int_buf[j]; j++) + if (int_buf[j] == '`') { + collapse_pos(i, j + 1); + j = 0; + break; + } + if (j) { + /* not found close ` - command mode, collapse all previous */ + collapse_pos(0, i + 1); + break; + } else + i--; /* hack incremet */ + } + + /* collapse (command...(command...)...) or {command...{command...}...} */ + c = 0; /* "recursive" level */ + c2 = 0; + for (i = 0; int_buf[i]; i++) + if (int_buf[i] == '(' || int_buf[i] == '{') { + if (int_buf[i] == '(') + c++; + else + c2++; + collapse_pos(0, i + 1); + i = -1; /* hack incremet */ + } + for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++) + if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) { + if (int_buf[i] == ')') + c--; + else + c2--; + collapse_pos(0, i + 1); + i = -1; /* hack incremet */ + } + + /* skip first not quote space */ + for (i = 0; int_buf[i]; i++) + if (int_buf[i] != ' ') + break; + if (i) + collapse_pos(0, i); + + /* set find mode for completion */ + command_mode = FIND_EXE_ONLY; + for (i = 0; int_buf[i]; i++) + if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') { + if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY + && matchBuf[pos_buf[0]]=='c' + && matchBuf[pos_buf[1]]=='d' ) + command_mode = FIND_DIR_ONLY; + else { + command_mode = FIND_FILE_ONLY; + break; + } + } + /* "strlen" */ + for (i = 0; int_buf[i]; i++); + /* find last word */ + for (--i; i >= 0; i--) { + c = int_buf[i]; + if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') { + collapse_pos(0, i + 1); + break; + } + } + /* skip first not quoted '\'' or '"' */ + for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++); + /* collapse quote or unquote // or /~ */ + while ((int_buf[i] & ~QUOT) == '/' && + ((int_buf[i + 1] & ~QUOT) == '/' + || (int_buf[i + 1] & ~QUOT) == '~')) { + i++; + } + + /* set only match and destroy quotes */ + j = 0; + for (c = 0; pos_buf[i] >= 0; i++) { + matchBuf[c++] = matchBuf[pos_buf[i]]; + j = pos_buf[i] + 1; + } + matchBuf[c] = 0; + /* old lenght matchBuf with quotes symbols */ + *len_with_quotes = j ? j - pos_buf[0] : 0; + + return command_mode; +} + +/* + display by column original ideas from ls applet, + very optimize by my :) +*/ +static void showfiles(void) +{ + int ncols, row; + int column_width = 0; + int nfiles = num_matches; + int nrows = nfiles; + char str_add_chr[2]; + int l; + + /* find the longest file name- use that as the column width */ + for (row = 0; row < nrows; row++) { + l = strlen(matches[row]); + if(add_char_to_match[row]) + l++; + if (column_width < l) + column_width = l; + } + column_width += 2; /* min space for columns */ + ncols = cmdedit_termw / column_width; + + if (ncols > 1) { + nrows /= ncols; + if(nfiles % ncols) + nrows++; /* round up fractionals */ + } else { + ncols = 1; + } + str_add_chr[1] = 0; + for (row = 0; row < nrows; row++) { + int n = row; + int nc; + int acol; + + for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) { + str_add_chr[0] = add_char_to_match[n]; + acol = str_add_chr[0] ? column_width - 1 : column_width; + printf("%s%s", matches[n], str_add_chr); + l = strlen(matches[n]); + while(l < acol) { + putchar(' '); + l++; + } + } + str_add_chr[0] = add_char_to_match[n]; + printf("%s%s\n", matches[n], str_add_chr); + } +} + + +static void input_tab(int *lastWasTab) +{ + /* Do TAB completion */ + if (lastWasTab == 0) { /* free all memory */ + if (matches) { + while (num_matches > 0) + free(matches[--num_matches]); + free(matches); + matches = (char **) NULL; + free(add_char_to_match); + add_char_to_match = NULL; + } + return; + } + if (! *lastWasTab) { + + char *tmp, *tmp1; + int len_found; + char matchBuf[BUFSIZ]; + int find_type; + int recalc_pos; + + *lastWasTab = TRUE; /* flop trigger */ + + /* Make a local copy of the string -- up + * to the position of the cursor */ + tmp = strncpy(matchBuf, command_ps, cursor); + tmp[cursor] = 0; + + find_type = find_match(matchBuf, &recalc_pos); + + /* Free up any memory already allocated */ + input_tab(0); + +#ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION + /* If the word starts with `~' and there is no slash in the word, + * then try completing this word as a username. */ + + if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0) + username_tab_completion(matchBuf, NULL); + if (!matches) +#endif + /* Try to match any executable in our path and everything + * in the current working directory that matches. */ + exe_n_cwd_tab_completion(matchBuf, find_type); + /* Remove duplicate found and sort */ + if(matches) { + int i, j, n, srt; + /* bubble */ + n = num_matches; + for(i=0; i<(n-1); i++) { + for(j=i+1; j<n; j++) { + if(matches[i]!=NULL && matches[j]!=NULL) { + srt = strcmp(matches[i], matches[j]); + if(srt == 0) { + free(matches[j]); + matches[j]=0; + } else if(srt > 0) { + tmp1 = matches[i]; + matches[i] = matches[j]; + matches[j] = tmp1; + srt = add_char_to_match[i]; + add_char_to_match[i] = add_char_to_match[j]; + add_char_to_match[j] = srt; + } + } + } + } + j = n; + n = 0; + for(i=0; i<j; i++) + if(matches[i]) { + matches[n]=matches[i]; + add_char_to_match[n]=add_char_to_match[i]; + n++; + } + num_matches = n; + } + /* Did we find exactly one match? */ + if (!matches || num_matches > 1) { + + beep(); + if (!matches) + return; /* not found */ + /* find minimal match */ + tmp1 = xstrdup(matches[0]); + for (tmp = tmp1; *tmp; tmp++) + for (len_found = 1; len_found < num_matches; len_found++) + if (matches[len_found][(tmp - tmp1)] != *tmp) { + *tmp = 0; + break; + } + if (*tmp1 == 0) { /* have unique */ + free(tmp1); + return; + } + tmp = add_quote_for_spec_chars(tmp1, 0); + free(tmp1); + } else { /* one match */ + tmp = add_quote_for_spec_chars(matches[0], add_char_to_match[0]); + /* for next completion current found */ + *lastWasTab = FALSE; + } + len_found = strlen(tmp); + /* have space to placed match? */ + if ((len_found - strlen(matchBuf) + len) < BUFSIZ) { + + /* before word for match */ + command_ps[cursor - recalc_pos] = 0; + /* save tail line */ + strcpy(matchBuf, command_ps + cursor); + /* add match */ + strcat(command_ps, tmp); + /* add tail */ + strcat(command_ps, matchBuf); + /* back to begin word for match */ + input_backward(recalc_pos); + /* new pos */ + recalc_pos = cursor + len_found; + /* new len */ + len = strlen(command_ps); + /* write out the matched command */ + redraw(cmdedit_y, len - recalc_pos); + } + free(tmp); + } else { + /* Ok -- the last char was a TAB. Since they + * just hit TAB again, print a list of all the + * available choices... */ + if (matches && num_matches > 0) { + int sav_cursor = cursor; /* change goto_new_line() */ + + /* Go to the next line */ + goto_new_line(); + showfiles(); + redraw(0, len - sav_cursor); + } + } +} +#endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */ + +#if MAX_HISTORY > 0 +static void get_previous_history(void) +{ + if(command_ps[0] != 0 || history[cur_history] == 0) { + free(history[cur_history]); + history[cur_history] = xstrdup(command_ps); + } + cur_history--; +} + +static int get_next_history(void) +{ + int ch = cur_history; + + if (ch < n_history) { + get_previous_history(); /* save the current history line */ + cur_history = ch + 1; + return cur_history; + } else { + beep(); + return 0; + } +} + +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY +void load_history ( const char *fromfile ) +{ + FILE *fp; + int hi; + + /* cleanup old */ + + for(hi = n_history; hi > 0; ) { + hi--; + free ( history [hi] ); + } + + if (( fp = fopen ( fromfile, "r" ))) { + + for ( hi = 0; hi < MAX_HISTORY; ) { + char * hl = xmalloc_getline(fp); + int l; + + if(!hl) + break; + l = strlen(hl); + if(l >= BUFSIZ) + hl[BUFSIZ-1] = 0; + if(l == 0 || hl[0] == ' ') { + free(hl); + continue; + } + history [hi++] = hl; + } + fclose ( fp ); + } + cur_history = n_history = hi; +} + +void save_history ( const char *tofile ) +{ + FILE *fp = fopen ( tofile, "w" ); + + if ( fp ) { + int i; + + for ( i = 0; i < n_history; i++ ) { + fprintf(fp, "%s\n", history [i]); + } + fclose ( fp ); + } +} +#endif + +#endif + +enum { + ESC = 27, + DEL = 127, +}; + + +/* + * This function is used to grab a character buffer + * from the input file descriptor and allows you to + * a string with full command editing (sort of like + * a mini readline). + * + * The following standard commands are not implemented: + * ESC-b -- Move back one word + * ESC-f -- Move forward one word + * ESC-d -- Delete back one word + * ESC-h -- Delete forward one word + * CTL-t -- Transpose two characters + * + * Minimalist vi-style command line editing available if configured. + * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us> + * + */ + +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI +static int vi_mode; + +void setvimode ( int viflag ) +{ + vi_mode = viflag; +} + +static void +vi_Word_motion(char *command, int eat) +{ + while (cursor < len && !isspace(command[cursor])) + input_forward(); + if (eat) while (cursor < len && isspace(command[cursor])) + input_forward(); +} + +static void +vi_word_motion(char *command, int eat) +{ + if (isalnum(command[cursor]) || command[cursor] == '_') { + while (cursor < len && + (isalnum(command[cursor+1]) || + command[cursor+1] == '_')) + input_forward(); + } else if (ispunct(command[cursor])) { + while (cursor < len && + (ispunct(command[cursor+1]))) + input_forward(); + } + + if (cursor < len) + input_forward(); + + if (eat && cursor < len && isspace(command[cursor])) + while (cursor < len && isspace(command[cursor])) + input_forward(); +} + +static void +vi_End_motion(char *command) +{ + input_forward(); + while (cursor < len && isspace(command[cursor])) + input_forward(); + while (cursor < len-1 && !isspace(command[cursor+1])) + input_forward(); +} + +static void +vi_end_motion(char *command) +{ + if (cursor >= len-1) + return; + input_forward(); + while (cursor < len-1 && isspace(command[cursor])) + input_forward(); + if (cursor >= len-1) + return; + if (isalnum(command[cursor]) || command[cursor] == '_') { + while (cursor < len-1 && + (isalnum(command[cursor+1]) || + command[cursor+1] == '_')) + input_forward(); + } else if (ispunct(command[cursor])) { + while (cursor < len-1 && + (ispunct(command[cursor+1]))) + input_forward(); + } +} + +static void +vi_Back_motion(char *command) +{ + while (cursor > 0 && isspace(command[cursor-1])) + input_backward(1); + while (cursor > 0 && !isspace(command[cursor-1])) + input_backward(1); +} + +static void +vi_back_motion(char *command) +{ + if (cursor <= 0) + return; + input_backward(1); + while (cursor > 0 && isspace(command[cursor])) + input_backward(1); + if (cursor <= 0) + return; + if (isalnum(command[cursor]) || command[cursor] == '_') { + while (cursor > 0 && + (isalnum(command[cursor-1]) || + command[cursor-1] == '_')) + input_backward(1); + } else if (ispunct(command[cursor])) { + while (cursor > 0 && + (ispunct(command[cursor-1]))) + input_backward(1); + } +} +#endif + +/* + * the emacs and vi modes share much of the code in the big + * command loop. commands entered when in vi's command mode (aka + * "escape mode") get an extra bit added to distinguish them -- + * this keeps them from being self-inserted. this clutters the + * big switch a bit, but keeps all the code in one place. + */ + +#define vbit 0x100 + +/* leave out the "vi-mode"-only case labels if vi editing isn't + * configured. */ +#define vi_case(caselabel) USE_FEATURE_COMMAND_EDITING(caselabel) + +/* convert uppercase ascii to equivalent control char, for readability */ +#define CNTRL(uc_char) ((uc_char) - 0x40) + + +int cmdedit_read_input(char *prompt, char command[BUFSIZ]) +{ + + int break_out = 0; + int lastWasTab = FALSE; + unsigned char c; + unsigned int ic; +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI + unsigned int prevc; + int vi_cmdmode = 0; +#endif + /* prepare before init handlers */ + cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */ + len = 0; + command_ps = command; + + getTermSettings(0, (void *) &initial_settings); + memcpy(&new_settings, &initial_settings, sizeof(struct termios)); + new_settings.c_lflag &= ~ICANON; /* unbuffered input */ + /* Turn off echoing and CTRL-C, so we can trap it */ + new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG); + /* Hmm, in linux c_cc[] not parsed if set ~ICANON */ + new_settings.c_cc[VMIN] = 1; + new_settings.c_cc[VTIME] = 0; + /* Turn off CTRL-C, so we can trap it */ +# ifndef _POSIX_VDISABLE +# define _POSIX_VDISABLE '\0' +# endif + new_settings.c_cc[VINTR] = _POSIX_VDISABLE; + command[0] = 0; + + setTermSettings(0, (void *) &new_settings); + handlers_sets |= SET_RESET_TERM; + + /* Now initialize things */ + cmdedit_init(); + /* Print out the command prompt */ + parse_prompt(prompt); + + while (1) { + + fflush(stdout); /* buffered out to fast */ + + if (safe_read(0, &c, 1) < 1) + /* if we can't read input then exit */ + goto prepare_to_die; + + ic = c; + +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI + newdelflag = 1; + if (vi_cmdmode) + ic |= vbit; +#endif + switch (ic) + { + case '\n': + case '\r': + vi_case( case '\n'|vbit: ) + vi_case( case '\r'|vbit: ) + /* Enter */ + goto_new_line(); + break_out = 1; + break; + case CNTRL('A'): + vi_case( case '0'|vbit: ) + /* Control-a -- Beginning of line */ + input_backward(cursor); + break; + case CNTRL('B'): + vi_case( case 'h'|vbit: ) + vi_case( case '\b'|vbit: ) + vi_case( case DEL|vbit: ) + /* Control-b -- Move back one character */ + input_backward(1); + break; + case CNTRL('C'): + vi_case( case CNTRL('C')|vbit: ) + /* Control-c -- stop gathering input */ + goto_new_line(); +#ifndef CONFIG_ASH + command[0] = 0; + len = 0; + lastWasTab = FALSE; + put_prompt(); +#else + len = 0; + break_out = -1; /* to control traps */ +#endif + break; + case CNTRL('D'): + /* Control-d -- Delete one character, or exit + * if the len=0 and no chars to delete */ + if (len == 0) { + errno = 0; +prepare_to_die: +#if !defined(CONFIG_ASH) + printf("exit"); + goto_new_line(); + /* cmdedit_reset_term() called in atexit */ + exit(EXIT_SUCCESS); +#else + /* to control stopped jobs */ + len = break_out = -1; + break; +#endif + } else { + input_delete(0); + } + break; + case CNTRL('E'): + vi_case( case '$'|vbit: ) + /* Control-e -- End of line */ + input_end(); + break; + case CNTRL('F'): + vi_case( case 'l'|vbit: ) + vi_case( case ' '|vbit: ) + /* Control-f -- Move forward one character */ + input_forward(); + break; + case '\b': + case DEL: + /* Control-h and DEL */ + input_backspace(); + break; + case '\t': +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + input_tab(&lastWasTab); +#endif + break; + case CNTRL('K'): + /* Control-k -- clear to end of line */ + *(command + cursor) = 0; + len = cursor; + printf("\033[J"); + break; + case CNTRL('L'): + vi_case( case CNTRL('L')|vbit: ) + /* Control-l -- clear screen */ + printf("\033[H"); + redraw(0, len-cursor); + break; +#if MAX_HISTORY > 0 + case CNTRL('N'): + vi_case( case CNTRL('N')|vbit: ) + vi_case( case 'j'|vbit: ) + /* Control-n -- Get next command in history */ + if (get_next_history()) + goto rewrite_line; + break; + case CNTRL('P'): + vi_case( case CNTRL('P')|vbit: ) + vi_case( case 'k'|vbit: ) + /* Control-p -- Get previous command from history */ + if (cur_history > 0) { + get_previous_history(); + goto rewrite_line; + } else { + beep(); + } + break; +#endif + case CNTRL('U'): + vi_case( case CNTRL('U')|vbit: ) + /* Control-U -- Clear line before cursor */ + if (cursor) { + strcpy(command, command + cursor); + redraw(cmdedit_y, len -= cursor); + } + break; + case CNTRL('W'): + vi_case( case CNTRL('W')|vbit: ) + /* Control-W -- Remove the last word */ + while (cursor > 0 && isspace(command[cursor-1])) + input_backspace(); + while (cursor > 0 &&!isspace(command[cursor-1])) + input_backspace(); + break; +#if ENABLE_FEATURE_COMMAND_EDITING_VI + case 'i'|vbit: + vi_cmdmode = 0; + break; + case 'I'|vbit: + input_backward(cursor); + vi_cmdmode = 0; + break; + case 'a'|vbit: + input_forward(); + vi_cmdmode = 0; + break; + case 'A'|vbit: + input_end(); + vi_cmdmode = 0; + break; + case 'x'|vbit: + input_delete(1); + break; + case 'X'|vbit: + if (cursor > 0) { + input_backward(1); + input_delete(1); + } + break; + case 'W'|vbit: + vi_Word_motion(command, 1); + break; + case 'w'|vbit: + vi_word_motion(command, 1); + break; + case 'E'|vbit: + vi_End_motion(command); + break; + case 'e'|vbit: + vi_end_motion(command); + break; + case 'B'|vbit: + vi_Back_motion(command); + break; + case 'b'|vbit: + vi_back_motion(command); + break; + case 'C'|vbit: + vi_cmdmode = 0; + /* fall through */ + case 'D'|vbit: + goto clear_to_eol; + + case 'c'|vbit: + vi_cmdmode = 0; + /* fall through */ + case 'd'|vbit: + { + int nc, sc; + sc = cursor; + prevc = ic; + if (safe_read(0, &c, 1) < 1) + goto prepare_to_die; + if (c == (prevc & 0xff)) { + /* "cc", "dd" */ + input_backward(cursor); + goto clear_to_eol; + break; + } + switch(c) { + case 'w': + case 'W': + case 'e': + case 'E': + switch (c) { + case 'w': /* "dw", "cw" */ + vi_word_motion(command, vi_cmdmode); + break; + case 'W': /* 'dW', 'cW' */ + vi_Word_motion(command, vi_cmdmode); + break; + case 'e': /* 'de', 'ce' */ + vi_end_motion(command); + input_forward(); + break; + case 'E': /* 'dE', 'cE' */ + vi_End_motion(command); + input_forward(); + break; + } + nc = cursor; + input_backward(cursor - sc); + while (nc-- > cursor) + input_delete(1); + break; + case 'b': /* "db", "cb" */ + case 'B': /* implemented as B */ + if (c == 'b') + vi_back_motion(command); + else + vi_Back_motion(command); + while (sc-- > cursor) + input_delete(1); + break; + case ' ': /* "d ", "c " */ + input_delete(1); + break; + case '$': /* "d$", "c$" */ + clear_to_eol: + while (cursor < len) + input_delete(1); + break; + } + } + break; + case 'p'|vbit: + input_forward(); + /* fallthrough */ + case 'P'|vbit: + put(); + break; + case 'r'|vbit: + if (safe_read(0, &c, 1) < 1) + goto prepare_to_die; + if (c == 0) + beep(); + else { + *(command + cursor) = c; + putchar(c); + putchar('\b'); + } + break; +#endif /* CONFIG_FEATURE_COMMAND_EDITING_VI */ + + case ESC: + +#if ENABLE_FEATURE_COMMAND_EDITING_VI + if (vi_mode) { + /* ESC: insert mode --> command mode */ + vi_cmdmode = 1; + input_backward(1); + break; + } +#endif + /* escape sequence follows */ + if (safe_read(0, &c, 1) < 1) + goto prepare_to_die; + /* different vt100 emulations */ + if (c == '[' || c == 'O') { + vi_case( case '['|vbit: ) + vi_case( case 'O'|vbit: ) + if (safe_read(0, &c, 1) < 1) + goto prepare_to_die; + } + if (c >= '1' && c <= '9') { + unsigned char dummy; + + if (safe_read(0, &dummy, 1) < 1) + goto prepare_to_die; + if(dummy != '~') + c = 0; + } + switch (c) { +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + case '\t': /* Alt-Tab */ + + input_tab(&lastWasTab); + break; +#endif +#if MAX_HISTORY > 0 + case 'A': + /* Up Arrow -- Get previous command from history */ + if (cur_history > 0) { + get_previous_history(); + goto rewrite_line; + } else { + beep(); + } + break; + case 'B': + /* Down Arrow -- Get next command in history */ + if (!get_next_history()) + break; + /* Rewrite the line with the selected history item */ +rewrite_line: + /* change command */ + len = strlen(strcpy(command, history[cur_history])); + /* redraw and go to eol (bol, in vi */ +#if ENABLE_FEATURE_COMMAND_EDITING_VI + redraw(cmdedit_y, vi_mode ? 9999:0); +#else + redraw(cmdedit_y, 0); +#endif + break; +#endif + case 'C': + /* Right Arrow -- Move forward one character */ + input_forward(); + break; + case 'D': + /* Left Arrow -- Move back one character */ + input_backward(1); + break; + case '3': + /* Delete */ + input_delete(0); + break; + case '1': + case 'H': + /* <Home> */ + input_backward(cursor); + break; + case '4': + case 'F': + /* <End> */ + input_end(); + break; + default: + c = 0; + beep(); + } + break; + + default: /* If it's regular input, do the normal thing */ +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT + /* Control-V -- Add non-printable symbol */ + if (c == CNTRL('V')) { + if (safe_read(0, &c, 1) < 1) + goto prepare_to_die; + if (c == 0) { + beep(); + break; + } + } else +#endif + { +#if ENABLE_FEATURE_COMMAND_EDITING_VI + if (vi_cmdmode) /* don't self-insert */ + break; +#endif + if (!Isprint(c)) /* Skip non-printable characters */ + break; + } + + if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ + break; + + len++; + + if (cursor == (len - 1)) { /* Append if at the end of the line */ + *(command + cursor) = c; + *(command + cursor + 1) = 0; + cmdedit_set_out_char(0); + } else { /* Insert otherwise */ + int sc = cursor; + + memmove(command + sc + 1, command + sc, len - sc); + *(command + sc) = c; + sc++; + /* rewrite from cursor */ + input_end(); + /* to prev x pos + 1 */ + input_backward(cursor - sc); + } + + break; + } + if (break_out) /* Enter is the command terminator, no more input. */ + break; + + if (c != '\t') + lastWasTab = FALSE; + } + + setTermSettings(0, (void *) &initial_settings); + handlers_sets &= ~SET_RESET_TERM; + +#if MAX_HISTORY > 0 + /* Handle command history log */ + /* cleanup may be saved current command line */ + if (len> 0) { /* no put empty line */ + int i = n_history; + + free(history[MAX_HISTORY]); + history[MAX_HISTORY] = 0; + /* After max history, remove the oldest command */ + if (i >= MAX_HISTORY) { + free(history[0]); + for(i = 0; i < (MAX_HISTORY-1); i++) + history[i] = history[i+1]; + } + history[i++] = xstrdup(command); + cur_history = i; + n_history = i; +#if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) + num_ok_lines++; +#endif + } +#else /* MAX_HISTORY == 0 */ +#if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) + if (len > 0) { /* no put empty line */ + num_ok_lines++; + } +#endif +#endif /* MAX_HISTORY > 0 */ + if (break_out > 0) { + command[len++] = '\n'; /* set '\n' */ + command[len] = 0; + } +#if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION) + input_tab(0); /* strong free */ +#endif +#if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) + free(cmdedit_prompt); +#endif + cmdedit_reset_term(); + return len; +} + + + +#endif /* CONFIG_FEATURE_COMMAND_EDITING */ + + +#ifdef TEST + +const char *applet_name = "debug stuff usage"; + +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT +#include <locale.h> +#endif + +int main(int argc, char **argv) +{ + char buff[BUFSIZ]; + char *prompt = +#if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) + "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\ +\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \ +\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]"; +#else + "% "; +#endif + +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT + setlocale(LC_ALL, ""); +#endif + while(1) { + int l; + l = cmdedit_read_input(prompt, buff); + if(l > 0 && buff[l-1] == '\n') { + buff[l-1] = 0; + printf("*** cmdedit_read_input() returned line =%s=\n", buff); + } else { + break; + } + } + printf("*** cmdedit_read_input() detect ^D\n"); + return 0; +} + +#endif /* TEST */ diff --git a/shell/cmdedit.h b/shell/cmdedit.h new file mode 100644 index 000000000..f5863921a --- /dev/null +++ b/shell/cmdedit.h @@ -0,0 +1,20 @@ +/* vi: set sw=4 ts=4: */ +#ifndef CMDEDIT_H +#define CMDEDIT_H + +int cmdedit_read_input(char* promptStr, char* command); + +#ifdef CONFIG_ASH +extern const char *cmdedit_path_lookup; +#endif + +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY +void load_history(const char *fromfile); +void save_history(const char *tofile); +#endif + +#if ENABLE_FEATURE_COMMAND_EDITING_VI +void setvimode(int viflag); +#endif + +#endif /* CMDEDIT_H */ diff --git a/shell/hush.c b/shell/hush.c new file mode 100644 index 000000000..a6e029e4e --- /dev/null +++ b/shell/hush.c @@ -0,0 +1,2875 @@ +/* vi: set sw=4 ts=4: */ +/* + * sh.c -- a prototype Bourne shell grammar parser + * Intended to follow the original Thompson and Ritchie + * "small and simple is beautiful" philosophy, which + * incidentally is a good match to today's BusyBox. + * + * Copyright (C) 2000,2001 Larry Doolittle <larry@doolittle.boa.org> + * + * Credits: + * The parser routines proper are all original material, first + * written Dec 2000 and Jan 2001 by Larry Doolittle. The + * execution engine, the builtins, and much of the underlying + * support has been adapted from busybox-0.49pre's lash, which is + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * written by Erik Andersen <andersen@codepoet.org>. That, in turn, + * is based in part on ladsh.c, by Michael K. Johnson and Erik W. + * Troan, which they placed in the public domain. I don't know + * how much of the Johnson/Troan code has survived the repeated + * rewrites. + * + * Other credits: + * simple_itoa() was lifted from boa-0.93.15 + * b_addchr() derived from similar w_addchar function in glibc-2.2 + * setup_redirect(), redirect_opt_num(), and big chunks of main() + * and many builtins derived from contributions by Erik Andersen + * miscellaneous bugfixes from Matt Kraai + * + * There are two big (and related) architecture differences between + * this parser and the lash parser. One is that this version is + * actually designed from the ground up to understand nearly all + * of the Bourne grammar. The second, consequential change is that + * the parser and input reader have been turned inside out. Now, + * the parser is in control, and asks for input as needed. The old + * way had the input reader in control, and it asked for parsing to + * take place as needed. The new way makes it much easier to properly + * handle the recursion implicit in the various substitutions, especially + * across continuation lines. + * + * Bash grammar not implemented: (how many of these were in original sh?) + * $@ (those sure look like weird quoting rules) + * $_ + * ! negation operator for pipes + * &> and >& redirection of stdout+stderr + * Brace Expansion + * Tilde Expansion + * fancy forms of Parameter Expansion + * aliases + * Arithmetic Expansion + * <(list) and >(list) Process Substitution + * reserved words: case, esac, select, function + * Here Documents ( << word ) + * Functions + * Major bugs: + * job handling woefully incomplete and buggy + * reserved word execution woefully incomplete and buggy + * to-do: + * port selected bugfixes from post-0.49 busybox lash - done? + * finish implementing reserved words: for, while, until, do, done + * change { and } from special chars to reserved words + * builtins: break, continue, eval, return, set, trap, ulimit + * test magic exec + * handle children going into background + * clean up recognition of null pipes + * check setting of global_argc and global_argv + * control-C handling, probably with longjmp + * follow IFS rules more precisely, including update semantics + * figure out what to do with backslash-newline + * explain why we use signal instead of sigaction + * propagate syntax errors, die on resource errors? + * continuation lines, both explicit and implicit - done? + * memory leak finding and plugging - done? + * more testing, especially quoting rules and redirection + * document how quoting rules not precisely followed for variable assignments + * maybe change map[] to use 2-bit entries + * (eventually) remove all the printf's + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include <ctype.h> /* isalpha, isdigit */ +#include <unistd.h> /* getpid */ +#include <stdlib.h> /* getenv, atoi */ +#include <string.h> /* strchr */ +#include <stdio.h> /* popen etc. */ +#include <glob.h> /* glob, of course */ +#include <stdarg.h> /* va_list */ +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> /* should be pretty obvious */ + +#include <sys/stat.h> /* ulimit */ +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> + +/* #include <dmalloc.h> */ +/* #define DEBUG_SHELL */ + +#include "cmdedit.h" + +#define SPECIAL_VAR_SYMBOL 03 +#define FLAG_EXIT_FROM_LOOP 1 +#define FLAG_PARSE_SEMICOLON (1 << 1) /* symbol ';' is special for parser */ +#define FLAG_REPARSING (1 << 2) /* >=2nd pass */ + +typedef enum { + REDIRECT_INPUT = 1, + REDIRECT_OVERWRITE = 2, + REDIRECT_APPEND = 3, + REDIRECT_HEREIS = 4, + REDIRECT_IO = 5 +} redir_type; + +/* The descrip member of this structure is only used to make debugging + * output pretty */ +static const struct {int mode; int default_fd; const char *descrip;} redir_table[] = { + { 0, 0, "()" }, + { O_RDONLY, 0, "<" }, + { O_CREAT|O_TRUNC|O_WRONLY, 1, ">" }, + { O_CREAT|O_APPEND|O_WRONLY, 1, ">>" }, + { O_RDONLY, -1, "<<" }, + { O_RDWR, 1, "<>" } +}; + +typedef enum { + PIPE_SEQ = 1, + PIPE_AND = 2, + PIPE_OR = 3, + PIPE_BG = 4, +} pipe_style; + +/* might eventually control execution */ +typedef enum { + RES_NONE = 0, + RES_IF = 1, + RES_THEN = 2, + RES_ELIF = 3, + RES_ELSE = 4, + RES_FI = 5, + RES_FOR = 6, + RES_WHILE = 7, + RES_UNTIL = 8, + RES_DO = 9, + RES_DONE = 10, + RES_XXXX = 11, + RES_IN = 12, + RES_SNTX = 13 +} reserved_style; +#define FLAG_END (1<<RES_NONE) +#define FLAG_IF (1<<RES_IF) +#define FLAG_THEN (1<<RES_THEN) +#define FLAG_ELIF (1<<RES_ELIF) +#define FLAG_ELSE (1<<RES_ELSE) +#define FLAG_FI (1<<RES_FI) +#define FLAG_FOR (1<<RES_FOR) +#define FLAG_WHILE (1<<RES_WHILE) +#define FLAG_UNTIL (1<<RES_UNTIL) +#define FLAG_DO (1<<RES_DO) +#define FLAG_DONE (1<<RES_DONE) +#define FLAG_IN (1<<RES_IN) +#define FLAG_START (1<<RES_XXXX) + +/* This holds pointers to the various results of parsing */ +struct p_context { + struct child_prog *child; + struct pipe *list_head; + struct pipe *pipe; + struct redir_struct *pending_redirect; + reserved_style w; + int old_flag; /* for figuring out valid reserved words */ + struct p_context *stack; + int type; /* define type of parser : ";$" common or special symbol */ + /* How about quoting status? */ +}; + +struct redir_struct { + redir_type type; /* type of redirection */ + int fd; /* file descriptor being redirected */ + int dup; /* -1, or file descriptor being duplicated */ + struct redir_struct *next; /* pointer to the next redirect in the list */ + glob_t word; /* *word.gl_pathv is the filename */ +}; + +struct child_prog { + pid_t pid; /* 0 if exited */ + char **argv; /* program name and arguments */ + struct pipe *group; /* if non-NULL, first in group or subshell */ + int subshell; /* flag, non-zero if group must be forked */ + struct redir_struct *redirects; /* I/O redirections */ + glob_t glob_result; /* result of parameter globbing */ + int is_stopped; /* is the program currently running? */ + struct pipe *family; /* pointer back to the child's parent pipe */ + int sp; /* number of SPECIAL_VAR_SYMBOL */ + int type; +}; + +struct pipe { + int jobid; /* job number */ + int num_progs; /* total number of programs in job */ + int running_progs; /* number of programs running */ + char *text; /* name of job */ + char *cmdbuf; /* buffer various argv's point into */ + pid_t pgrp; /* process group ID for the job */ + struct child_prog *progs; /* array of commands in pipe */ + struct pipe *next; /* to track background commands */ + int stopped_progs; /* number of programs alive, but stopped */ + int job_context; /* bitmask defining current context */ + pipe_style followup; /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */ + reserved_style r_mode; /* supports if, for, while, until */ +}; + +struct close_me { + int fd; + struct close_me *next; +}; + +struct variables { + char *name; + char *value; + int flg_export; + int flg_read_only; + struct variables *next; +}; + +/* globals, connect us to the outside world + * the first three support $?, $#, and $1 */ +static char **global_argv; +static int global_argc; +static int last_return_code; +extern char **environ; /* This is in <unistd.h>, but protected with __USE_GNU */ + +/* "globals" within this file */ +static char *ifs; +static unsigned char map[256]; +static int fake_mode; +static int interactive; +static struct close_me *close_me_head; +static const char *cwd; +static struct pipe *job_list; +static unsigned int last_bg_pid; +static int last_jobid; +static unsigned int shell_terminal; +static char *PS1; +static char *PS2; +static struct variables shell_ver = { "HUSH_VERSION", "0.01", 1, 1, 0 }; +static struct variables *top_vars = &shell_ver; + + +#define B_CHUNK (100) +#define B_NOSPAC 1 + +typedef struct { + char *data; + int length; + int maxlen; + int quote; + int nonnull; +} o_string; +#define NULL_O_STRING {NULL,0,0,0,0} +/* used for initialization: + o_string foo = NULL_O_STRING; */ + +/* I can almost use ordinary FILE *. Is open_memstream() universally + * available? Where is it documented? */ +struct in_str { + const char *p; + char peek_buf[2]; + int __promptme; + int promptmode; + FILE *file; + int (*get) (struct in_str *); + int (*peek) (struct in_str *); +}; +#define b_getch(input) ((input)->get(input)) +#define b_peek(input) ((input)->peek(input)) + +#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" + +struct built_in_command { + const char *cmd; /* name */ + const char *descr; /* description */ + int (*function) (struct child_prog *); /* function ptr */ +}; + +/* belongs in busybox.h */ +static int max(int a, int b) { + return (a>b)?a:b; +} + +/* This should be in utility.c */ +#ifdef DEBUG_SHELL +static void debug_printf(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} +/* broken, of course, but OK for testing */ +static char *indenter(int i) +{ + static char blanks[]=" "; + return &blanks[sizeof(blanks)-i-1]; +} +#else +#define debug_printf(...) do {;} while(0); +#endif +#define final_printf debug_printf + +static void __syntax(char *file, int line) { + bb_error_msg("syntax error %s:%d", file, line); +} +// NB: was __FILE__, but that produces full path sometimess, so... +#define syntax() __syntax("hush.c", __LINE__) + +/* Index of subroutines: */ +/* function prototypes for builtins */ +static int builtin_cd(struct child_prog *child); +static int builtin_env(struct child_prog *child); +static int builtin_eval(struct child_prog *child); +static int builtin_exec(struct child_prog *child); +static int builtin_exit(struct child_prog *child); +static int builtin_export(struct child_prog *child); +static int builtin_fg_bg(struct child_prog *child); +static int builtin_help(struct child_prog *child); +static int builtin_jobs(struct child_prog *child); +static int builtin_pwd(struct child_prog *child); +static int builtin_read(struct child_prog *child); +static int builtin_set(struct child_prog *child); +static int builtin_shift(struct child_prog *child); +static int builtin_source(struct child_prog *child); +static int builtin_umask(struct child_prog *child); +static int builtin_unset(struct child_prog *child); +static int builtin_not_written(struct child_prog *child); +/* o_string manipulation: */ +static int b_check_space(o_string *o, int len); +static int b_addchr(o_string *o, int ch); +static void b_reset(o_string *o); +static int b_addqchr(o_string *o, int ch, int quote); +static int b_adduint(o_string *o, unsigned int i); +/* in_str manipulations: */ +static int static_get(struct in_str *i); +static int static_peek(struct in_str *i); +static int file_get(struct in_str *i); +static int file_peek(struct in_str *i); +static void setup_file_in_str(struct in_str *i, FILE *f); +static void setup_string_in_str(struct in_str *i, const char *s); +/* close_me manipulations: */ +static void mark_open(int fd); +static void mark_closed(int fd); +static void close_all(void); +/* "run" the final data structures: */ +static int free_pipe_list(struct pipe *head, int indent); +static int free_pipe(struct pipe *pi, int indent); +/* really run the final data structures: */ +static int setup_redirects(struct child_prog *prog, int squirrel[]); +static int run_list_real(struct pipe *pi); +static void pseudo_exec(struct child_prog *child) ATTRIBUTE_NORETURN; +static int run_pipe_real(struct pipe *pi); +/* extended glob support: */ +static int globhack(const char *src, int flags, glob_t *pglob); +static int glob_needed(const char *s); +static int xglob(o_string *dest, int flags, glob_t *pglob); +/* variable assignment: */ +static int is_assignment(const char *s); +/* data structure manipulation: */ +static int setup_redirect(struct p_context *ctx, int fd, redir_type style, struct in_str *input); +static void initialize_context(struct p_context *ctx); +static int done_word(o_string *dest, struct p_context *ctx); +static int done_command(struct p_context *ctx); +static int done_pipe(struct p_context *ctx, pipe_style type); +/* primary string parsing: */ +static int redirect_dup_num(struct in_str *input); +static int redirect_opt_num(o_string *o); +static int process_command_subs(o_string *dest, struct p_context *ctx, struct in_str *input, int subst_end); +static int parse_group(o_string *dest, struct p_context *ctx, struct in_str *input, int ch); +static char *lookup_param(char *src); +static char *make_string(char **inp); +static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input); +static int parse_string(o_string *dest, struct p_context *ctx, const char *src); +static int parse_stream(o_string *dest, struct p_context *ctx, struct in_str *input0, int end_trigger); +/* setup: */ +static int parse_stream_outer(struct in_str *inp, int flag); +static int parse_string_outer(const char *s, int flag); +static int parse_file_outer(FILE *f); +/* job management: */ +static int checkjobs(struct pipe* fg_pipe); +static void insert_bg_job(struct pipe *pi); +static void remove_bg_job(struct pipe *pi); +/* local variable support */ +static char **make_list_in(char **inp, char *name); +static char *insert_var_value(char *inp); +static char *get_local_var(const char *var); +static void unset_local_var(const char *name); +static int set_local_var(const char *s, int flg_export); + +/* Table of built-in functions. They can be forked or not, depending on + * context: within pipes, they fork. As simple commands, they do not. + * When used in non-forking context, they can change global variables + * in the parent shell process. If forked, of course they cannot. + * For example, 'unset foo | whatever' will parse and run, but foo will + * still be set at the end. */ +static const struct built_in_command bltins[] = { + {"bg", "Resume a job in the background", builtin_fg_bg}, + {"break", "Exit for, while or until loop", builtin_not_written}, + {"cd", "Change working directory", builtin_cd}, + {"continue", "Continue for, while or until loop", builtin_not_written}, + {"env", "Print all environment variables", builtin_env}, + {"eval", "Construct and run shell command", builtin_eval}, + {"exec", "Exec command, replacing this shell with the exec'd process", + builtin_exec}, + {"exit", "Exit from shell()", builtin_exit}, + {"export", "Set environment variable", builtin_export}, + {"fg", "Bring job into the foreground", builtin_fg_bg}, + {"jobs", "Lists the active jobs", builtin_jobs}, + {"pwd", "Print current directory", builtin_pwd}, + {"read", "Input environment variable", builtin_read}, + {"return", "Return from a function", builtin_not_written}, + {"set", "Set/unset shell local variables", builtin_set}, + {"shift", "Shift positional parameters", builtin_shift}, + {"trap", "Trap signals", builtin_not_written}, + {"ulimit","Controls resource limits", builtin_not_written}, + {"umask","Sets file creation mask", builtin_umask}, + {"unset", "Unset environment variable", builtin_unset}, + {".", "Source-in and run commands in a file", builtin_source}, + {"help", "List shell built-in commands", builtin_help}, + {NULL, NULL, NULL} +}; + +static const char *set_cwd(void) +{ + if(cwd==bb_msg_unknown) + cwd = NULL; /* xgetcwd(arg) called free(arg) */ + cwd = xgetcwd((char *)cwd); + if (!cwd) + cwd = bb_msg_unknown; + return cwd; +} + +/* built-in 'eval' handler */ +static int builtin_eval(struct child_prog *child) +{ + char *str = NULL; + int rcode = EXIT_SUCCESS; + + if (child->argv[1]) { + str = make_string(child->argv + 1); + parse_string_outer(str, FLAG_EXIT_FROM_LOOP | + FLAG_PARSE_SEMICOLON); + free(str); + rcode = last_return_code; + } + return rcode; +} + +/* built-in 'cd <path>' handler */ +static int builtin_cd(struct child_prog *child) +{ + char *newdir; + if (child->argv[1] == NULL) + newdir = getenv("HOME"); + else + newdir = child->argv[1]; + if (chdir(newdir)) { + printf("cd: %s: %s\n", newdir, strerror(errno)); + return EXIT_FAILURE; + } + set_cwd(); + return EXIT_SUCCESS; +} + +/* built-in 'env' handler */ +static int builtin_env(struct child_prog *dummy ATTRIBUTE_UNUSED) +{ + char **e = environ; + if (e == NULL) return EXIT_FAILURE; + for (; *e; e++) { + puts(*e); + } + return EXIT_SUCCESS; +} + +/* built-in 'exec' handler */ +static int builtin_exec(struct child_prog *child) +{ + if (child->argv[1] == NULL) + return EXIT_SUCCESS; /* Really? */ + child->argv++; + pseudo_exec(child); + /* never returns */ +} + +/* built-in 'exit' handler */ +static int builtin_exit(struct child_prog *child) +{ + if (child->argv[1] == NULL) + exit(last_return_code); + exit (atoi(child->argv[1])); +} + +/* built-in 'export VAR=value' handler */ +static int builtin_export(struct child_prog *child) +{ + int res = 0; + char *name = child->argv[1]; + + if (name == NULL) { + return builtin_env(child); + } + + name = strdup(name); + + if(name) { + char *value = strchr(name, '='); + + if (!value) { + char *tmp; + /* They are exporting something without an =VALUE */ + + value = get_local_var(name); + if (value) { + size_t ln = strlen(name); + + tmp = realloc(name, ln+strlen(value)+2); + if(tmp==NULL) + res = -1; + else { + sprintf(tmp+ln, "=%s", value); + name = tmp; + } + } else { + /* bash does not return an error when trying to export + * an undefined variable. Do likewise. */ + res = 1; + } + } + } + if (res<0) + bb_perror_msg("export"); + else if(res==0) + res = set_local_var(name, 1); + else + res = 0; + free(name); + return res; +} + +/* built-in 'fg' and 'bg' handler */ +static int builtin_fg_bg(struct child_prog *child) +{ + int i, jobnum; + struct pipe *pi=NULL; + + if (!interactive) + return EXIT_FAILURE; + /* If they gave us no args, assume they want the last backgrounded task */ + if (!child->argv[1]) { + for (pi = job_list; pi; pi = pi->next) { + if (pi->jobid == last_jobid) { + break; + } + } + if (!pi) { + bb_error_msg("%s: no current job", child->argv[0]); + return EXIT_FAILURE; + } + } else { + if (sscanf(child->argv[1], "%%%d", &jobnum) != 1) { + bb_error_msg("%s: bad argument '%s'", child->argv[0], child->argv[1]); + return EXIT_FAILURE; + } + for (pi = job_list; pi; pi = pi->next) { + if (pi->jobid == jobnum) { + break; + } + } + if (!pi) { + bb_error_msg("%s: %d: no such job", child->argv[0], jobnum); + return EXIT_FAILURE; + } + } + + if (*child->argv[0] == 'f') { + /* Put the job into the foreground. */ + tcsetpgrp(shell_terminal, pi->pgrp); + } + + /* Restart the processes in the job */ + for (i = 0; i < pi->num_progs; i++) + pi->progs[i].is_stopped = 0; + + if ( (i=kill(- pi->pgrp, SIGCONT)) < 0) { + if (i == ESRCH) { + remove_bg_job(pi); + } else { + bb_perror_msg("kill (SIGCONT)"); + } + } + + pi->stopped_progs = 0; + return EXIT_SUCCESS; +} + +/* built-in 'help' handler */ +static int builtin_help(struct child_prog *dummy ATTRIBUTE_UNUSED) +{ + const struct built_in_command *x; + + printf("\nBuilt-in commands:\n"); + printf("-------------------\n"); + for (x = bltins; x->cmd; x++) { + if (x->descr==NULL) + continue; + printf("%s\t%s\n", x->cmd, x->descr); + } + printf("\n\n"); + return EXIT_SUCCESS; +} + +/* built-in 'jobs' handler */ +static int builtin_jobs(struct child_prog *child ATTRIBUTE_UNUSED) +{ + struct pipe *job; + char *status_string; + + for (job = job_list; job; job = job->next) { + if (job->running_progs == job->stopped_progs) + status_string = "Stopped"; + else + status_string = "Running"; + + printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->text); + } + return EXIT_SUCCESS; +} + + +/* built-in 'pwd' handler */ +static int builtin_pwd(struct child_prog *dummy ATTRIBUTE_UNUSED) +{ + puts(set_cwd()); + return EXIT_SUCCESS; +} + +/* built-in 'read VAR' handler */ +static int builtin_read(struct child_prog *child) +{ + int res; + + if (child->argv[1]) { + char string[BUFSIZ]; + char *var = 0; + + string[0] = 0; /* In case stdin has only EOF */ + /* read string */ + fgets(string, sizeof(string), stdin); + chomp(string); + var = malloc(strlen(child->argv[1])+strlen(string)+2); + if(var) { + sprintf(var, "%s=%s", child->argv[1], string); + res = set_local_var(var, 0); + } else + res = -1; + if (res) + bb_perror_msg("read"); + free(var); /* So not move up to avoid breaking errno */ + return res; + } else { + do res=getchar(); while(res!='\n' && res!=EOF); + return 0; + } +} + +/* built-in 'set VAR=value' handler */ +static int builtin_set(struct child_prog *child) +{ + char *temp = child->argv[1]; + struct variables *e; + + if (temp == NULL) + for(e = top_vars; e; e=e->next) + printf("%s=%s\n", e->name, e->value); + else + set_local_var(temp, 0); + + return EXIT_SUCCESS; +} + + +/* Built-in 'shift' handler */ +static int builtin_shift(struct child_prog *child) +{ + int n=1; + if (child->argv[1]) { + n=atoi(child->argv[1]); + } + if (n>=0 && n<global_argc) { + /* XXX This probably breaks $0 */ + global_argc -= n; + global_argv += n; + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } +} + +/* Built-in '.' handler (read-in and execute commands from file) */ +static int builtin_source(struct child_prog *child) +{ + FILE *input; + int status; + + if (child->argv[1] == NULL) + return EXIT_FAILURE; + + /* XXX search through $PATH is missing */ + input = fopen(child->argv[1], "r"); + if (!input) { + bb_error_msg("cannot open '%s'", child->argv[1]); + return EXIT_FAILURE; + } + + /* Now run the file */ + /* XXX argv and argc are broken; need to save old global_argv + * (pointer only is OK!) on this stack frame, + * set global_argv=child->argv+1, recurse, and restore. */ + mark_open(fileno(input)); + status = parse_file_outer(input); + mark_closed(fileno(input)); + fclose(input); + return status; +} + +static int builtin_umask(struct child_prog *child) +{ + mode_t new_umask; + const char *arg = child->argv[1]; + char *end; + if (arg) { + new_umask=strtoul(arg, &end, 8); + if (*end!='\0' || end == arg) { + return EXIT_FAILURE; + } + } else { + printf("%.3o\n", (unsigned int) (new_umask=umask(0))); + } + umask(new_umask); + return EXIT_SUCCESS; +} + +/* built-in 'unset VAR' handler */ +static int builtin_unset(struct child_prog *child) +{ + /* bash returned already true */ + unset_local_var(child->argv[1]); + return EXIT_SUCCESS; +} + +static int builtin_not_written(struct child_prog *child) +{ + printf("builtin_%s not written\n",child->argv[0]); + return EXIT_FAILURE; +} + +static int b_check_space(o_string *o, int len) +{ + /* It would be easy to drop a more restrictive policy + * in here, such as setting a maximum string length */ + if (o->length + len > o->maxlen) { + char *old_data = o->data; + /* assert (data == NULL || o->maxlen != 0); */ + o->maxlen += max(2*len, B_CHUNK); + o->data = realloc(o->data, 1 + o->maxlen); + if (o->data == NULL) { + free(old_data); + } + } + return o->data == NULL; +} + +static int b_addchr(o_string *o, int ch) +{ + debug_printf("b_addchr: %c %d %p\n", ch, o->length, o); + if (b_check_space(o, 1)) return B_NOSPAC; + o->data[o->length] = ch; + o->length++; + o->data[o->length] = '\0'; + return 0; +} + +static void b_reset(o_string *o) +{ + o->length = 0; + o->nonnull = 0; + if (o->data != NULL) *o->data = '\0'; +} + +static void b_free(o_string *o) +{ + b_reset(o); + free(o->data); + o->data = NULL; + o->maxlen = 0; +} + +/* My analysis of quoting semantics tells me that state information + * is associated with a destination, not a source. + */ +static int b_addqchr(o_string *o, int ch, int quote) +{ + if (quote && strchr("*?[\\",ch)) { + int rc; + rc = b_addchr(o, '\\'); + if (rc) return rc; + } + return b_addchr(o, ch); +} + +/* belongs in utility.c */ +static char *simple_itoa(unsigned int i) +{ + /* 21 digits plus null terminator, good for 64-bit or smaller ints */ + static char local[22]; + char *p = &local[21]; + *p-- = '\0'; + do { + *p-- = '0' + i % 10; + i /= 10; + } while (i > 0); + return p + 1; +} + +static int b_adduint(o_string *o, unsigned int i) +{ + int r; + char *p = simple_itoa(i); + /* no escape checking necessary */ + do r=b_addchr(o, *p++); while (r==0 && *p); + return r; +} + +static int static_get(struct in_str *i) +{ + int ch=*i->p++; + if (ch=='\0') return EOF; + return ch; +} + +static int static_peek(struct in_str *i) +{ + return *i->p; +} + +static void cmdedit_set_initial_prompt(void) +{ +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT + PS1 = NULL; +#else + PS1 = getenv("PS1"); + if(PS1==0) + PS1 = "\\w \\$ "; +#endif +} + +static void setup_prompt_string(int promptmode, char **prompt_str) +{ + debug_printf("setup_prompt_string %d ",promptmode); +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT + /* Set up the prompt */ + if (promptmode == 1) { + free(PS1); + PS1=xmalloc(strlen(cwd)+4); + sprintf(PS1, "%s %s", cwd, ( geteuid() != 0 ) ? "$ ":"# "); + *prompt_str = PS1; + } else { + *prompt_str = PS2; + } +#else + *prompt_str = (promptmode==1)? PS1 : PS2; +#endif + debug_printf("result %s\n",*prompt_str); +} + +static void get_user_input(struct in_str *i) +{ + char *prompt_str; + static char the_command[BUFSIZ]; + + setup_prompt_string(i->promptmode, &prompt_str); +#ifdef CONFIG_FEATURE_COMMAND_EDITING + /* + ** enable command line editing only while a command line + ** is actually being read; otherwise, we'll end up bequeathing + ** atexit() handlers and other unwanted stuff to our + ** child processes (rob@sysgo.de) + */ + cmdedit_read_input(prompt_str, the_command); +#else + fputs(prompt_str, stdout); + fflush(stdout); + the_command[0]=fgetc(i->file); + the_command[1]='\0'; +#endif + fflush(stdout); + i->p = the_command; +} + +/* This is the magic location that prints prompts + * and gets data back from the user */ +static int file_get(struct in_str *i) +{ + int ch; + + ch = 0; + /* If there is data waiting, eat it up */ + if (i->p && *i->p) { + ch=*i->p++; + } else { + /* need to double check i->file because we might be doing something + * more complicated by now, like sourcing or substituting. */ + if (i->__promptme && interactive && i->file == stdin) { + while(! i->p || (interactive && strlen(i->p)==0) ) { + get_user_input(i); + } + i->promptmode=2; + i->__promptme = 0; + if (i->p && *i->p) { + ch=*i->p++; + } + } else { + ch = fgetc(i->file); + } + + debug_printf("b_getch: got a %d\n", ch); + } + if (ch == '\n') i->__promptme=1; + return ch; +} + +/* All the callers guarantee this routine will never be + * used right after a newline, so prompting is not needed. + */ +static int file_peek(struct in_str *i) +{ + if (i->p && *i->p) { + return *i->p; + } else { + i->peek_buf[0] = fgetc(i->file); + i->peek_buf[1] = '\0'; + i->p = i->peek_buf; + debug_printf("b_peek: got a %d\n", *i->p); + return *i->p; + } +} + +static void setup_file_in_str(struct in_str *i, FILE *f) +{ + i->peek = file_peek; + i->get = file_get; + i->__promptme=1; + i->promptmode=1; + i->file = f; + i->p = NULL; +} + +static void setup_string_in_str(struct in_str *i, const char *s) +{ + i->peek = static_peek; + i->get = static_get; + i->__promptme=1; + i->promptmode=1; + i->p = s; +} + +static void mark_open(int fd) +{ + struct close_me *new = xmalloc(sizeof(struct close_me)); + new->fd = fd; + new->next = close_me_head; + close_me_head = new; +} + +static void mark_closed(int fd) +{ + struct close_me *tmp; + if (close_me_head == NULL || close_me_head->fd != fd) + bb_error_msg_and_die("corrupt close_me"); + tmp = close_me_head; + close_me_head = close_me_head->next; + free(tmp); +} + +static void close_all(void) +{ + struct close_me *c; + for (c=close_me_head; c; c=c->next) { + close(c->fd); + } + close_me_head = NULL; +} + +/* squirrel != NULL means we squirrel away copies of stdin, stdout, + * and stderr if they are redirected. */ +static int setup_redirects(struct child_prog *prog, int squirrel[]) +{ + int openfd, mode; + struct redir_struct *redir; + + for (redir=prog->redirects; redir; redir=redir->next) { + if (redir->dup == -1 && redir->word.gl_pathv == NULL) { + /* something went wrong in the parse. Pretend it didn't happen */ + continue; + } + if (redir->dup == -1) { + mode=redir_table[redir->type].mode; + openfd = open(redir->word.gl_pathv[0], mode, 0666); + if (openfd < 0) { + /* this could get lost if stderr has been redirected, but + bash and ash both lose it as well (though zsh doesn't!) */ + bb_perror_msg("error opening %s", redir->word.gl_pathv[0]); + return 1; + } + } else { + openfd = redir->dup; + } + + if (openfd != redir->fd) { + if (squirrel && redir->fd < 3) { + squirrel[redir->fd] = dup(redir->fd); + } + if (openfd == -3) { + close(openfd); + } else { + dup2(openfd, redir->fd); + if (redir->dup == -1) + close (openfd); + } + } + } + return 0; +} + +static void restore_redirects(int squirrel[]) +{ + int i, fd; + for (i=0; i<3; i++) { + fd = squirrel[i]; + if (fd != -1) { + /* No error checking. I sure wouldn't know what + * to do with an error if I found one! */ + dup2(fd, i); + close(fd); + } + } +} + +/* never returns */ +/* XXX no exit() here. If you don't exec, use _exit instead. + * The at_exit handlers apparently confuse the calling process, + * in particular stdin handling. Not sure why? */ +static void pseudo_exec(struct child_prog *child) +{ + int i, rcode; + char *p; + const struct built_in_command *x; + if (child->argv) { + for (i=0; is_assignment(child->argv[i]); i++) { + debug_printf("pid %d environment modification: %s\n",getpid(),child->argv[i]); + p = insert_var_value(child->argv[i]); + putenv(strdup(p)); + if (p != child->argv[i]) free(p); + } + child->argv+=i; /* XXX this hack isn't so horrible, since we are about + to exit, and therefore don't need to keep data + structures consistent for free() use. */ + /* If a variable is assigned in a forest, and nobody listens, + * was it ever really set? + */ + if (child->argv[0] == NULL) { + _exit(EXIT_SUCCESS); + } + + /* + * Check if the command matches any of the builtins. + * Depending on context, this might be redundant. But it's + * easier to waste a few CPU cycles than it is to figure out + * if this is one of those cases. + */ + for (x = bltins; x->cmd; x++) { + if (strcmp(child->argv[0], x->cmd) == 0 ) { + debug_printf("builtin exec %s\n", child->argv[0]); + rcode = x->function(child); + fflush(stdout); + _exit(rcode); + } + } + + /* Check if the command matches any busybox internal commands + * ("applets") here. + * FIXME: This feature is not 100% safe, since + * BusyBox is not fully reentrant, so we have no guarantee the things + * from the .bss are still zeroed, or that things from .data are still + * at their defaults. We could exec ourself from /proc/self/exe, but I + * really dislike relying on /proc for things. We could exec ourself + * from global_argv[0], but if we are in a chroot, we may not be able + * to find ourself... */ +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + { + int argc_l; + char** argv_l=child->argv; + char *name = child->argv[0]; + + /* Count argc for use in a second... */ + for(argc_l=0;*argv_l!=NULL; argv_l++, argc_l++); + optind = 1; + debug_printf("running applet %s\n", name); + run_applet_by_name(name, argc_l, child->argv); + } +#endif + debug_printf("exec of %s\n",child->argv[0]); + execvp(child->argv[0],child->argv); + bb_perror_msg("cannot exec: %s",child->argv[0]); + _exit(1); + } else if (child->group) { + debug_printf("runtime nesting to group\n"); + interactive=0; /* crucial!!!! */ + rcode = run_list_real(child->group); + /* OK to leak memory by not calling free_pipe_list, + * since this process is about to exit */ + _exit(rcode); + } else { + /* Can happen. See what bash does with ">foo" by itself. */ + debug_printf("trying to pseudo_exec null command\n"); + _exit(EXIT_SUCCESS); + } +} + +static void insert_bg_job(struct pipe *pi) +{ + struct pipe *thejob; + + /* Linear search for the ID of the job to use */ + pi->jobid = 1; + for (thejob = job_list; thejob; thejob = thejob->next) + if (thejob->jobid >= pi->jobid) + pi->jobid = thejob->jobid + 1; + + /* add thejob to the list of running jobs */ + if (!job_list) { + thejob = job_list = xmalloc(sizeof(*thejob)); + } else { + for (thejob = job_list; thejob->next; thejob = thejob->next) /* nothing */; + thejob->next = xmalloc(sizeof(*thejob)); + thejob = thejob->next; + } + + /* physically copy the struct job */ + memcpy(thejob, pi, sizeof(struct pipe)); + thejob->next = NULL; + thejob->running_progs = thejob->num_progs; + thejob->stopped_progs = 0; + thejob->text = xmalloc(BUFSIZ); /* cmdedit buffer size */ + + //if (pi->progs[0] && pi->progs[0].argv && pi->progs[0].argv[0]) + { + char *bar=thejob->text; + char **foo=pi->progs[0].argv; + while(foo && *foo) { + bar += sprintf(bar, "%s ", *foo++); + } + } + + /* we don't wait for background thejobs to return -- append it + to the list of backgrounded thejobs and leave it alone */ + printf("[%d] %d\n", thejob->jobid, thejob->progs[0].pid); + last_bg_pid = thejob->progs[0].pid; + last_jobid = thejob->jobid; +} + +/* remove a backgrounded job */ +static void remove_bg_job(struct pipe *pi) +{ + struct pipe *prev_pipe; + + if (pi == job_list) { + job_list = pi->next; + } else { + prev_pipe = job_list; + while (prev_pipe->next != pi) + prev_pipe = prev_pipe->next; + prev_pipe->next = pi->next; + } + if (job_list) + last_jobid = job_list->jobid; + else + last_jobid = 0; + + pi->stopped_progs = 0; + free_pipe(pi, 0); + free(pi); +} + +/* Checks to see if any processes have exited -- if they + have, figure out why and see if a job has completed */ +static int checkjobs(struct pipe* fg_pipe) +{ + int attributes; + int status; + int prognum = 0; + struct pipe *pi; + pid_t childpid; + + attributes = WUNTRACED; + if (fg_pipe==NULL) { + attributes |= WNOHANG; + } + + while ((childpid = waitpid(-1, &status, attributes)) > 0) { + if (fg_pipe) { + int i, rcode = 0; + for (i=0; i < fg_pipe->num_progs; i++) { + if (fg_pipe->progs[i].pid == childpid) { + if (i==fg_pipe->num_progs-1) + rcode=WEXITSTATUS(status); + (fg_pipe->num_progs)--; + return rcode; + } + } + } + + for (pi = job_list; pi; pi = pi->next) { + prognum = 0; + while (prognum < pi->num_progs && pi->progs[prognum].pid != childpid) { + prognum++; + } + if (prognum < pi->num_progs) + break; + } + + if(pi==NULL) { + debug_printf("checkjobs: pid %d was not in our list!\n", childpid); + continue; + } + + if (WIFEXITED(status) || WIFSIGNALED(status)) { + /* child exited */ + pi->running_progs--; + pi->progs[prognum].pid = 0; + + if (!pi->running_progs) { + printf(JOB_STATUS_FORMAT, pi->jobid, "Done", pi->text); + remove_bg_job(pi); + } + } else { + /* child stopped */ + pi->stopped_progs++; + pi->progs[prognum].is_stopped = 1; + } + } + + if (childpid == -1 && errno != ECHILD) + bb_perror_msg("waitpid"); + + /* move the shell to the foreground */ + //if (interactive && tcsetpgrp(shell_terminal, getpgid(0))) + // bb_perror_msg("tcsetpgrp-2"); + return -1; +} + +/* run_pipe_real() starts all the jobs, but doesn't wait for anything + * to finish. See checkjobs(). + * + * return code is normally -1, when the caller has to wait for children + * to finish to determine the exit status of the pipe. If the pipe + * is a simple builtin command, however, the action is done by the + * time run_pipe_real returns, and the exit code is provided as the + * return value. + * + * The input of the pipe is always stdin, the output is always + * stdout. The outpipe[] mechanism in BusyBox-0.48 lash is bogus, + * because it tries to avoid running the command substitution in + * subshell, when that is in fact necessary. The subshell process + * now has its stdout directed to the input of the appropriate pipe, + * so this routine is noticeably simpler. + */ +static int run_pipe_real(struct pipe *pi) +{ + int i; + int nextin, nextout; + int pipefds[2]; /* pipefds[0] is for reading */ + struct child_prog *child; + const struct built_in_command *x; + char *p; + + nextin = 0; + pi->pgrp = -1; + + /* Check if this is a simple builtin (not part of a pipe). + * Builtins within pipes have to fork anyway, and are handled in + * pseudo_exec. "echo foo | read bar" doesn't work on bash, either. + */ + child = & (pi->progs[0]); + if (pi->num_progs == 1 && child->group && child->subshell == 0) { + int squirrel[] = {-1, -1, -1}; + int rcode; + debug_printf("non-subshell grouping\n"); + setup_redirects(child, squirrel); + /* XXX could we merge code with following builtin case, + * by creating a pseudo builtin that calls run_list_real? */ + rcode = run_list_real(child->group); + restore_redirects(squirrel); + return rcode; + } else if (pi->num_progs == 1 && pi->progs[0].argv != NULL) { + for (i=0; is_assignment(child->argv[i]); i++) { /* nothing */ } + if (i!=0 && child->argv[i]==NULL) { + /* assignments, but no command: set the local environment */ + for (i=0; child->argv[i]!=NULL; i++) { + + /* Ok, this case is tricky. We have to decide if this is a + * local variable, or an already exported variable. If it is + * already exported, we have to export the new value. If it is + * not exported, we need only set this as a local variable. + * This junk is all to decide whether or not to export this + * variable. */ + int export_me=0; + char *name, *value; + name = xstrdup(child->argv[i]); + debug_printf("Local environment set: %s\n", name); + value = strchr(name, '='); + if (value) + *value=0; + if ( get_local_var(name)) { + export_me=1; + } + free(name); + p = insert_var_value(child->argv[i]); + set_local_var(p, export_me); + if (p != child->argv[i]) free(p); + } + return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ + } + for (i = 0; is_assignment(child->argv[i]); i++) { + p = insert_var_value(child->argv[i]); + putenv(strdup(p)); + if (p != child->argv[i]) { + child->sp--; + free(p); + } + } + if (child->sp) { + char * str = NULL; + + str = make_string((child->argv + i)); + parse_string_outer(str, FLAG_EXIT_FROM_LOOP | FLAG_REPARSING); + free(str); + return last_return_code; + } + for (x = bltins; x->cmd; x++) { + if (strcmp(child->argv[i], x->cmd) == 0 ) { + int squirrel[] = {-1, -1, -1}; + int rcode; + if (x->function == builtin_exec && child->argv[i+1]==NULL) { + debug_printf("magic exec\n"); + setup_redirects(child,NULL); + return EXIT_SUCCESS; + } + debug_printf("builtin inline %s\n", child->argv[0]); + /* XXX setup_redirects acts on file descriptors, not FILEs. + * This is perfect for work that comes after exec(). + * Is it really safe for inline use? Experimentally, + * things seem to work with glibc. */ + setup_redirects(child, squirrel); + child->argv+=i; /* XXX horrible hack */ + rcode = x->function(child); + child->argv-=i; /* XXX restore hack so free() can work right */ + restore_redirects(squirrel); + return rcode; + } + } + } + + for (i = 0; i < pi->num_progs; i++) { + child = & (pi->progs[i]); + + /* pipes are inserted between pairs of commands */ + if ((i + 1) < pi->num_progs) { + if (pipe(pipefds)<0) bb_perror_msg_and_die("pipe"); + nextout = pipefds[1]; + } else { + nextout=1; + pipefds[0] = -1; + } + + /* XXX test for failed fork()? */ +#if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__) + if (!(child->pid = fork())) +#else + if (!(child->pid = vfork())) +#endif + { + /* Set the handling for job control signals back to the default. */ + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + signal(SIGTTIN, SIG_DFL); + signal(SIGTTOU, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + close_all(); + + if (nextin != 0) { + dup2(nextin, 0); + close(nextin); + } + if (nextout != 1) { + dup2(nextout, 1); + close(nextout); + } + if (pipefds[0]!=-1) { + close(pipefds[0]); /* opposite end of our output pipe */ + } + + /* Like bash, explicit redirects override pipes, + * and the pipe fd is available for dup'ing. */ + setup_redirects(child,NULL); + + if (interactive && pi->followup!=PIPE_BG) { + /* If we (the child) win the race, put ourselves in the process + * group whose leader is the first process in this pipe. */ + if (pi->pgrp < 0) { + pi->pgrp = getpid(); + } + if (setpgid(0, pi->pgrp) == 0) { + tcsetpgrp(2, pi->pgrp); + } + } + + pseudo_exec(child); + } + + + /* put our child in the process group whose leader is the + first process in this pipe */ + if (pi->pgrp < 0) { + pi->pgrp = child->pid; + } + /* Don't check for errors. The child may be dead already, + * in which case setpgid returns error code EACCES. */ + setpgid(child->pid, pi->pgrp); + + if (nextin != 0) + close(nextin); + if (nextout != 1) + close(nextout); + + /* If there isn't another process, nextin is garbage + but it doesn't matter */ + nextin = pipefds[0]; + } + return -1; +} + +static int run_list_real(struct pipe *pi) +{ + char *save_name = NULL; + char **list = NULL; + char **save_list = NULL; + struct pipe *rpipe; + int flag_rep = 0; + int save_num_progs; + int rcode=0, flag_skip=1; + int flag_restore = 0; + int if_code=0, next_if_code=0; /* need double-buffer to handle elif */ + reserved_style rmode, skip_more_in_this_rmode=RES_XXXX; + /* check syntax for "for" */ + for (rpipe = pi; rpipe; rpipe = rpipe->next) { + if ((rpipe->r_mode == RES_IN || + rpipe->r_mode == RES_FOR) && + (rpipe->next == NULL)) { + syntax(); + return 1; + } + if ((rpipe->r_mode == RES_IN && + (rpipe->next->r_mode == RES_IN && + rpipe->next->progs->argv != NULL))|| + (rpipe->r_mode == RES_FOR && + rpipe->next->r_mode != RES_IN)) { + syntax(); + return 1; + } + } + for (; pi; pi = (flag_restore != 0) ? rpipe : pi->next) { + if (pi->r_mode == RES_WHILE || pi->r_mode == RES_UNTIL || + pi->r_mode == RES_FOR) { + flag_restore = 0; + if (!rpipe) { + flag_rep = 0; + rpipe = pi; + } + } + rmode = pi->r_mode; + debug_printf("rmode=%d if_code=%d next_if_code=%d skip_more=%d\n", rmode, if_code, next_if_code, skip_more_in_this_rmode); + if (rmode == skip_more_in_this_rmode && flag_skip) { + if (pi->followup == PIPE_SEQ) flag_skip=0; + continue; + } + flag_skip = 1; + skip_more_in_this_rmode = RES_XXXX; + if (rmode == RES_THEN || rmode == RES_ELSE) if_code = next_if_code; + if (rmode == RES_THEN && if_code) continue; + if (rmode == RES_ELSE && !if_code) continue; + if (rmode == RES_ELIF && !if_code) break; + if (rmode == RES_FOR && pi->num_progs) { + if (!list) { + /* if no variable values after "in" we skip "for" */ + if (!pi->next->progs->argv) continue; + /* create list of variable values */ + list = make_list_in(pi->next->progs->argv, + pi->progs->argv[0]); + save_list = list; + save_name = pi->progs->argv[0]; + pi->progs->argv[0] = NULL; + flag_rep = 1; + } + if (!(*list)) { + free(pi->progs->argv[0]); + free(save_list); + list = NULL; + flag_rep = 0; + pi->progs->argv[0] = save_name; + pi->progs->glob_result.gl_pathv[0] = + pi->progs->argv[0]; + continue; + } else { + /* insert new value from list for variable */ + if (pi->progs->argv[0]) + free(pi->progs->argv[0]); + pi->progs->argv[0] = *list++; + pi->progs->glob_result.gl_pathv[0] = + pi->progs->argv[0]; + } + } + if (rmode == RES_IN) continue; + if (rmode == RES_DO) { + if (!flag_rep) continue; + } + if ((rmode == RES_DONE)) { + if (flag_rep) { + flag_restore = 1; + } else { + rpipe = NULL; + } + } + if (pi->num_progs == 0) continue; + save_num_progs = pi->num_progs; /* save number of programs */ + rcode = run_pipe_real(pi); + debug_printf("run_pipe_real returned %d\n",rcode); + if (rcode!=-1) { + /* We only ran a builtin: rcode was set by the return value + * of run_pipe_real(), and we don't need to wait for anything. */ + } else if (pi->followup==PIPE_BG) { + /* XXX check bash's behavior with nontrivial pipes */ + /* XXX compute jobid */ + /* XXX what does bash do with attempts to background builtins? */ + insert_bg_job(pi); + rcode = EXIT_SUCCESS; + } else { + if (interactive) { + /* move the new process group into the foreground */ + if (tcsetpgrp(shell_terminal, pi->pgrp) && errno != ENOTTY) + bb_perror_msg("tcsetpgrp-3"); + rcode = checkjobs(pi); + /* move the shell to the foreground */ + if (tcsetpgrp(shell_terminal, getpgid(0)) && errno != ENOTTY) + bb_perror_msg("tcsetpgrp-4"); + } else { + rcode = checkjobs(pi); + } + debug_printf("checkjobs returned %d\n",rcode); + } + last_return_code=rcode; + pi->num_progs = save_num_progs; /* restore number of programs */ + if ( rmode == RES_IF || rmode == RES_ELIF ) + next_if_code=rcode; /* can be overwritten a number of times */ + if (rmode == RES_WHILE) + flag_rep = !last_return_code; + if (rmode == RES_UNTIL) + flag_rep = last_return_code; + if ( (rcode==EXIT_SUCCESS && pi->followup==PIPE_OR) || + (rcode!=EXIT_SUCCESS && pi->followup==PIPE_AND) ) + skip_more_in_this_rmode=rmode; + checkjobs(NULL); + } + return rcode; +} + +/* return code is the exit status of the pipe */ +static int free_pipe(struct pipe *pi, int indent) +{ + char **p; + struct child_prog *child; + struct redir_struct *r, *rnext; + int a, i, ret_code=0; + + if (pi->stopped_progs > 0) + return ret_code; + final_printf("%s run pipe: (pid %d)\n",indenter(indent),getpid()); + for (i=0; i<pi->num_progs; i++) { + child = &pi->progs[i]; + final_printf("%s command %d:\n",indenter(indent),i); + if (child->argv) { + for (a=0,p=child->argv; *p; a++,p++) { + final_printf("%s argv[%d] = %s\n",indenter(indent),a,*p); + } + globfree(&child->glob_result); + child->argv=NULL; + } else if (child->group) { + final_printf("%s begin group (subshell:%d)\n",indenter(indent), child->subshell); + ret_code = free_pipe_list(child->group,indent+3); + final_printf("%s end group\n",indenter(indent)); + } else { + final_printf("%s (nil)\n",indenter(indent)); + } + for (r=child->redirects; r; r=rnext) { + final_printf("%s redirect %d%s", indenter(indent), r->fd, redir_table[r->type].descrip); + if (r->dup == -1) { + /* guard against the case >$FOO, where foo is unset or blank */ + if (r->word.gl_pathv) { + final_printf(" %s\n", *r->word.gl_pathv); + globfree(&r->word); + } + } else { + final_printf("&%d\n", r->dup); + } + rnext=r->next; + free(r); + } + child->redirects=NULL; + } + free(pi->progs); /* children are an array, they get freed all at once */ + pi->progs=NULL; + return ret_code; +} + +static int free_pipe_list(struct pipe *head, int indent) +{ + int rcode=0; /* if list has no members */ + struct pipe *pi, *next; + for (pi=head; pi; pi=next) { + final_printf("%s pipe reserved mode %d\n", indenter(indent), pi->r_mode); + rcode = free_pipe(pi, indent); + final_printf("%s pipe followup code %d\n", indenter(indent), pi->followup); + next=pi->next; + pi->next=NULL; + free(pi); + } + return rcode; +} + +/* Select which version we will use */ +static int run_list(struct pipe *pi) +{ + int rcode=0; + if (fake_mode==0) { + rcode = run_list_real(pi); + } + /* free_pipe_list has the side effect of clearing memory + * In the long run that function can be merged with run_list_real, + * but doing that now would hobble the debugging effort. */ + free_pipe_list(pi,0); + return rcode; +} + +/* The API for glob is arguably broken. This routine pushes a non-matching + * string into the output structure, removing non-backslashed backslashes. + * If someone can prove me wrong, by performing this function within the + * original glob(3) api, feel free to rewrite this routine into oblivion. + * Return code (0 vs. GLOB_NOSPACE) matches glob(3). + * XXX broken if the last character is '\\', check that before calling. + */ +static int globhack(const char *src, int flags, glob_t *pglob) +{ + int cnt=0, pathc; + const char *s; + char *dest; + for (cnt=1, s=src; s && *s; s++) { + if (*s == '\\') s++; + cnt++; + } + dest = malloc(cnt); + if (!dest) return GLOB_NOSPACE; + if (!(flags & GLOB_APPEND)) { + pglob->gl_pathv=NULL; + pglob->gl_pathc=0; + pglob->gl_offs=0; + pglob->gl_offs=0; + } + pathc = ++pglob->gl_pathc; + pglob->gl_pathv = realloc(pglob->gl_pathv, (pathc+1)*sizeof(*pglob->gl_pathv)); + if (pglob->gl_pathv == NULL) return GLOB_NOSPACE; + pglob->gl_pathv[pathc-1]=dest; + pglob->gl_pathv[pathc]=NULL; + for (s=src; s && *s; s++, dest++) { + if (*s == '\\') s++; + *dest = *s; + } + *dest='\0'; + return 0; +} + +/* XXX broken if the last character is '\\', check that before calling */ +static int glob_needed(const char *s) +{ + for (; *s; s++) { + if (*s == '\\') s++; + if (strchr("*[?",*s)) return 1; + } + return 0; +} + +static int xglob(o_string *dest, int flags, glob_t *pglob) +{ + int gr; + + /* short-circuit for null word */ + /* we can code this better when the debug_printf's are gone */ + if (dest->length == 0) { + if (dest->nonnull) { + /* bash man page calls this an "explicit" null */ + gr = globhack(dest->data, flags, pglob); + debug_printf("globhack returned %d\n",gr); + } else { + return 0; + } + } else if (glob_needed(dest->data)) { + gr = glob(dest->data, flags, NULL, pglob); + debug_printf("glob returned %d\n",gr); + if (gr == GLOB_NOMATCH) { + /* quote removal, or more accurately, backslash removal */ + gr = globhack(dest->data, flags, pglob); + debug_printf("globhack returned %d\n",gr); + } + } else { + gr = globhack(dest->data, flags, pglob); + debug_printf("globhack returned %d\n",gr); + } + if (gr == GLOB_NOSPACE) + bb_error_msg_and_die("out of memory during glob"); + if (gr != 0) { /* GLOB_ABORTED ? */ + bb_error_msg("glob(3) error %d",gr); + } + /* globprint(glob_target); */ + return gr; +} + +/* This is used to get/check local shell variables */ +static char *get_local_var(const char *s) +{ + struct variables *cur; + + if (!s) + return NULL; + for (cur = top_vars; cur; cur=cur->next) + if(strcmp(cur->name, s)==0) + return cur->value; + return NULL; +} + +/* This is used to set local shell variables + flg_export==0 if only local (not exporting) variable + flg_export==1 if "new" exporting environ + flg_export>1 if current startup environ (not call putenv()) */ +static int set_local_var(const char *s, int flg_export) +{ + char *name, *value; + int result=0; + struct variables *cur; + + name=strdup(s); + + /* Assume when we enter this function that we are already in + * NAME=VALUE format. So the first order of business is to + * split 's' on the '=' into 'name' and 'value' */ + value = strchr(name, '='); + if (value==0 && ++value==0) { + free(name); + return -1; + } + *value++ = 0; + + for(cur = top_vars; cur; cur = cur->next) { + if(strcmp(cur->name, name)==0) + break; + } + + if(cur) { + if(strcmp(cur->value, value)==0) { + if(flg_export>0 && cur->flg_export==0) + cur->flg_export=flg_export; + else + result++; + } else { + if(cur->flg_read_only) { + bb_error_msg("%s: readonly variable", name); + result = -1; + } else { + if(flg_export>0 || cur->flg_export>1) + cur->flg_export=1; + free(cur->value); + + cur->value = strdup(value); + } + } + } else { + cur = malloc(sizeof(struct variables)); + if(!cur) { + result = -1; + } else { + cur->name = strdup(name); + if(cur->name == 0) { + free(cur); + result = -1; + } else { + struct variables *bottom = top_vars; + cur->value = strdup(value); + cur->next = 0; + cur->flg_export = flg_export; + cur->flg_read_only = 0; + while(bottom->next) bottom=bottom->next; + bottom->next = cur; + } + } + } + + if(result==0 && cur->flg_export==1) { + *(value-1) = '='; + result = putenv(name); + } else { + free(name); + if(result>0) /* equivalent to previous set */ + result = 0; + } + return result; +} + +static void unset_local_var(const char *name) +{ + struct variables *cur; + + if (name) { + for (cur = top_vars; cur; cur=cur->next) { + if(strcmp(cur->name, name)==0) + break; + } + if(cur!=0) { + struct variables *next = top_vars; + if(cur->flg_read_only) { + bb_error_msg("%s: readonly variable", name); + return; + } else { + if(cur->flg_export) + unsetenv(cur->name); + free(cur->name); + free(cur->value); + while (next->next != cur) + next = next->next; + next->next = cur->next; + } + free(cur); + } + } +} + +static int is_assignment(const char *s) +{ + if (s==NULL || !isalpha(*s)) return 0; + ++s; + while(isalnum(*s) || *s=='_') ++s; + return *s=='='; +} + +/* the src parameter allows us to peek forward to a possible &n syntax + * for file descriptor duplication, e.g., "2>&1". + * Return code is 0 normally, 1 if a syntax error is detected in src. + * Resource errors (in xmalloc) cause the process to exit */ +static int setup_redirect(struct p_context *ctx, int fd, redir_type style, + struct in_str *input) +{ + struct child_prog *child=ctx->child; + struct redir_struct *redir = child->redirects; + struct redir_struct *last_redir=NULL; + + /* Create a new redir_struct and drop it onto the end of the linked list */ + while(redir) { + last_redir=redir; + redir=redir->next; + } + redir = xmalloc(sizeof(struct redir_struct)); + redir->next=NULL; + redir->word.gl_pathv=NULL; + if (last_redir) { + last_redir->next=redir; + } else { + child->redirects=redir; + } + + redir->type=style; + redir->fd= (fd==-1) ? redir_table[style].default_fd : fd ; + + debug_printf("Redirect type %d%s\n", redir->fd, redir_table[style].descrip); + + /* Check for a '2>&1' type redirect */ + redir->dup = redirect_dup_num(input); + if (redir->dup == -2) return 1; /* syntax error */ + if (redir->dup != -1) { + /* Erik had a check here that the file descriptor in question + * is legit; I postpone that to "run time" + * A "-" representation of "close me" shows up as a -3 here */ + debug_printf("Duplicating redirect '%d>&%d'\n", redir->fd, redir->dup); + } else { + /* We do _not_ try to open the file that src points to, + * since we need to return and let src be expanded first. + * Set ctx->pending_redirect, so we know what to do at the + * end of the next parsed word. + */ + ctx->pending_redirect = redir; + } + return 0; +} + +static struct pipe *new_pipe(void) { + struct pipe *pi; + pi = xmalloc(sizeof(struct pipe)); + pi->num_progs = 0; + pi->progs = NULL; + pi->next = NULL; + pi->followup = 0; /* invalid */ + pi->r_mode = RES_NONE; + return pi; +} + +static void initialize_context(struct p_context *ctx) +{ + ctx->pipe=NULL; + ctx->pending_redirect=NULL; + ctx->child=NULL; + ctx->list_head=new_pipe(); + ctx->pipe=ctx->list_head; + ctx->w=RES_NONE; + ctx->stack=NULL; + ctx->old_flag=0; + done_command(ctx); /* creates the memory for working child */ +} + +/* normal return is 0 + * if a reserved word is found, and processed, return 1 + * should handle if, then, elif, else, fi, for, while, until, do, done. + * case, function, and select are obnoxious, save those for later. + */ +static int reserved_word(o_string *dest, struct p_context *ctx) +{ + struct reserved_combo { + char *literal; + int code; + long flag; + }; + /* Mostly a list of accepted follow-up reserved words. + * FLAG_END means we are done with the sequence, and are ready + * to turn the compound list into a command. + * FLAG_START means the word must start a new compound list. + */ + static struct reserved_combo reserved_list[] = { + { "if", RES_IF, FLAG_THEN | FLAG_START }, + { "then", RES_THEN, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, + { "elif", RES_ELIF, FLAG_THEN }, + { "else", RES_ELSE, FLAG_FI }, + { "fi", RES_FI, FLAG_END }, + { "for", RES_FOR, FLAG_IN | FLAG_START }, + { "while", RES_WHILE, FLAG_DO | FLAG_START }, + { "until", RES_UNTIL, FLAG_DO | FLAG_START }, + { "in", RES_IN, FLAG_DO }, + { "do", RES_DO, FLAG_DONE }, + { "done", RES_DONE, FLAG_END } + }; + struct reserved_combo *r; + for (r=reserved_list; +#define NRES sizeof(reserved_list)/sizeof(struct reserved_combo) + r<reserved_list+NRES; r++) { + if (strcmp(dest->data, r->literal) == 0) { + debug_printf("found reserved word %s, code %d\n",r->literal,r->code); + if (r->flag & FLAG_START) { + struct p_context *new = xmalloc(sizeof(struct p_context)); + debug_printf("push stack\n"); + if (ctx->w == RES_IN || ctx->w == RES_FOR) { + syntax(); + free(new); + ctx->w = RES_SNTX; + b_reset(dest); + return 1; + } + *new = *ctx; /* physical copy */ + initialize_context(ctx); + ctx->stack=new; + } else if ( ctx->w == RES_NONE || ! (ctx->old_flag & (1<<r->code))) { + syntax(); + ctx->w = RES_SNTX; + b_reset(dest); + return 1; + } + ctx->w=r->code; + ctx->old_flag = r->flag; + if (ctx->old_flag & FLAG_END) { + struct p_context *old; + debug_printf("pop stack\n"); + done_pipe(ctx,PIPE_SEQ); + old = ctx->stack; + old->child->group = ctx->list_head; + old->child->subshell = 0; + *ctx = *old; /* physical copy */ + free(old); + } + b_reset (dest); + return 1; + } + } + return 0; +} + +/* normal return is 0. + * Syntax or xglob errors return 1. */ +static int done_word(o_string *dest, struct p_context *ctx) +{ + struct child_prog *child=ctx->child; + glob_t *glob_target; + int gr, flags = 0; + + debug_printf("done_word: %s %p\n", dest->data, child); + if (dest->length == 0 && !dest->nonnull) { + debug_printf(" true null, ignored\n"); + return 0; + } + if (ctx->pending_redirect) { + glob_target = &ctx->pending_redirect->word; + } else { + if (child->group) { + syntax(); + return 1; /* syntax error, groups and arglists don't mix */ + } + if (!child->argv && (ctx->type & FLAG_PARSE_SEMICOLON)) { + debug_printf("checking %s for reserved-ness\n",dest->data); + if (reserved_word(dest,ctx)) return ctx->w==RES_SNTX; + } + glob_target = &child->glob_result; + if (child->argv) flags |= GLOB_APPEND; + } + gr = xglob(dest, flags, glob_target); + if (gr != 0) return 1; + + b_reset(dest); + if (ctx->pending_redirect) { + ctx->pending_redirect=NULL; + if (glob_target->gl_pathc != 1) { + bb_error_msg("ambiguous redirect"); + return 1; + } + } else { + child->argv = glob_target->gl_pathv; + } + if (ctx->w == RES_FOR) { + done_word(dest,ctx); + done_pipe(ctx,PIPE_SEQ); + } + return 0; +} + +/* The only possible error here is out of memory, in which case + * xmalloc exits. */ +static int done_command(struct p_context *ctx) +{ + /* The child is really already in the pipe structure, so + * advance the pipe counter and make a new, null child. + * Only real trickiness here is that the uncommitted + * child structure, to which ctx->child points, is not + * counted in pi->num_progs. */ + struct pipe *pi=ctx->pipe; + struct child_prog *prog=ctx->child; + + if (prog && prog->group == NULL + && prog->argv == NULL + && prog->redirects == NULL) { + debug_printf("done_command: skipping null command\n"); + return 0; + } else if (prog) { + pi->num_progs++; + debug_printf("done_command: num_progs incremented to %d\n",pi->num_progs); + } else { + debug_printf("done_command: initializing\n"); + } + pi->progs = xrealloc(pi->progs, sizeof(*pi->progs) * (pi->num_progs+1)); + + prog = pi->progs + pi->num_progs; + prog->redirects = NULL; + prog->argv = NULL; + prog->is_stopped = 0; + prog->group = NULL; + prog->glob_result.gl_pathv = NULL; + prog->family = pi; + prog->sp = 0; + ctx->child = prog; + prog->type = ctx->type; + + /* but ctx->pipe and ctx->list_head remain unchanged */ + return 0; +} + +static int done_pipe(struct p_context *ctx, pipe_style type) +{ + struct pipe *new_p; + done_command(ctx); /* implicit closure of previous command */ + debug_printf("done_pipe, type %d\n", type); + ctx->pipe->followup = type; + ctx->pipe->r_mode = ctx->w; + new_p=new_pipe(); + ctx->pipe->next = new_p; + ctx->pipe = new_p; + ctx->child = NULL; + done_command(ctx); /* set up new pipe to accept commands */ + return 0; +} + +/* peek ahead in the in_str to find out if we have a "&n" construct, + * as in "2>&1", that represents duplicating a file descriptor. + * returns either -2 (syntax error), -1 (no &), or the number found. + */ +static int redirect_dup_num(struct in_str *input) +{ + int ch, d=0, ok=0; + ch = b_peek(input); + if (ch != '&') return -1; + + b_getch(input); /* get the & */ + ch=b_peek(input); + if (ch == '-') { + b_getch(input); + return -3; /* "-" represents "close me" */ + } + while (isdigit(ch)) { + d = d*10+(ch-'0'); + ok=1; + b_getch(input); + ch = b_peek(input); + } + if (ok) return d; + + bb_error_msg("ambiguous redirect"); + return -2; +} + +/* If a redirect is immediately preceded by a number, that number is + * supposed to tell which file descriptor to redirect. This routine + * looks for such preceding numbers. In an ideal world this routine + * needs to handle all the following classes of redirects... + * echo 2>foo # redirects fd 2 to file "foo", nothing passed to echo + * echo 49>foo # redirects fd 49 to file "foo", nothing passed to echo + * echo -2>foo # redirects fd 1 to file "foo", "-2" passed to echo + * echo 49x>foo # redirects fd 1 to file "foo", "49x" passed to echo + * A -1 output from this program means no valid number was found, so the + * caller should use the appropriate default for this redirection. + */ +static int redirect_opt_num(o_string *o) +{ + int num; + + if (o->length==0) return -1; + for(num=0; num<o->length; num++) { + if (!isdigit(*(o->data+num))) { + return -1; + } + } + /* reuse num (and save an int) */ + num=atoi(o->data); + b_reset(o); + return num; +} + +static FILE *generate_stream_from_list(struct pipe *head) +{ + FILE *pf; + int pid, channel[2]; + if (pipe(channel)<0) bb_perror_msg_and_die("pipe"); +#if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__) + pid=fork(); +#else + pid=vfork(); +#endif + if (pid<0) { + bb_perror_msg_and_die("fork"); + } else if (pid==0) { + close(channel[0]); + if (channel[1] != 1) { + dup2(channel[1],1); + close(channel[1]); + } + _exit(run_list_real(head)); /* leaks memory */ + } + debug_printf("forked child %d\n",pid); + close(channel[1]); + pf = fdopen(channel[0],"r"); + debug_printf("pipe on FILE *%p\n",pf); + return pf; +} + +/* this version hacked for testing purposes */ +/* return code is exit status of the process that is run. */ +static int process_command_subs(o_string *dest, struct p_context *ctx, struct in_str *input, int subst_end) +{ + int retcode; + o_string result=NULL_O_STRING; + struct p_context inner; + FILE *p; + struct in_str pipe_str; + initialize_context(&inner); + + /* recursion to generate command */ + retcode = parse_stream(&result, &inner, input, subst_end); + if (retcode != 0) return retcode; /* syntax error or EOF */ + done_word(&result, &inner); + done_pipe(&inner, PIPE_SEQ); + b_free(&result); + + p=generate_stream_from_list(inner.list_head); + if (p==NULL) return 1; + mark_open(fileno(p)); + setup_file_in_str(&pipe_str, p); + + /* now send results of command back into original context */ + retcode = parse_stream(dest, ctx, &pipe_str, '\0'); + /* XXX In case of a syntax error, should we try to kill the child? + * That would be tough to do right, so just read until EOF. */ + if (retcode == 1) { + while (b_getch(&pipe_str)!=EOF) { /* discard */ }; + } + + debug_printf("done reading from pipe, pclose()ing\n"); + /* This is the step that wait()s for the child. Should be pretty + * safe, since we just read an EOF from its stdout. We could try + * to better, by using wait(), and keeping track of background jobs + * at the same time. That would be a lot of work, and contrary + * to the KISS philosophy of this program. */ + mark_closed(fileno(p)); + retcode=pclose(p); + free_pipe_list(inner.list_head,0); + debug_printf("pclosed, retcode=%d\n",retcode); + /* XXX this process fails to trim a single trailing newline */ + return retcode; +} + +static int parse_group(o_string *dest, struct p_context *ctx, + struct in_str *input, int ch) +{ + int rcode, endch=0; + struct p_context sub; + struct child_prog *child = ctx->child; + if (child->argv) { + syntax(); + return 1; /* syntax error, groups and arglists don't mix */ + } + initialize_context(&sub); + switch(ch) { + case '(': endch=')'; child->subshell=1; break; + case '{': endch='}'; break; + default: syntax(); /* really logic error */ + } + rcode=parse_stream(dest,&sub,input,endch); + done_word(dest,&sub); /* finish off the final word in the subcontext */ + done_pipe(&sub, PIPE_SEQ); /* and the final command there, too */ + child->group = sub.list_head; + return rcode; + /* child remains "open", available for possible redirects */ +} + +/* basically useful version until someone wants to get fancier, + * see the bash man page under "Parameter Expansion" */ +static char *lookup_param(char *src) +{ + char *p=NULL; + if (src) { + p = getenv(src); + if (!p) + p = get_local_var(src); + } + return p; +} + +/* return code: 0 for OK, 1 for syntax error */ +static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input) +{ + int i, advance=0; + char sep[]=" "; + int ch = input->peek(input); /* first character after the $ */ + debug_printf("handle_dollar: ch=%c\n",ch); + if (isalpha(ch)) { + b_addchr(dest, SPECIAL_VAR_SYMBOL); + ctx->child->sp++; + while(ch=b_peek(input),isalnum(ch) || ch=='_') { + b_getch(input); + b_addchr(dest,ch); + } + b_addchr(dest, SPECIAL_VAR_SYMBOL); + } else if (isdigit(ch)) { + i = ch-'0'; /* XXX is $0 special? */ + if (i<global_argc) { + parse_string(dest, ctx, global_argv[i]); /* recursion */ + } + advance = 1; + } else switch (ch) { + case '$': + b_adduint(dest,getpid()); + advance = 1; + break; + case '!': + if (last_bg_pid > 0) b_adduint(dest, last_bg_pid); + advance = 1; + break; + case '?': + b_adduint(dest,last_return_code); + advance = 1; + break; + case '#': + b_adduint(dest,global_argc ? global_argc-1 : 0); + advance = 1; + break; + case '{': + b_addchr(dest, SPECIAL_VAR_SYMBOL); + ctx->child->sp++; + b_getch(input); + /* XXX maybe someone will try to escape the '}' */ + while(ch=b_getch(input),ch!=EOF && ch!='}') { + b_addchr(dest,ch); + } + if (ch != '}') { + syntax(); + return 1; + } + b_addchr(dest, SPECIAL_VAR_SYMBOL); + break; + case '(': + b_getch(input); + process_command_subs(dest, ctx, input, ')'); + break; + case '*': + sep[0]=ifs[0]; + for (i=1; i<global_argc; i++) { + parse_string(dest, ctx, global_argv[i]); + if (i+1 < global_argc) parse_string(dest, ctx, sep); + } + break; + case '@': + case '-': + case '_': + /* still unhandled, but should be eventually */ + bb_error_msg("unhandled syntax: $%c",ch); + return 1; + break; + default: + b_addqchr(dest,'$',dest->quote); + } + /* Eat the character if the flag was set. If the compiler + * is smart enough, we could substitute "b_getch(input);" + * for all the "advance = 1;" above, and also end up with + * a nice size-optimized program. Hah! That'll be the day. + */ + if (advance) b_getch(input); + return 0; +} + +int parse_string(o_string *dest, struct p_context *ctx, const char *src) +{ + struct in_str foo; + setup_string_in_str(&foo, src); + return parse_stream(dest, ctx, &foo, '\0'); +} + +/* return code is 0 for normal exit, 1 for syntax error */ +int parse_stream(o_string *dest, struct p_context *ctx, + struct in_str *input, int end_trigger) +{ + int ch, m; + int redir_fd; + redir_type redir_style; + int next; + + /* Only double-quote state is handled in the state variable dest->quote. + * A single-quote triggers a bypass of the main loop until its mate is + * found. When recursing, quote state is passed in via dest->quote. */ + + debug_printf("parse_stream, end_trigger=%d\n",end_trigger); + while ((ch=b_getch(input))!=EOF) { + m = map[ch]; + next = (ch == '\n') ? 0 : b_peek(input); + debug_printf("parse_stream: ch=%c (%d) m=%d quote=%d\n", + ch,ch,m,dest->quote); + if (m==0 || ((m==1 || m==2) && dest->quote)) { + b_addqchr(dest, ch, dest->quote); + } else { + if (m==2) { /* unquoted IFS */ + if (done_word(dest, ctx)) { + return 1; + } + /* If we aren't performing a substitution, treat a newline as a + * command separator. */ + if (end_trigger != '\0' && ch=='\n') + done_pipe(ctx,PIPE_SEQ); + } + if (ch == end_trigger && !dest->quote && ctx->w==RES_NONE) { + debug_printf("leaving parse_stream (triggered)\n"); + return 0; + } + if (m!=2) switch (ch) { + case '#': + if (dest->length == 0 && !dest->quote) { + while(ch=b_peek(input),ch!=EOF && ch!='\n') { b_getch(input); } + } else { + b_addqchr(dest, ch, dest->quote); + } + break; + case '\\': + if (next == EOF) { + syntax(); + return 1; + } + b_addqchr(dest, '\\', dest->quote); + b_addqchr(dest, b_getch(input), dest->quote); + break; + case '$': + if (handle_dollar(dest, ctx, input)!=0) return 1; + break; + case '\'': + dest->nonnull = 1; + while(ch=b_getch(input),ch!=EOF && ch!='\'') { + b_addchr(dest,ch); + } + if (ch==EOF) { + syntax(); + return 1; + } + break; + case '"': + dest->nonnull = 1; + dest->quote = !dest->quote; + break; + case '`': + process_command_subs(dest, ctx, input, '`'); + break; + case '>': + redir_fd = redirect_opt_num(dest); + done_word(dest, ctx); + redir_style=REDIRECT_OVERWRITE; + if (next == '>') { + redir_style=REDIRECT_APPEND; + b_getch(input); + } else if (next == '(') { + syntax(); /* until we support >(list) Process Substitution */ + return 1; + } + setup_redirect(ctx, redir_fd, redir_style, input); + break; + case '<': + redir_fd = redirect_opt_num(dest); + done_word(dest, ctx); + redir_style=REDIRECT_INPUT; + if (next == '<') { + redir_style=REDIRECT_HEREIS; + b_getch(input); + } else if (next == '>') { + redir_style=REDIRECT_IO; + b_getch(input); + } else if (next == '(') { + syntax(); /* until we support <(list) Process Substitution */ + return 1; + } + setup_redirect(ctx, redir_fd, redir_style, input); + break; + case ';': + done_word(dest, ctx); + done_pipe(ctx,PIPE_SEQ); + break; + case '&': + done_word(dest, ctx); + if (next=='&') { + b_getch(input); + done_pipe(ctx,PIPE_AND); + } else { + done_pipe(ctx,PIPE_BG); + } + break; + case '|': + done_word(dest, ctx); + if (next=='|') { + b_getch(input); + done_pipe(ctx,PIPE_OR); + } else { + /* we could pick up a file descriptor choice here + * with redirect_opt_num(), but bash doesn't do it. + * "echo foo 2| cat" yields "foo 2". */ + done_command(ctx); + } + break; + case '(': + case '{': + if (parse_group(dest, ctx, input, ch)!=0) return 1; + break; + case ')': + case '}': + syntax(); /* Proper use of this character caught by end_trigger */ + return 1; + break; + default: + syntax(); /* this is really an internal logic error */ + return 1; + } + } + } + /* complain if quote? No, maybe we just finished a command substitution + * that was quoted. Example: + * $ echo "`cat foo` plus more" + * and we just got the EOF generated by the subshell that ran "cat foo" + * The only real complaint is if we got an EOF when end_trigger != '\0', + * that is, we were really supposed to get end_trigger, and never got + * one before the EOF. Can't use the standard "syntax error" return code, + * so that parse_stream_outer can distinguish the EOF and exit smoothly. */ + debug_printf("leaving parse_stream (EOF)\n"); + if (end_trigger != '\0') return -1; + return 0; +} + +static void mapset(const char *set, int code) +{ + const unsigned char *s; + for (s = (const unsigned char *)set; *s; s++) map[(int)*s] = code; +} + +static void update_ifs_map(void) +{ + /* char *ifs and char map[256] are both globals. */ + ifs = getenv("IFS"); + if (ifs == NULL) ifs=" \t\n"; + /* Precompute a list of 'flow through' behavior so it can be treated + * quickly up front. Computation is necessary because of IFS. + * Special case handling of IFS == " \t\n" is not implemented. + * The map[] array only really needs two bits each, and on most machines + * that would be faster because of the reduced L1 cache footprint. + */ + memset(map,0,sizeof(map)); /* most characters flow through always */ + mapset("\\$'\"`", 3); /* never flow through */ + mapset("<>;&|(){}#", 1); /* flow through if quoted */ + mapset(ifs, 2); /* also flow through if quoted */ +} + +/* most recursion does not come through here, the exception is + * from builtin_source() */ +int parse_stream_outer(struct in_str *inp, int flag) +{ + + struct p_context ctx; + o_string temp=NULL_O_STRING; + int rcode; + do { + ctx.type = flag; + initialize_context(&ctx); + update_ifs_map(); + if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING)) mapset(";$&|", 0); + inp->promptmode=1; + rcode = parse_stream(&temp, &ctx, inp, '\n'); + if (rcode != 1 && ctx.old_flag != 0) { + syntax(); + } + if (rcode != 1 && ctx.old_flag == 0) { + done_word(&temp, &ctx); + done_pipe(&ctx,PIPE_SEQ); + run_list(ctx.list_head); + } else { + if (ctx.old_flag != 0) { + free(ctx.stack); + b_reset(&temp); + } + temp.nonnull = 0; + temp.quote = 0; + inp->p = NULL; + free_pipe_list(ctx.list_head,0); + } + b_free(&temp); + } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP)); /* loop on syntax errors, return on EOF */ + return 0; +} + +static int parse_string_outer(const char *s, int flag) +{ + struct in_str input; + setup_string_in_str(&input, s); + return parse_stream_outer(&input, flag); +} + +static int parse_file_outer(FILE *f) +{ + int rcode; + struct in_str input; + setup_file_in_str(&input, f); + rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON); + return rcode; +} + +/* Make sure we have a controlling tty. If we get started under a job + * aware app (like bash for example), make sure we are now in charge so + * we don't fight over who gets the foreground */ +static void setup_job_control(void) +{ + static pid_t shell_pgrp; + /* Loop until we are in the foreground. */ + while (tcgetpgrp (shell_terminal) != (shell_pgrp = getpgrp ())) + kill (- shell_pgrp, SIGTTIN); + + /* Ignore interactive and job-control signals. */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + + /* Put ourselves in our own process group. */ + setsid(); + shell_pgrp = getpid (); + setpgid (shell_pgrp, shell_pgrp); + + /* Grab control of the terminal. */ + tcsetpgrp(shell_terminal, shell_pgrp); +} + +int hush_main(int argc, char **argv) +{ + int opt; + FILE *input; + char **e = environ; + + /* XXX what should these be while sourcing /etc/profile? */ + global_argc = argc; + global_argv = argv; + + /* (re?) initialize globals. Sometimes hush_main() ends up calling + * hush_main(), therefore we cannot rely on the BSS to zero out this + * stuff. Reset these to 0 every time. */ + ifs = NULL; + /* map[] is taken care of with call to update_ifs_map() */ + fake_mode = 0; + interactive = 0; + close_me_head = NULL; + last_bg_pid = 0; + job_list = NULL; + last_jobid = 0; + + /* Initialize some more globals to non-zero values */ + set_cwd(); + if (ENABLE_FEATURE_COMMAND_EDITING) cmdedit_set_initial_prompt(); + else PS1 = NULL; + PS2 = "> "; + + /* initialize our shell local variables with the values + * currently living in the environment */ + if (e) { + for (; *e; e++) + set_local_var(*e, 2); /* without call putenv() */ + } + + last_return_code=EXIT_SUCCESS; + + + if (argv[0] && argv[0][0] == '-') { + debug_printf("\nsourcing /etc/profile\n"); + if ((input = fopen("/etc/profile", "r")) != NULL) { + mark_open(fileno(input)); + parse_file_outer(input); + mark_closed(fileno(input)); + fclose(input); + } + } + input=stdin; + + while ((opt = getopt(argc, argv, "c:xif")) > 0) { + switch (opt) { + case 'c': + { + global_argv = argv+optind; + global_argc = argc-optind; + opt = parse_string_outer(optarg, FLAG_PARSE_SEMICOLON); + goto final_return; + } + break; + case 'i': + interactive++; + break; + case 'f': + fake_mode++; + break; + default: +#ifndef BB_VER + fprintf(stderr, "Usage: sh [FILE]...\n" + " or: sh -c command [args]...\n\n"); + exit(EXIT_FAILURE); +#else + bb_show_usage(); +#endif + } + } + /* A shell is interactive if the `-i' flag was given, or if all of + * the following conditions are met: + * no -c command + * no arguments remaining or the -s flag given + * standard input is a terminal + * standard output is a terminal + * Refer to Posix.2, the description of the `sh' utility. */ + if (argv[optind]==NULL && input==stdin && + isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) { + interactive++; + } + + debug_printf("\ninteractive=%d\n", interactive); + if (interactive) { + /* Looks like they want an interactive shell */ +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET + printf( "\n\n%s hush - the humble shell v0.01 (testing)\n", + BB_BANNER); + printf( "Enter 'help' for a list of built-in commands.\n\n"); +#endif + setup_job_control(); + } + + if (argv[optind]==NULL) { + opt=parse_file_outer(stdin); + goto final_return; + } + + debug_printf("\nrunning script '%s'\n", argv[optind]); + global_argv = argv+optind; + global_argc = argc-optind; + input = xfopen(argv[optind], "r"); + opt = parse_file_outer(input); + +#ifdef CONFIG_FEATURE_CLEAN_UP + fclose(input); + if (cwd && cwd != bb_msg_unknown) + free((char*)cwd); + { + struct variables *cur, *tmp; + for(cur = top_vars; cur; cur = tmp) { + tmp = cur->next; + if (!cur->flg_read_only) { + free(cur->name); + free(cur->value); + free(cur); + } + } + } +#endif + +final_return: + return opt ? opt : last_return_code; +} + +static char *insert_var_value(char *inp) +{ + int res_str_len = 0; + int len; + int done = 0; + char *p, *p1, *res_str = NULL; + + while ((p = strchr(inp, SPECIAL_VAR_SYMBOL))) { + if (p != inp) { + len = p - inp; + res_str = xrealloc(res_str, (res_str_len + len)); + strncpy((res_str + res_str_len), inp, len); + res_str_len += len; + } + inp = ++p; + p = strchr(inp, SPECIAL_VAR_SYMBOL); + *p = '\0'; + if ((p1 = lookup_param(inp))) { + len = res_str_len + strlen(p1); + res_str = xrealloc(res_str, (1 + len)); + strcpy((res_str + res_str_len), p1); + res_str_len = len; + } + *p = SPECIAL_VAR_SYMBOL; + inp = ++p; + done = 1; + } + if (done) { + res_str = xrealloc(res_str, (1 + res_str_len + strlen(inp))); + strcpy((res_str + res_str_len), inp); + while ((p = strchr(res_str, '\n'))) { + *p = ' '; + } + } + return (res_str == NULL) ? inp : res_str; +} + +static char **make_list_in(char **inp, char *name) +{ + int len, i; + int name_len = strlen(name); + int n = 0; + char **list; + char *p1, *p2, *p3; + + /* create list of variable values */ + list = xmalloc(sizeof(*list)); + for (i = 0; inp[i]; i++) { + p3 = insert_var_value(inp[i]); + p1 = p3; + while (*p1) { + if ((*p1 == ' ')) { + p1++; + continue; + } + if ((p2 = strchr(p1, ' '))) { + len = p2 - p1; + } else { + len = strlen(p1); + p2 = p1 + len; + } + /* we use n + 2 in realloc for list,because we add + * new element and then we will add NULL element */ + list = xrealloc(list, sizeof(*list) * (n + 2)); + list[n] = xmalloc(2 + name_len + len); + strcpy(list[n], name); + strcat(list[n], "="); + strncat(list[n], p1, len); + list[n++][name_len + len + 1] = '\0'; + p1 = p2; + } + if (p3 != inp[i]) free(p3); + } + list[n] = NULL; + return list; +} + +/* Make new string for parser */ +static char * make_string(char ** inp) +{ + char *p; + char *str = NULL; + int n; + int len = 2; + + for (n = 0; inp[n]; n++) { + p = insert_var_value(inp[n]); + str = xrealloc(str, (len + strlen(p))); + if (n) { + strcat(str, " "); + } else { + *str = '\0'; + } + strcat(str, p); + len = strlen(str) + 3; + if (p != inp[n]) free(p); + } + len = strlen(str); + *(str + len) = '\n'; + *(str + len + 1) = '\0'; + return str; +} diff --git a/shell/lash.c b/shell/lash.c new file mode 100644 index 000000000..3b51e98a9 --- /dev/null +++ b/shell/lash.c @@ -0,0 +1,1573 @@ +/* vi: set sw=4 ts=4: */ +/* + * lash -- the BusyBox Lame-Ass SHell + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Based in part on ladsh.c by Michael K. Johnson and Erik W. Troan, which is + * under the following liberal license: "We have placed this source code in the + * public domain. Use it in any project, free or commercial." + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* This shell's parsing engine is officially at a dead-end. Future + * work shell work should be done using hush, msh, or ash. This is + * still a very useful, small shell -- it just don't need any more + * features beyond what it already has... + */ + +//For debugging/development on the shell only... +//#define DEBUG_SHELL + + +#include "busybox.h" +#include <getopt.h> +#include "cmdedit.h" + +#include <glob.h> +#define expand_t glob_t + +/* Always enable for the moment... */ +#define CONFIG_LASH_PIPE_N_REDIRECTS +#define CONFIG_LASH_JOB_CONTROL + +static const int MAX_READ = 128; /* size of input buffer for `read' builtin */ +#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" + + +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS +enum redir_type { REDIRECT_INPUT, REDIRECT_OVERWRITE, + REDIRECT_APPEND +}; +#endif + +enum { + DEFAULT_CONTEXT = 0x1, + IF_TRUE_CONTEXT = 0x2, + IF_FALSE_CONTEXT = 0x4, + THEN_EXP_CONTEXT = 0x8, + ELSE_EXP_CONTEXT = 0x10 +}; + +#define LASH_OPT_DONE (1) +#define LASH_OPT_SAW_QUOTE (2) + +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS +struct redir_struct { + enum redir_type type; /* type of redirection */ + int fd; /* file descriptor being redirected */ + char *filename; /* file to redirect fd to */ +}; +#endif + +struct child_prog { + pid_t pid; /* 0 if exited */ + char **argv; /* program name and arguments */ + int num_redirects; /* elements in redirection array */ + int is_stopped; /* is the program currently running? */ + struct job *family; /* pointer back to the child's parent job */ +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS + struct redir_struct *redirects; /* I/O redirects */ +#endif +}; + +struct jobset { + struct job *head; /* head of list of running jobs */ + struct job *fg; /* current foreground job */ +}; + +struct job { + int jobid; /* job number */ + int num_progs; /* total number of programs in job */ + int running_progs; /* number of programs running */ + char *text; /* name of job */ + char *cmdbuf; /* buffer various argv's point into */ + pid_t pgrp; /* process group ID for the job */ + struct child_prog *progs; /* array of programs in job */ + struct job *next; /* to track background commands */ + int stopped_progs; /* number of programs alive, but stopped */ + unsigned int job_context; /* bitmask defining current context */ + struct jobset *job_list; +}; + +struct built_in_command { + char *cmd; /* name */ + char *descr; /* description */ + int (*function) (struct child_prog *); /* function ptr */ +}; + +/* function prototypes for builtins */ +static int builtin_cd(struct child_prog *cmd); +static int builtin_exec(struct child_prog *cmd); +static int builtin_exit(struct child_prog *cmd); +static int builtin_fg_bg(struct child_prog *cmd); +static int builtin_help(struct child_prog *cmd); +static int builtin_jobs(struct child_prog *dummy); +static int builtin_pwd(struct child_prog *dummy); +static int builtin_export(struct child_prog *cmd); +static int builtin_source(struct child_prog *cmd); +static int builtin_unset(struct child_prog *cmd); +static int builtin_read(struct child_prog *cmd); + + +/* function prototypes for shell stuff */ +static void checkjobs(struct jobset *job_list); +static void remove_job(struct jobset *j_list, struct job *job); +static int get_command(FILE * source, char *command); +static int parse_command(char **command_ptr, struct job *job, int *inbg); +static int run_command(struct job *newjob, int inbg, int outpipe[2]); +static int pseudo_exec(struct child_prog *cmd) ATTRIBUTE_NORETURN; +static int busy_loop(FILE * input); + + +/* Table of built-in functions (these are non-forking builtins, meaning they + * can change global variables in the parent shell process but they will not + * work with pipes and redirects; 'unset foo | whatever' will not work) */ +static struct built_in_command bltins[] = { + {"bg", "Resume a job in the background", builtin_fg_bg}, + {"cd", "Change working directory", builtin_cd}, + {"exec", "Exec command, replacing this shell with the exec'd process", builtin_exec}, + {"exit", "Exit from shell()", builtin_exit}, + {"fg", "Bring job into the foreground", builtin_fg_bg}, + {"jobs", "Lists the active jobs", builtin_jobs}, + {"export", "Set environment variable", builtin_export}, + {"unset", "Unset environment variable", builtin_unset}, + {"read", "Input environment variable", builtin_read}, + {".", "Source-in and run commands in a file", builtin_source}, + /* to do: add ulimit */ + {NULL, NULL, NULL} +}; + +/* Table of forking built-in functions (things that fork cannot change global + * variables in the parent process, such as the current working directory) */ +static struct built_in_command bltins_forking[] = { + {"pwd", "Print current directory", builtin_pwd}, + {"help", "List shell built-in commands", builtin_help}, + {NULL, NULL, NULL} +}; + + +static int shell_context; /* Type prompt trigger (PS1 or PS2) */ + + +/* Globals that are static to this file */ +static const char *cwd; +static char *local_pending_command; +static struct jobset job_list = { NULL, NULL }; +static int argc; +static char **argv; +static llist_t *close_me_list; +static int last_return_code; +static int last_bg_pid; +static unsigned int last_jobid; +static int shell_terminal; +static char *PS1; +static char *PS2 = "> "; + + +#ifdef DEBUG_SHELL +static inline void debug_printf(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} +#else +static inline void debug_printf(const char ATTRIBUTE_UNUSED *format, ...) { } +#endif + +/* + Most builtins need access to the struct child_prog that has + their arguments, previously coded as cmd->progs[0]. That coding + can exhibit a bug, if the builtin is not the first command in + a pipeline: "echo foo | exec sort" will attempt to exec foo. + +builtin previous use notes +------ ----------------- --------- +cd cmd->progs[0] +exec cmd->progs[0] squashed bug: didn't look for applets or forking builtins +exit cmd->progs[0] +fg_bg cmd->progs[0], job_list->head, job_list->fg +help 0 +jobs job_list->head +pwd 0 +export cmd->progs[0] +source cmd->progs[0] +unset cmd->progs[0] +read cmd->progs[0] + +I added "struct job *family;" to struct child_prog, +and switched API to builtin_foo(struct child_prog *child); +So cmd->text becomes child->family->text + cmd->job_context becomes child->family->job_context + cmd->progs[0] becomes *child + job_list becomes child->family->job_list + */ + +/* built-in 'cd <path>' handler */ +static int builtin_cd(struct child_prog *child) +{ + char *newdir; + + if (child->argv[1] == NULL) + newdir = getenv("HOME"); + else + newdir = child->argv[1]; + if (chdir(newdir)) { + bb_perror_msg("cd: %s", newdir); + return EXIT_FAILURE; + } + cwd = xgetcwd((char *)cwd); + if (!cwd) + cwd = bb_msg_unknown; + return EXIT_SUCCESS; +} + +/* built-in 'exec' handler */ +static int builtin_exec(struct child_prog *child) +{ + if (child->argv[1] == NULL) + return EXIT_SUCCESS; /* Really? */ + child->argv++; + while(close_me_list) close((long)llist_pop(&close_me_list)); + pseudo_exec(child); + /* never returns */ +} + +/* built-in 'exit' handler */ +static int builtin_exit(struct child_prog *child) +{ + if (child->argv[1] == NULL) + exit(EXIT_SUCCESS); + + exit (atoi(child->argv[1])); +} + +/* built-in 'fg' and 'bg' handler */ +static int builtin_fg_bg(struct child_prog *child) +{ + int i, jobnum; + struct job *job=NULL; + + /* If they gave us no args, assume they want the last backgrounded task */ + if (!child->argv[1]) { + for (job = child->family->job_list->head; job; job = job->next) { + if (job->jobid == last_jobid) { + break; + } + } + if (!job) { + bb_error_msg("%s: no current job", child->argv[0]); + return EXIT_FAILURE; + } + } else { + if (sscanf(child->argv[1], "%%%d", &jobnum) != 1) { + bb_error_msg(bb_msg_invalid_arg, child->argv[1], child->argv[0]); + return EXIT_FAILURE; + } + for (job = child->family->job_list->head; job; job = job->next) { + if (job->jobid == jobnum) { + break; + } + } + if (!job) { + bb_error_msg("%s: %d: no such job", child->argv[0], jobnum); + return EXIT_FAILURE; + } + } + + if (*child->argv[0] == 'f') { + /* Put the job into the foreground. */ + tcsetpgrp(shell_terminal, job->pgrp); + + child->family->job_list->fg = job; + } + + /* Restart the processes in the job */ + for (i = 0; i < job->num_progs; i++) + job->progs[i].is_stopped = 0; + + job->stopped_progs = 0; + + if ( (i=kill(- job->pgrp, SIGCONT)) < 0) { + if (i == ESRCH) { + remove_job(&job_list, job); + } else { + bb_perror_msg("kill (SIGCONT)"); + } + } + + return EXIT_SUCCESS; +} + +/* built-in 'help' handler */ +static int builtin_help(struct child_prog ATTRIBUTE_UNUSED *dummy) +{ + struct built_in_command *x; + + printf("\nBuilt-in commands:\n" + "-------------------\n"); + for (x = bltins; x->cmd; x++) { + if (x->descr==NULL) + continue; + printf("%s\t%s\n", x->cmd, x->descr); + } + for (x = bltins_forking; x->cmd; x++) { + if (x->descr==NULL) + continue; + printf("%s\t%s\n", x->cmd, x->descr); + } + putchar('\n'); + return EXIT_SUCCESS; +} + +/* built-in 'jobs' handler */ +static int builtin_jobs(struct child_prog *child) +{ + struct job *job; + char *status_string; + + for (job = child->family->job_list->head; job; job = job->next) { + if (job->running_progs == job->stopped_progs) + status_string = "Stopped"; + else + status_string = "Running"; + + printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->text); + } + return EXIT_SUCCESS; +} + + +/* built-in 'pwd' handler */ +static int builtin_pwd(struct child_prog ATTRIBUTE_UNUSED *dummy) +{ + cwd = xgetcwd((char *)cwd); + if (!cwd) + cwd = bb_msg_unknown; + puts(cwd); + return EXIT_SUCCESS; +} + +/* built-in 'export VAR=value' handler */ +static int builtin_export(struct child_prog *child) +{ + int res; + char *v = child->argv[1]; + + if (v == NULL) { + char **e; + for (e = environ; *e; e++) { + puts(*e); + } + return 0; + } + res = putenv(v); + if (res) + bb_perror_msg("export"); +#ifdef CONFIG_FEATURE_SH_FANCY_PROMPT + if (strncmp(v, "PS1=", 4)==0) + PS1 = getenv("PS1"); +#endif + +#ifdef CONFIG_LOCALE_SUPPORT + // TODO: why getenv? "" would be just as good... + if(strncmp(v, "LC_ALL=", 7)==0) + setlocale(LC_ALL, getenv("LC_ALL")); + if(strncmp(v, "LC_CTYPE=", 9)==0) + setlocale(LC_CTYPE, getenv("LC_CTYPE")); +#endif + + return res; +} + +/* built-in 'read VAR' handler */ +static int builtin_read(struct child_prog *child) +{ + int res = 0, len; + char *s; + char string[MAX_READ]; + + if (child->argv[1]) { + /* argument (VAR) given: put "VAR=" into buffer */ + safe_strncpy(string, child->argv[1], MAX_READ-1); + len = strlen(string); + string[len++] = '='; + string[len] = '\0'; + fgets(&string[len], sizeof(string) - len, stdin); /* read string */ + res = strlen(string); + if (res > len) + string[--res] = '\0'; /* chomp trailing newline */ + /* + ** string should now contain "VAR=<value>" + ** copy it (putenv() won't do that, so we must make sure + ** the string resides in a static buffer!) + */ + res = -1; + if ((s = strdup(string))) + res = putenv(s); + if (res) + bb_perror_msg("read"); + } + else + fgets(string, sizeof(string), stdin); + + return res; +} + +/* Built-in '.' handler (read-in and execute commands from file) */ +static int builtin_source(struct child_prog *child) +{ + FILE *input; + int status; + + input = fopen_or_warn(child->argv[1], "r"); + if (!input) { + return EXIT_FAILURE; + } + + llist_add_to(&close_me_list, (void *)(long)fileno(input)); + /* Now run the file */ + status = busy_loop(input); + fclose(input); + llist_pop(&close_me_list); + return status; +} + +/* built-in 'unset VAR' handler */ +static int builtin_unset(struct child_prog *child) +{ + if (child->argv[1] == NULL) { + printf(bb_msg_requires_arg, "unset"); + return EXIT_FAILURE; + } + unsetenv(child->argv[1]); + return EXIT_SUCCESS; +} + +#ifdef CONFIG_LASH_JOB_CONTROL +/* free up all memory from a job */ +static void free_job(struct job *cmd) +{ + int i; + struct jobset *keep; + + for (i = 0; i < cmd->num_progs; i++) { + free(cmd->progs[i].argv); +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS + if (cmd->progs[i].redirects) + free(cmd->progs[i].redirects); +#endif + } + free(cmd->progs); + free(cmd->text); + free(cmd->cmdbuf); + keep = cmd->job_list; + memset(cmd, 0, sizeof(struct job)); + cmd->job_list = keep; +} + +/* remove a job from a jobset */ +static void remove_job(struct jobset *j_list, struct job *job) +{ + struct job *prevjob; + + free_job(job); + if (job == j_list->head) { + j_list->head = job->next; + } else { + prevjob = j_list->head; + while (prevjob->next != job) + prevjob = prevjob->next; + prevjob->next = job->next; + } + + if (j_list->head) + last_jobid = j_list->head->jobid; + else + last_jobid = 0; + + free(job); +} + +/* Checks to see if any background processes have exited -- if they + have, figure out why and see if a job has completed */ +static void checkjobs(struct jobset *j_list) +{ + struct job *job; + pid_t childpid; + int status; + int prognum = 0; + + while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { + for (job = j_list->head; job; job = job->next) { + prognum = 0; + while (prognum < job->num_progs && + job->progs[prognum].pid != childpid) prognum++; + if (prognum < job->num_progs) + break; + } + + /* This happens on backticked commands */ + if(job==NULL) + return; + + if (WIFEXITED(status) || WIFSIGNALED(status)) { + /* child exited */ + job->running_progs--; + job->progs[prognum].pid = 0; + + if (!job->running_progs) { + printf(JOB_STATUS_FORMAT, job->jobid, "Done", job->text); + last_jobid=0; + remove_job(j_list, job); + } + } else { + /* child stopped */ + job->stopped_progs++; + job->progs[prognum].is_stopped = 1; + } + } + + if (childpid == -1 && errno != ECHILD) + bb_perror_msg("waitpid"); +} +#else +static void checkjobs(struct jobset *j_list) +{ +} +static void free_job(struct job *cmd) +{ +} +static void remove_job(struct jobset *j_list, struct job *job) +{ +} +#endif + +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS +/* squirrel != NULL means we squirrel away copies of stdin, stdout, + * and stderr if they are redirected. */ +static int setup_redirects(struct child_prog *prog, int squirrel[]) +{ + int i; + int openfd; + int mode = O_RDONLY; + struct redir_struct *redir = prog->redirects; + + for (i = 0; i < prog->num_redirects; i++, redir++) { + switch (redir->type) { + case REDIRECT_INPUT: + mode = O_RDONLY; + break; + case REDIRECT_OVERWRITE: + mode = O_WRONLY | O_CREAT | O_TRUNC; + break; + case REDIRECT_APPEND: + mode = O_WRONLY | O_CREAT | O_APPEND; + break; + } + + openfd = open(redir->filename, mode, 0666); + if (openfd < 0) { + /* this could get lost if stderr has been redirected, but + bash and ash both lose it as well (though zsh doesn't!) */ + bb_perror_msg("error opening %s", redir->filename); + return 1; + } + + if (openfd != redir->fd) { + if (squirrel && redir->fd < 3) { + squirrel[redir->fd] = dup(redir->fd); + fcntl (squirrel[redir->fd], F_SETFD, FD_CLOEXEC); + } + dup2(openfd, redir->fd); + close(openfd); + } + } + + return 0; +} + +static void restore_redirects(int squirrel[]) +{ + int i, fd; + for (i=0; i<3; i++) { + fd = squirrel[i]; + if (fd != -1) { + /* No error checking. I sure wouldn't know what + * to do with an error if I found one! */ + dup2(fd, i); + close(fd); + } + } +} +#else +static inline int setup_redirects(struct child_prog *prog, int squirrel[]) +{ + return 0; +} +static inline void restore_redirects(int squirrel[]) +{ +} +#endif + +static inline void cmdedit_set_initial_prompt(void) +{ +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT + PS1 = NULL; +#else + PS1 = getenv("PS1"); + if(PS1==0) + PS1 = "\\w \\$ "; +#endif +} + +static inline void setup_prompt_string(char **prompt_str) +{ +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT + /* Set up the prompt */ + if (shell_context == 0) { + free(PS1); + PS1=xmalloc(strlen(cwd)+4); + sprintf(PS1, "%s %c ", cwd, ( geteuid() != 0 ) ? '$': '#'); + *prompt_str = PS1; + } else { + *prompt_str = PS2; + } +#else + *prompt_str = (shell_context==0)? PS1 : PS2; +#endif +} + +static int get_command(FILE * source, char *command) +{ + char *prompt_str; + + if (source == NULL) { + if (local_pending_command) { + /* a command specified (-c option): return it & mark it done */ + strcpy(command, local_pending_command); + free(local_pending_command); + local_pending_command = NULL; + return 0; + } + return 1; + } + + if (source == stdin) { + setup_prompt_string(&prompt_str); + +#ifdef CONFIG_FEATURE_COMMAND_EDITING + /* + ** enable command line editing only while a command line + ** is actually being read; otherwise, we'll end up bequeathing + ** atexit() handlers and other unwanted stuff to our + ** child processes (rob@sysgo.de) + */ + cmdedit_read_input(prompt_str, command); + return 0; +#else + fputs(prompt_str, stdout); +#endif + } + + if (!fgets(command, BUFSIZ - 2, source)) { + if (source == stdin) + puts(""); + return 1; + } + + return 0; +} + +static char * strsep_space( char *string, int * ix) +{ + /* Short circuit the trivial case */ + if ( !string || ! string[*ix]) + return NULL; + + /* Find the end of the token. */ + while (string[*ix] && !isspace(string[*ix]) ) { + (*ix)++; + } + + /* Find the end of any whitespace trailing behind + * the token and let that be part of the token */ + while (string[*ix] && (isspace)(string[*ix]) ) { + (*ix)++; + } + + if (!*ix) { + /* Nothing useful was found */ + return NULL; + } + + return xstrndup(string, *ix); +} + +static int expand_arguments(char *command) +{ + int total_length=0, length, i, retval, ix = 0; + expand_t expand_result; + char *tmpcmd, *cmd, *cmd_copy; + char *src, *dst, *var; + const char * const out_of_space = "out of space during expansion"; + int flags = GLOB_NOCHECK +#ifdef GLOB_BRACE + | GLOB_BRACE +#endif +#ifdef GLOB_TILDE + | GLOB_TILDE +#endif + ; + + /* get rid of the terminating \n */ + chomp(command); + + /* Fix up escape sequences to be the Real Thing(tm) */ + while( command && command[ix]) { + if (command[ix] == '\\') { + const char *tmp = command+ix+1; + command[ix] = bb_process_escape_sequence( &tmp ); + memmove(command+ix + 1, tmp, strlen(tmp)+1); + } + ix++; + } + /* Use glob and then fixup environment variables and such */ + + /* It turns out that glob is very stupid. We have to feed it one word at a + * time since it can't cope with a full string. Here we convert command + * (char*) into cmd (char**, one word per string) */ + + /* We need a clean copy, so strsep can mess up the copy while + * we write stuff into the original (in a minute) */ + cmd = cmd_copy = xstrdup(command); + *command = '\0'; + for (ix = 0, tmpcmd = cmd; + (tmpcmd = strsep_space(cmd, &ix)) != NULL; cmd += ix, ix=0) { + if (*tmpcmd == '\0') + break; + /* we need to trim() the result for glob! */ + trim(tmpcmd); + retval = glob(tmpcmd, flags, NULL, &expand_result); + free(tmpcmd); /* Free mem allocated by strsep_space */ + if (retval == GLOB_NOSPACE) { + /* Mem may have been allocated... */ + globfree (&expand_result); + bb_error_msg(out_of_space); + return FALSE; + } else if (retval != 0) { + /* Some other error. GLOB_NOMATCH shouldn't + * happen because of the GLOB_NOCHECK flag in + * the glob call. */ + bb_error_msg("syntax error"); + return FALSE; + } else { + /* Convert from char** (one word per string) to a simple char*, + * but don't overflow command which is BUFSIZ in length */ + for (i=0; i < expand_result.gl_pathc; i++) { + length=strlen(expand_result.gl_pathv[i]); + if (total_length+length+1 >= BUFSIZ) { + bb_error_msg(out_of_space); + return FALSE; + } + strcat(command+total_length, " "); + total_length+=1; + strcat(command+total_length, expand_result.gl_pathv[i]); + total_length+=length; + } + globfree (&expand_result); + } + } + free(cmd_copy); + trim(command); + + /* Now do the shell variable substitutions which + * wordexp can't do for us, namely $? and $! */ + src = command; + while((dst = strchr(src,'$')) != NULL){ + var = NULL; + switch(*(dst+1)) { + case '?': + var = itoa(last_return_code); + break; + case '!': + if (last_bg_pid==-1) + *(var)='\0'; + else + var = itoa(last_bg_pid); + break; + /* Everything else like $$, $#, $[0-9], etc. should all be + * expanded by wordexp(), so we can in theory skip that stuff + * here, but just to be on the safe side (i.e., since uClibc + * wordexp doesn't do this stuff yet), lets leave it in for + * now. */ + case '$': + var = itoa(getpid()); + break; + case '#': + var = itoa(argc-1); + break; + case '0':case '1':case '2':case '3':case '4': + case '5':case '6':case '7':case '8':case '9': + { + int ixx=*(dst+1)-48+1; + if (ixx >= argc) { + var='\0'; + } else { + var = argv[ixx]; + } + } + break; + + } + if (var) { + /* a single character construction was found, and + * already handled in the case statement */ + src=dst+2; + } else { + /* Looks like an environment variable */ + char delim_hold; + int num_skip_chars=0; + int dstlen = strlen(dst); + /* Is this a ${foo} type variable? */ + if (dstlen >=2 && *(dst+1) == '{') { + src=strchr(dst+1, '}'); + num_skip_chars=1; + } else { + src=dst+1; + while((isalnum)(*src) || *src=='_') src++; + } + if (src == NULL) { + src = dst+dstlen; + } + delim_hold=*src; + *src='\0'; /* temporary */ + var = getenv(dst + 1 + num_skip_chars); + *src=delim_hold; + src += num_skip_chars; + } + if (var == NULL) { + /* Seems we got an un-expandable variable. So delete it. */ + var = ""; + } + { + int subst_len = strlen(var); + int trail_len = strlen(src); + if (dst+subst_len+trail_len >= command+BUFSIZ) { + bb_error_msg(out_of_space); + return FALSE; + } + /* Move stuff to the end of the string to accommodate + * filling the created gap with the new stuff */ + memmove(dst+subst_len, src, trail_len+1); + /* Now copy in the new stuff */ + memcpy(dst, var, subst_len); + src = dst+subst_len; + } + } + + return TRUE; +} + +/* Return cmd->num_progs as 0 if no command is present (e.g. an empty + line). If a valid command is found, command_ptr is set to point to + the beginning of the next command (if the original command had more + then one job associated with it) or NULL if no more commands are + present. */ +static int parse_command(char **command_ptr, struct job *job, int *inbg) +{ + char *command; + char *return_command = NULL; + char *src, *buf; + int argc_l; + int flag; + int argv_alloced; + char quote = '\0'; + struct child_prog *prog; +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS + int i; + char *chptr; +#endif + + /* skip leading white space */ + *command_ptr = skip_whitespace(*command_ptr); + + /* this handles empty lines or leading '#' characters */ + if (!**command_ptr || (**command_ptr == '#')) { + job->num_progs=0; + return 0; + } + + *inbg = 0; + job->num_progs = 1; + job->progs = xmalloc(sizeof(*job->progs)); + + /* We set the argv elements to point inside of this string. The + memory is freed by free_job(). Allocate twice the original + length in case we need to quote every single character. + + Getting clean memory relieves us of the task of NULL + terminating things and makes the rest of this look a bit + cleaner (though it is, admittedly, a tad less efficient) */ + job->cmdbuf = command = xzalloc(2*strlen(*command_ptr) + 1); + job->text = NULL; + + prog = job->progs; + prog->num_redirects = 0; + prog->is_stopped = 0; + prog->family = job; +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS + prog->redirects = NULL; +#endif + + argv_alloced = 5; + prog->argv = xmalloc(sizeof(*prog->argv) * argv_alloced); + prog->argv[0] = job->cmdbuf; + + flag = argc_l = 0; + buf = command; + src = *command_ptr; + while (*src && !(flag & LASH_OPT_DONE)) { + if (quote == *src) { + quote = '\0'; + } else if (quote) { + if (*src == '\\') { + src++; + if (!*src) { + bb_error_msg("character expected after \\"); + free_job(job); + return 1; + } + + /* in shell, "\'" should yield \' */ + if (*src != quote) { + *buf++ = '\\'; + *buf++ = '\\'; + } + } else if (*src == '*' || *src == '?' || *src == '[' || + *src == ']') *buf++ = '\\'; + *buf++ = *src; + } else if (isspace(*src)) { + if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE) { + buf++, argc_l++; + /* +1 here leaves room for the NULL which ends argv */ + if ((argc_l + 1) == argv_alloced) { + argv_alloced += 5; + prog->argv = xrealloc(prog->argv, + sizeof(*prog->argv) * + argv_alloced); + } + prog->argv[argc_l] = buf; + flag ^= LASH_OPT_SAW_QUOTE; + } + } else + switch (*src) { + case '"': + case '\'': + quote = *src; + flag |= LASH_OPT_SAW_QUOTE; + break; + + case '#': /* comment */ + if (*(src-1)== '$') + *buf++ = *src; + else + flag |= LASH_OPT_DONE; + break; + +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS + case '>': /* redirects */ + case '<': + i = prog->num_redirects++; + prog->redirects = xrealloc(prog->redirects, + sizeof(*prog->redirects) * + (i + 1)); + + prog->redirects[i].fd = -1; + if (buf != prog->argv[argc_l]) { + /* the stuff before this character may be the file number + being redirected */ + prog->redirects[i].fd = + strtol(prog->argv[argc_l], &chptr, 10); + + if (*chptr && *prog->argv[argc_l]) { + buf++, argc_l++; + prog->argv[argc_l] = buf; + } + } + + if (prog->redirects[i].fd == -1) { + if (*src == '>') + prog->redirects[i].fd = 1; + else + prog->redirects[i].fd = 0; + } + + if (*src++ == '>') { + if (*src == '>') + prog->redirects[i].type = + REDIRECT_APPEND, src++; + else + prog->redirects[i].type = REDIRECT_OVERWRITE; + } else { + prog->redirects[i].type = REDIRECT_INPUT; + } + + /* This isn't POSIX sh compliant. Oh well. */ + chptr = src; + chptr = skip_whitespace(chptr); + + if (!*chptr) { + bb_error_msg("file name expected after %c", *(src-1)); + free_job(job); + job->num_progs=0; + return 1; + } + + prog->redirects[i].filename = buf; + while (*chptr && !isspace(*chptr)) + *buf++ = *chptr++; + + src = chptr - 1; /* we src++ later */ + prog->argv[argc_l] = ++buf; + break; + + case '|': /* pipe */ + /* finish this command */ + if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE) + argc_l++; + if (!argc_l) { + goto empty_command_in_pipe; + } + prog->argv[argc_l] = NULL; + + /* and start the next */ + job->num_progs++; + job->progs = xrealloc(job->progs, + sizeof(*job->progs) * job->num_progs); + prog = job->progs + (job->num_progs - 1); + prog->num_redirects = 0; + prog->redirects = NULL; + prog->is_stopped = 0; + prog->family = job; + argc_l = 0; + + argv_alloced = 5; + prog->argv = xmalloc(sizeof(*prog->argv) * argv_alloced); + prog->argv[0] = ++buf; + + src++; + src = skip_whitespace(src); + + if (!*src) { +empty_command_in_pipe: + bb_error_msg("empty command in pipe"); + free_job(job); + job->num_progs=0; + return 1; + } + src--; /* we'll ++ it at the end of the loop */ + + break; +#endif + +#ifdef CONFIG_LASH_JOB_CONTROL + case '&': /* background */ + *inbg = 1; + /* fallthrough */ +#endif + case ';': /* multiple commands */ + flag |= LASH_OPT_DONE; + return_command = *command_ptr + (src - *command_ptr) + 1; + break; + + case '\\': + src++; + if (!*src) { + bb_error_msg("character expected after \\"); + free_job(job); + return 1; + } + if (*src == '*' || *src == '[' || *src == ']' + || *src == '?') *buf++ = '\\'; + /* fallthrough */ + default: + *buf++ = *src; + } + + src++; + } + + if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE) { + argc_l++; + } + if (!argc_l) { + free_job(job); + return 0; + } + prog->argv[argc_l] = NULL; + + if (!return_command) { + job->text = xstrdup(*command_ptr); + } else { + /* This leaves any trailing spaces, which is a bit sloppy */ + job->text = xstrndup(*command_ptr, return_command - *command_ptr); + } + + *command_ptr = return_command; + + return 0; +} + +/* Run the child_prog, no matter what kind of command it uses. + */ +static int pseudo_exec(struct child_prog *child) +{ + struct built_in_command *x; + + /* Check if the command matches any of the non-forking builtins. + * Depending on context, this might be redundant. But it's + * easier to waste a few CPU cycles than it is to figure out + * if this is one of those cases. + */ + for (x = bltins; x->cmd; x++) { + if (strcmp(child->argv[0], x->cmd) == 0 ) { + _exit(x->function(child)); + } + } + + /* Check if the command matches any of the forking builtins. */ + for (x = bltins_forking; x->cmd; x++) { + if (strcmp(child->argv[0], x->cmd) == 0) { + applet_name=x->cmd; + _exit (x->function(child)); + } + } + + /* Check if the command matches any busybox internal + * commands ("applets") here. Following discussions from + * November 2000 on busybox@busybox.net, don't use + * bb_get_last_path_component(). This way explicit (with + * slashes) filenames will never be interpreted as an + * applet, just like with builtins. This way the user can + * override an applet with an explicit filename reference. + * The only downside to this change is that an explicit + * /bin/foo invocation will fork and exec /bin/foo, even if + * /bin/foo is a symlink to busybox. + */ + + if (ENABLE_FEATURE_SH_STANDALONE_SHELL) { + char **argv_l = child->argv; + int argc_l; + + for(argc_l=0; *argv_l; argv_l++, argc_l++); + optind = 1; + run_applet_by_name(child->argv[0], argc_l, child->argv); + } + + execvp(child->argv[0], child->argv); + + /* Do not use bb_perror_msg_and_die() here, since we must not + * call exit() but should call _exit() instead */ + bb_perror_msg("%s", child->argv[0]); + _exit(EXIT_FAILURE); +} + +static void insert_job(struct job *newjob, int inbg) +{ + struct job *thejob; + struct jobset *j_list=newjob->job_list; + + /* find the ID for thejob to use */ + newjob->jobid = 1; + for (thejob = j_list->head; thejob; thejob = thejob->next) + if (thejob->jobid >= newjob->jobid) + newjob->jobid = thejob->jobid + 1; + + /* add thejob to the list of running jobs */ + if (!j_list->head) { + thejob = j_list->head = xmalloc(sizeof(*thejob)); + } else { + for (thejob = j_list->head; thejob->next; thejob = thejob->next) /* nothing */; + thejob->next = xmalloc(sizeof(*thejob)); + thejob = thejob->next; + } + + *thejob = *newjob; /* physically copy the struct job */ + thejob->next = NULL; + thejob->running_progs = thejob->num_progs; + thejob->stopped_progs = 0; + +#ifdef CONFIG_LASH_JOB_CONTROL + if (inbg) { + /* we don't wait for background thejobs to return -- append it + to the list of backgrounded thejobs and leave it alone */ + printf("[%d] %d\n", thejob->jobid, + newjob->progs[newjob->num_progs - 1].pid); + last_jobid = newjob->jobid; + last_bg_pid=newjob->progs[newjob->num_progs - 1].pid; + } else { + newjob->job_list->fg = thejob; + + /* move the new process group into the foreground */ + /* Ignore errors since child could have already exited */ + tcsetpgrp(shell_terminal, newjob->pgrp); + } +#endif +} + +static int run_command(struct job *newjob, int inbg, int outpipe[2]) +{ + /* struct job *thejob; */ + int i; + int nextin, nextout; + int pipefds[2]; /* pipefd[0] is for reading */ + struct built_in_command *x; + struct child_prog *child; + + nextin = 0, nextout = 1; + for (i = 0; i < newjob->num_progs; i++) { + child = & (newjob->progs[i]); + + if ((i + 1) < newjob->num_progs) { + if (pipe(pipefds)<0) bb_perror_msg_and_die("pipe"); + nextout = pipefds[1]; + } else { + if (outpipe[1]!=-1) { + nextout = outpipe[1]; + } else { + nextout = 1; + } + } + + + /* Check if the command matches any non-forking builtins, + * but only if this is a simple command. + * Non-forking builtins within pipes have to fork anyway, + * and are handled in pseudo_exec. "echo foo | read bar" + * is doomed to failure, and doesn't work on bash, either. + */ + if (newjob->num_progs == 1) { + /* Check if the command sets an environment variable. */ + if (strchr(child->argv[0], '=') != NULL) { + child->argv[1] = child->argv[0]; + return builtin_export(child); + } + + for (x = bltins; x->cmd; x++) { + if (strcmp(child->argv[0], x->cmd) == 0 ) { + int rcode; + int squirrel[] = {-1, -1, -1}; + setup_redirects(child, squirrel); + rcode = x->function(child); + restore_redirects(squirrel); + return rcode; + } + } + } + +#if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__) + if (!(child->pid = fork())) +#else + if (!(child->pid = vfork())) +#endif + { + /* Set the handling for job control signals back to the default. */ + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + signal(SIGTTIN, SIG_DFL); + signal(SIGTTOU, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + /* Close all open filehandles. */ + while(close_me_list) close((long)llist_pop(&close_me_list)); + + if (outpipe[1]!=-1) { + close(outpipe[0]); + } + if (nextin != 0) { + dup2(nextin, 0); + close(nextin); + } + + if (nextout != 1) { + dup2(nextout, 1); + dup2(nextout, 2); /* Really? */ + close(nextout); + close(pipefds[0]); + } + + /* explicit redirects override pipes */ + setup_redirects(child,NULL); + + pseudo_exec(child); + } + if (outpipe[1]!=-1) { + close(outpipe[1]); + } + + /* put our child in the process group whose leader is the + first process in this pipe */ + setpgid(child->pid, newjob->progs[0].pid); + if (nextin != 0) + close(nextin); + if (nextout != 1) + close(nextout); + + /* If there isn't another process, nextin is garbage + but it doesn't matter */ + nextin = pipefds[0]; + } + + newjob->pgrp = newjob->progs[0].pid; + + insert_job(newjob, inbg); + + return 0; +} + +static int busy_loop(FILE * input) +{ + char *command; + char *next_command = NULL; + struct job newjob; + int i; + int inbg = 0; + int status; +#ifdef CONFIG_LASH_JOB_CONTROL + pid_t parent_pgrp; + /* save current owner of TTY so we can restore it on exit */ + parent_pgrp = tcgetpgrp(shell_terminal); +#endif + newjob.job_list = &job_list; + newjob.job_context = DEFAULT_CONTEXT; + + command = xzalloc(BUFSIZ); + + while (1) { + if (!job_list.fg) { + /* no job is in the foreground */ + + /* see if any background processes have exited */ + checkjobs(&job_list); + + if (!next_command) { + if (get_command(input, command)) + break; + next_command = command; + } + + if (! expand_arguments(next_command)) { + free(command); + command = xzalloc(BUFSIZ); + next_command = NULL; + continue; + } + + if (!parse_command(&next_command, &newjob, &inbg) && + newjob.num_progs) { + int pipefds[2] = {-1,-1}; + debug_printf( "job=%p fed to run_command by busy_loop()'\n", + &newjob); + run_command(&newjob, inbg, pipefds); + } + else { + free(command); + command = (char *) xzalloc(BUFSIZ); + next_command = NULL; + } + } else { + /* a job is running in the foreground; wait for it */ + i = 0; + while (!job_list.fg->progs[i].pid || + job_list.fg->progs[i].is_stopped == 1) i++; + + if (waitpid(job_list.fg->progs[i].pid, &status, WUNTRACED)<0) { + if (errno != ECHILD) { + bb_perror_msg_and_die("waitpid(%d)",job_list.fg->progs[i].pid); + } + } + + if (WIFEXITED(status) || WIFSIGNALED(status)) { + /* the child exited */ + job_list.fg->running_progs--; + job_list.fg->progs[i].pid = 0; + + last_return_code=WEXITSTATUS(status); + + if (!job_list.fg->running_progs) { + /* child exited */ + remove_job(&job_list, job_list.fg); + job_list.fg = NULL; + } + } +#ifdef CONFIG_LASH_JOB_CONTROL + else { + /* the child was stopped */ + job_list.fg->stopped_progs++; + job_list.fg->progs[i].is_stopped = 1; + + if (job_list.fg->stopped_progs == job_list.fg->running_progs) { + printf("\n" JOB_STATUS_FORMAT, job_list.fg->jobid, + "Stopped", job_list.fg->text); + job_list.fg = NULL; + } + } + + if (!job_list.fg) { + /* move the shell to the foreground */ + /* suppress messages when run from /linuxrc mag@sysgo.de */ + if (tcsetpgrp(shell_terminal, getpgrp()) && errno != ENOTTY) + bb_perror_msg("tcsetpgrp"); + } +#endif + } + } + free(command); + +#ifdef CONFIG_LASH_JOB_CONTROL + /* return controlling TTY back to parent process group before exiting */ + if (tcsetpgrp(shell_terminal, parent_pgrp) && errno != ENOTTY) + bb_perror_msg("tcsetpgrp"); +#endif + + /* return exit status if called with "-c" */ + if (input == NULL && WIFEXITED(status)) + return WEXITSTATUS(status); + + return 0; +} + +#ifdef CONFIG_FEATURE_CLEAN_UP +static void free_memory(void) +{ + if (cwd && cwd!=bb_msg_unknown) { + free((char*)cwd); + } + if (local_pending_command) + free(local_pending_command); + + if (job_list.fg && !job_list.fg->running_progs) { + remove_job(&job_list, job_list.fg); + } +} +#else +void free_memory(void); +#endif + +#ifdef CONFIG_LASH_JOB_CONTROL +/* Make sure we have a controlling tty. If we get started under a job + * aware app (like bash for example), make sure we are now in charge so + * we don't fight over who gets the foreground */ +static void setup_job_control(void) +{ + int status; + pid_t shell_pgrp; + + /* Loop until we are in the foreground. */ + while ((status = tcgetpgrp (shell_terminal)) >= 0) { + if (status == (shell_pgrp = getpgrp ())) { + break; + } + kill (- shell_pgrp, SIGTTIN); + } + + /* Ignore interactive and job-control signals. */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + + /* Put ourselves in our own process group. */ + setsid(); + shell_pgrp = getpid (); + setpgid(shell_pgrp, shell_pgrp); + + /* Grab control of the terminal. */ + tcsetpgrp(shell_terminal, shell_pgrp); +} +#else +static inline void setup_job_control(void) +{ +} +#endif + +int lash_main(int argc_l, char **argv_l) +{ + unsigned opt; + FILE *input = stdin; + argc = argc_l; + argv = argv_l; + + /* These variables need re-initializing when recursing */ + last_jobid = 0; + close_me_list = NULL; + job_list.head = NULL; + job_list.fg = NULL; + last_return_code=1; + + if (argv[0] && argv[0][0] == '-') { + FILE *prof_input; + prof_input = fopen("/etc/profile", "r"); + if (prof_input) { + llist_add_to(&close_me_list, (void *)(long)fileno(prof_input)); + /* Now run the file */ + busy_loop(prof_input); + fclose_if_not_stdin(prof_input); + llist_pop(&close_me_list); + } + } + + opt = getopt32(argc_l, argv_l, "+ic:", &local_pending_command); +#define LASH_OPT_i (1<<0) +#define LASH_OPT_c (1<<2) + if (opt & LASH_OPT_c) { + input = NULL; + optind++; + argv += optind; + } + /* A shell is interactive if the `-i' flag was given, or if all of + * the following conditions are met: + * no -c command + * no arguments remaining or the -s flag given + * standard input is a terminal + * standard output is a terminal + * Refer to Posix.2, the description of the `sh' utility. */ + if (argv[optind]==NULL && input==stdin && + isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) + { + opt |= LASH_OPT_i; + } + setup_job_control(); + if (opt & LASH_OPT_i) { + /* Looks like they want an interactive shell */ + if (!ENABLE_FEATURE_SH_EXTRA_QUIET) { + printf("\n\n%s Built-in shell (lash)\n" + "Enter 'help' for a list of built-in commands.\n\n", + BB_BANNER); + } + } else if (!local_pending_command && argv[optind]) { + //printf( "optind=%d argv[optind]='%s'\n", optind, argv[optind]); + input = xfopen(argv[optind], "r"); + /* be lazy, never mark this closed */ + llist_add_to(&close_me_list, (void *)(long)fileno(input)); + } + + /* initialize the cwd -- this is never freed...*/ + cwd = xgetcwd(0); + if (!cwd) + cwd = bb_msg_unknown; + + if (ENABLE_FEATURE_CLEAN_UP) atexit(free_memory); + + if (ENABLE_FEATURE_COMMAND_EDITING) cmdedit_set_initial_prompt(); + else PS1 = NULL; + + return (busy_loop(input)); +} diff --git a/shell/msh.c b/shell/msh.c new file mode 100644 index 000000000..492d3cf43 --- /dev/null +++ b/shell/msh.c @@ -0,0 +1,5250 @@ +/* vi: set sw=4 ts=4: */ +/* + * Minix shell port for busybox + * + * This version of the Minix shell was adapted for use in busybox + * by Erik Andersen <andersen@codepoet.org> + * + * - backtick expansion did not work properly + * Jonas Holmberg <jonas.holmberg@axis.com> + * Robert Schwebel <r.schwebel@pengutronix.de> + * Erik Andersen <andersen@codepoet.org> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <setjmp.h> +#include <sys/times.h> + +#include "cmdedit.h" + +/*#define MSHDEBUG 1*/ + +#ifdef MSHDEBUG +int mshdbg = MSHDEBUG; + +#define DBGPRINTF(x) if(mshdbg>0)printf x +#define DBGPRINTF0(x) if(mshdbg>0)printf x +#define DBGPRINTF1(x) if(mshdbg>1)printf x +#define DBGPRINTF2(x) if(mshdbg>2)printf x +#define DBGPRINTF3(x) if(mshdbg>3)printf x +#define DBGPRINTF4(x) if(mshdbg>4)printf x +#define DBGPRINTF5(x) if(mshdbg>5)printf x +#define DBGPRINTF6(x) if(mshdbg>6)printf x +#define DBGPRINTF7(x) if(mshdbg>7)printf x +#define DBGPRINTF8(x) if(mshdbg>8)printf x +#define DBGPRINTF9(x) if(mshdbg>9)printf x + +int mshdbg_rc = 0; + +#define RCPRINTF(x) if(mshdbg_rc)printf x + +#else + +#define DBGPRINTF(x) +#define DBGPRINTF0(x) +#define DBGPRINTF1(x) +#define DBGPRINTF2(x) +#define DBGPRINTF3(x) +#define DBGPRINTF4(x) +#define DBGPRINTF5(x) +#define DBGPRINTF6(x) +#define DBGPRINTF7(x) +#define DBGPRINTF8(x) +#define DBGPRINTF9(x) + +#define RCPRINTF(x) + +#endif /* MSHDEBUG */ + + +#ifdef CONFIG_FEATURE_SH_FANCY_PROMPT +# define DEFAULT_ROOT_PROMPT "\\u:\\w> " +# define DEFAULT_USER_PROMPT "\\u:\\w$ " +#else +# define DEFAULT_ROOT_PROMPT "# " +# define DEFAULT_USER_PROMPT "$ " +#endif + + +/* -------- sh.h -------- */ +/* + * shell + */ + +#define LINELIM 2100 +#define NPUSH 8 /* limit to input nesting */ + +#undef NOFILE +#define NOFILE 20 /* Number of open files */ +#define NUFILE 10 /* Number of user-accessible files */ +#define FDBASE 10 /* First file usable by Shell */ + +/* + * values returned by wait + */ +#define WAITSIG(s) ((s)&0177) +#define WAITVAL(s) (((s)>>8)&0377) +#define WAITCORE(s) (((s)&0200)!=0) + +/* + * library and system definitions + */ +typedef void xint; /* base type of jmp_buf, for not broken compilers */ + +/* + * shell components + */ + +#define QUOTE 0200 + +#define NOBLOCK ((struct op *)NULL) +#define NOWORD ((char *)NULL) +#define NOWORDS ((char **)NULL) +#define NOPIPE ((int *)NULL) + +/* + * Description of a command or an operation on commands. + * Might eventually use a union. + */ +struct op { + int type; /* operation type, see below */ + char **words; /* arguments to a command */ + struct ioword **ioact; /* IO actions (eg, < > >>) */ + struct op *left; + struct op *right; + char *str; /* identifier for case and for */ +}; + +#define TCOM 1 /* command */ +#define TPAREN 2 /* (c-list) */ +#define TPIPE 3 /* a | b */ +#define TLIST 4 /* a [&;] b */ +#define TOR 5 /* || */ +#define TAND 6 /* && */ +#define TFOR 7 +#define TDO 8 +#define TCASE 9 +#define TIF 10 +#define TWHILE 11 +#define TUNTIL 12 +#define TELIF 13 +#define TPAT 14 /* pattern in case */ +#define TBRACE 15 /* {c-list} */ +#define TASYNC 16 /* c & */ +/* Added to support "." file expansion */ +#define TDOT 17 + +/* Strings for names to make debug easier */ +#ifdef MSHDEBUG +static char *T_CMD_NAMES[] = { + "PLACEHOLDER", + "TCOM", + "TPAREN", + "TPIPE", + "TLIST", + "TOR", + "TAND", + "TFOR", + "TDO", + "TCASE", + "TIF", + "TWHILE", + "TUNTIL", + "TELIF", + "TPAT", + "TBRACE", + "TASYNC", + "TDOT", +}; +#endif + +/* + * actions determining the environment of a process + */ +#define BIT(i) (1<<(i)) +#define FEXEC BIT(0) /* execute without forking */ + +#define AREASIZE (90000) + +/* + * flags to control evaluation of words + */ +#define DOSUB 1 /* interpret $, `, and quotes */ +#define DOBLANK 2 /* perform blank interpretation */ +#define DOGLOB 4 /* interpret [?* */ +#define DOKEY 8 /* move words with `=' to 2nd arg. list */ +#define DOTRIM 16 /* trim resulting string */ + +#define DOALL (DOSUB|DOBLANK|DOGLOB|DOKEY|DOTRIM) + + +/* PROTOTYPES */ +static int newfile(char *s); +static char *findeq(char *cp); +static char *cclass(char *p, int sub); +static void initarea(void); +extern int msh_main(int argc, char **argv); + + +struct brkcon { + jmp_buf brkpt; + struct brkcon *nextlev; +}; + + +/* + * redirection + */ +struct ioword { + short io_unit; /* unit affected */ + short io_flag; /* action (below) */ + char *io_name; /* file name */ +}; + +#define IOREAD 1 /* < */ +#define IOHERE 2 /* << (here file) */ +#define IOWRITE 4 /* > */ +#define IOCAT 8 /* >> */ +#define IOXHERE 16 /* ${}, ` in << */ +#define IODUP 32 /* >&digit */ +#define IOCLOSE 64 /* >&- */ + +#define IODEFAULT (-1) /* token for default IO unit */ + + + +/* + * parsing & execution environment + */ +static struct env { + char *linep; + struct io *iobase; + struct io *iop; + xint *errpt; /* void * */ + int iofd; + struct env *oenv; +} e; + +/* + * flags: + * -e: quit on error + * -k: look for name=value everywhere on command line + * -n: no execution + * -t: exit after reading and executing one command + * -v: echo as read + * -x: trace + * -u: unset variables net diagnostic + */ +static char flags['z' - 'a' + 1]; +/* this looks weird, but is OK ... we index flag with 'a'...'z' */ +static char *flag = flags - 'a'; + +static char *null; /* null value for variable */ +static int intr; /* interrupt pending */ + +static char *trap[_NSIG + 1]; +static char ourtrap[_NSIG + 1]; +static int trapset; /* trap pending */ + +static int heedint; /* heed interrupt signals */ + +static int yynerrs; /* yacc */ + +static char line[LINELIM]; +static char *elinep; + + +/* + * other functions + */ +static int (*inbuilt(char *s)) (struct op *); + +static char *rexecve(char *c, char **v, char **envp); +static char *space(int n); +static char *strsave(char *s, int a); +static char *evalstr(char *cp, int f); +static char *putn(int n); +static char *unquote(char *as); +static struct var *lookup(char *n); +static int rlookup(char *n); +static struct wdblock *glob(char *cp, struct wdblock *wb); +static int my_getc(int ec); +static int subgetc(char ec, int quoted); +static char **makenv(int all, struct wdblock *wb); +static char **eval(char **ap, int f); +static int setstatus(int s); +static int waitfor(int lastpid, int canintr); + +static void onintr(int s); /* SIGINT handler */ + +static int newenv(int f); +static void quitenv(void); +static void err(char *s); +static int anys(char *s1, char *s2); +static int any(int c, char *s); +static void next(int f); +static void setdash(void); +static void onecommand(void); +static void runtrap(int i); +static int gmatch(char *s, char *p); + + +/* + * error handling + */ +static void leave(void); /* abort shell (or fail in subshell) */ +static void fail(void); /* fail but return to process next command */ +static void warn(char *s); +static void sig(int i); /* default signal handler */ + + + +/* -------- area stuff -------- */ + +#define REGSIZE sizeof(struct region) +#define GROWBY (256) +/* #define SHRINKBY (64) */ +#undef SHRINKBY +#define FREE (32767) +#define BUSY (0) +#define ALIGN (sizeof(int)-1) + + +struct region { + struct region *next; + int area; +}; + + + +/* -------- grammar stuff -------- */ +typedef union { + char *cp; + char **wp; + int i; + struct op *o; +} YYSTYPE; + +#define WORD 256 +#define LOGAND 257 +#define LOGOR 258 +#define BREAK 259 +#define IF 260 +#define THEN 261 +#define ELSE 262 +#define ELIF 263 +#define FI 264 +#define CASE 265 +#define ESAC 266 +#define FOR 267 +#define WHILE 268 +#define UNTIL 269 +#define DO 270 +#define DONE 271 +#define IN 272 +/* Added for "." file expansion */ +#define DOT 273 + +#define YYERRCODE 300 + +/* flags to yylex */ +#define CONTIN 01 /* skip new lines to complete command */ + +#define SYNTAXERR zzerr() + +static struct op *pipeline(int cf); +static struct op *andor(void); +static struct op *c_list(void); +static int synio(int cf); +static void musthave(int c, int cf); +static struct op *simple(void); +static struct op *nested(int type, int mark); +static struct op *command(int cf); +static struct op *dogroup(int onlydone); +static struct op *thenpart(void); +static struct op *elsepart(void); +static struct op *caselist(void); +static struct op *casepart(void); +static char **pattern(void); +static char **wordlist(void); +static struct op *list(struct op *t1, struct op *t2); +static struct op *block(int type, struct op *t1, struct op *t2, char **wp); +static struct op *newtp(void); +static struct op *namelist(struct op *t); +static char **copyw(void); +static void word(char *cp); +static struct ioword **copyio(void); +static struct ioword *io(int u, int f, char *cp); +static void zzerr(void); +static void yyerror(char *s); +static int yylex(int cf); +static int collect(int c, int c1); +static int dual(int c); +static void diag(int ec); +static char *tree(unsigned size); + +/* -------- var.h -------- */ + +struct var { + char *value; + char *name; + struct var *next; + char status; +}; + +#define COPYV 1 /* flag to setval, suggesting copy */ +#define RONLY 01 /* variable is read-only */ +#define EXPORT 02 /* variable is to be exported */ +#define GETCELL 04 /* name & value space was got with getcell */ + +static int yyparse(void); +static struct var *lookup(char *n); +static void setval(struct var *vp, char *val); +static void nameval(struct var *vp, char *val, char *name); +static void export(struct var *vp); +static void ronly(struct var *vp); +static int isassign(char *s); +static int checkname(char *cp); +static int assign(char *s, int cf); +static void putvlist(int f, int out); +static int eqname(char *n1, char *n2); + +static int execute(struct op *t, int *pin, int *pout, int act); + + +/* -------- io.h -------- */ +/* io buffer */ +struct iobuf { + unsigned id; /* buffer id */ + char buf[512]; /* buffer */ + char *bufp; /* pointer into buffer */ + char *ebufp; /* pointer to end of buffer */ +}; + +/* possible arguments to an IO function */ +struct ioarg { + char *aword; + char **awordlist; + int afile; /* file descriptor */ + unsigned afid; /* buffer id */ + long afpos; /* file position */ + struct iobuf *afbuf; /* buffer for this file */ +}; + +//static struct ioarg ioargstack[NPUSH]; +#define AFID_NOBUF (~0) +#define AFID_ID 0 + +/* an input generator's state */ +struct io { + int (*iofn) (struct ioarg *, struct io *); + struct ioarg *argp; + int peekc; + char prev; /* previous character read by readc() */ + char nlcount; /* for `'s */ + char xchar; /* for `'s */ + char task; /* reason for pushed IO */ +}; + +//static struct io iostack[NPUSH]; +#define XOTHER 0 /* none of the below */ +#define XDOLL 1 /* expanding ${} */ +#define XGRAVE 2 /* expanding `'s */ +#define XIO 3 /* file IO */ + +/* in substitution */ +#define INSUB() (e.iop->task == XGRAVE || e.iop->task == XDOLL) + + +/* + * input generators for IO structure + */ +static int nlchar(struct ioarg *ap); +static int strchar(struct ioarg *ap); +static int qstrchar(struct ioarg *ap); +static int filechar(struct ioarg *ap); +static int herechar(struct ioarg *ap); +static int linechar(struct ioarg *ap); +static int gravechar(struct ioarg *ap, struct io *iop); +static int qgravechar(struct ioarg *ap, struct io *iop); +static int dolchar(struct ioarg *ap); +static int wdchar(struct ioarg *ap); +static void scraphere(void); +static void freehere(int area); +static void gethere(void); +static void markhere(char *s, struct ioword *iop); +static int herein(char *hname, int xdoll); +static int run(struct ioarg *argp, int (*f) (struct ioarg *)); + + +/* + * IO functions + */ +static int eofc(void); +static int readc(void); +static void unget(int c); +static void ioecho(char c); +static void prs(const char *s); +static void prn(unsigned u); +static void closef(int i); +static void closeall(void); + + +/* + * IO control + */ +static void pushio(struct ioarg *argp, int (*f) (struct ioarg *)); +static int remap(int fd); +static int openpipe(int *pv); +static void closepipe(int *pv); +static struct io *setbase(struct io *ip); + +#define PUSHIO(what,arg,gen) ((temparg.what = (arg)),pushio(&temparg,(gen))) +#define RUN(what,arg,gen) ((temparg.what = (arg)), run(&temparg,(gen))) + +/* -------- word.h -------- */ + +#define NSTART 16 /* default number of words to allow for initially */ + +struct wdblock { + short w_bsize; + short w_nword; + /* bounds are arbitrary */ + char *w_words[1]; +}; + +static struct wdblock *addword(char *wd, struct wdblock *wb); +static struct wdblock *newword(int nw); +static char **getwords(struct wdblock *wb); + +/* -------- area.h -------- */ + +/* + * storage allocation + */ +static char *getcell(unsigned nbytes); +static void garbage(void); +static void setarea(char *cp, int a); +static int getarea(char *cp); +static void freearea(int a); +static void freecell(char *cp); +static int areanum; /* current allocation area */ + +#define NEW(type) (type *)getcell(sizeof(type)) +#define DELETE(obj) freecell((char *)obj) + + +/* -------- misc stuff -------- */ + +static int forkexec(struct op *t, int *pin, int *pout, int act, char **wp); +static int iosetup(struct ioword *iop, int pipein, int pipeout); +static void echo(char **wp); +static struct op **find1case(struct op *t, char *w); +static struct op *findcase(struct op *t, char *w); +static void brkset(struct brkcon *bc); +static int dolabel(struct op *t); +static int dohelp(struct op *t); +static int dochdir(struct op *t); +static int doshift(struct op *t); +static int dologin(struct op *t); +static int doumask(struct op *t); +static int doexec(struct op *t); +static int dodot(struct op *t); +static int dowait(struct op *t); +static int doread(struct op *t); +static int doeval(struct op *t); +static int dotrap(struct op *t); +static int getsig(char *s); +static void setsig(int n, sighandler_t f); +static int getn(char *as); +static int dobreak(struct op *t); +static int docontinue(struct op *t); +static int brkcontin(char *cp, int val); +static int doexit(struct op *t); +static int doexport(struct op *t); +static int doreadonly(struct op *t); +static void rdexp(char **wp, void (*f) (struct var *), int key); +static void badid(char *s); +static int doset(struct op *t); +static void varput(char *s, int out); +static int dotimes(struct op *t); +static int expand(char *cp, struct wdblock **wbp, int f); +static char *blank(int f); +static int dollar(int quoted); +static int grave(int quoted); +static void globname(char *we, char *pp); +static char *generate(char *start1, char *end1, char *middle, char *end); +static int anyspcl(struct wdblock *wb); +static int xstrcmp(char *p1, char *p2); +static void glob0(char *a0, unsigned int a1, int a2, + int (*a3) (char *, char *)); +static void glob1(char *base, char *lim); +static void glob2(char *i, char *j); +static void glob3(char *i, char *j, char *k); +static void readhere(char **name, char *s, int ec); +static void pushio(struct ioarg *argp, int (*f) (struct ioarg *)); +static int xxchar(struct ioarg *ap); + +struct here { + char *h_tag; + int h_dosub; + struct ioword *h_iop; + struct here *h_next; +}; + +static const char * const signame[] = { + "Signal 0", + "Hangup", + (char *) NULL, /* interrupt */ + "Quit", + "Illegal instruction", + "Trace/BPT trap", + "Abort", + "Bus error", + "Floating Point Exception", + "Killed", + "SIGUSR1", + "SIGSEGV", + "SIGUSR2", + (char *) NULL, /* broken pipe */ + "Alarm clock", + "Terminated", +}; + +#define NSIGNAL (sizeof(signame)/sizeof(signame[0])) + +struct res { + const char *r_name; + int r_val; +}; +static const struct res restab[] = { + {"for", FOR}, + {"case", CASE}, + {"esac", ESAC}, + {"while", WHILE}, + {"do", DO}, + {"done", DONE}, + {"if", IF}, + {"in", IN}, + {"then", THEN}, + {"else", ELSE}, + {"elif", ELIF}, + {"until", UNTIL}, + {"fi", FI}, + {";;", BREAK}, + {"||", LOGOR}, + {"&&", LOGAND}, + {"{", '{'}, + {"}", '}'}, + {".", DOT}, + {0, 0}, +}; + + +struct builtincmd { + const char *name; + int (*builtinfunc) (struct op * t); +}; +static const struct builtincmd builtincmds[] = { + {".", dodot}, + {":", dolabel}, + {"break", dobreak}, + {"cd", dochdir}, + {"continue", docontinue}, + {"eval", doeval}, + {"exec", doexec}, + {"exit", doexit}, + {"export", doexport}, + {"help", dohelp}, + {"login", dologin}, + {"newgrp", dologin}, + {"read", doread}, + {"readonly", doreadonly}, + {"set", doset}, + {"shift", doshift}, + {"times", dotimes}, + {"trap", dotrap}, + {"umask", doumask}, + {"wait", dowait}, + {0, 0} +}; + +static struct op *scantree(struct op *); +static struct op *dowholefile(int, int); + +/* Globals */ +extern char **environ; /* environment pointer */ + +static char **dolv; +static int dolc; +static int exstat; +static char gflg; +static int interactive; /* Is this an interactive shell */ +static int execflg; +static int multiline; /* \n changed to ; */ +static struct op *outtree; /* result from parser */ +static xint *failpt; +static xint *errpt; +static struct brkcon *brklist; +static int isbreak; +static struct wdblock *wdlist; +static struct wdblock *iolist; +static char *trap[_NSIG + 1]; +static char ourtrap[_NSIG + 1]; +static int trapset; /* trap pending */ +static int yynerrs; /* yacc */ +static char line[LINELIM]; + +#ifdef MSHDEBUG +static struct var *mshdbg_var; +#endif +static struct var *vlist; /* dictionary */ +static struct var *homedir; /* home directory */ +static struct var *prompt; /* main prompt */ +static struct var *cprompt; /* continuation prompt */ +static struct var *path; /* search path for commands */ +static struct var *shell; /* shell to interpret command files */ +static struct var *ifs; /* field separators */ + +static int areanum; /* current allocation area */ +static int intr; +static int inparse; +static char *null = ""; +static int heedint = 1; +static void (*qflag) (int) = SIG_IGN; +static int startl; +static int peeksym; +static int nlseen; +static int iounit = IODEFAULT; +static YYSTYPE yylval; +static char *elinep = line + sizeof(line) - 5; + +static struct ioarg temparg = { 0, 0, 0, AFID_NOBUF, 0 }; /* temporary for PUSHIO */ +static struct ioarg ioargstack[NPUSH]; +static struct io iostack[NPUSH]; +static struct iobuf sharedbuf = { AFID_NOBUF }; +static struct iobuf mainbuf = { AFID_NOBUF }; +static unsigned bufid = AFID_ID; /* buffer id counter */ + +static struct here *inhere; /* list of hear docs while parsing */ +static struct here *acthere; /* list of active here documents */ +static struct region *areabot; /* bottom of area */ +static struct region *areatop; /* top of area */ +static struct region *areanxt; /* starting point of scan */ +static void *brktop; +static void *brkaddr; + +static struct env e = { + line, /* linep: char ptr */ + iostack, /* iobase: struct io ptr */ + iostack - 1, /* iop: struct io ptr */ + (xint *) NULL, /* errpt: void ptr for errors? */ + FDBASE, /* iofd: file desc */ + (struct env *) NULL /* oenv: struct env ptr */ +}; + +#ifdef MSHDEBUG +void print_t(struct op *t); +void print_t(struct op *t) +{ + DBGPRINTF(("T: t=%p, type %s, words=%p, IOword=%p\n", t, + T_CMD_NAMES[t->type], t->words, t->ioact)); + + if (t->words) { + DBGPRINTF(("T: W1: %s", t->words[0])); + } + + return; +} + +void print_tree(struct op *head); +void print_tree(struct op *head) +{ + if (head == NULL) { + DBGPRINTF(("PRINT_TREE: no tree\n")); + return; + } + + DBGPRINTF(("NODE: %p, left %p, right %p\n", head, head->left, + head->right)); + + if (head->left) + print_tree(head->left); + + if (head->right) + print_tree(head->right); + + return; +} +#endif /* MSHDEBUG */ + + +#ifdef CONFIG_FEATURE_COMMAND_EDITING +static char *current_prompt; +#endif + +/* -------- sh.c -------- */ +/* + * shell + */ + + +int msh_main(int argc, char **argv) +{ + int f; + char *s; + int cflag; + char *name, **ap; + int (*iof) (struct ioarg *); + + DBGPRINTF(("MSH_MAIN: argc %d, environ %p\n", argc, environ)); + + initarea(); + if ((ap = environ) != NULL) { + while (*ap) + assign(*ap++, !COPYV); + for (ap = environ; *ap;) + export(lookup(*ap++)); + } + closeall(); + areanum = 1; + + shell = lookup("SHELL"); + if (shell->value == null) + setval(shell, (char *)DEFAULT_SHELL); + export(shell); + + homedir = lookup("HOME"); + if (homedir->value == null) + setval(homedir, "/"); + export(homedir); + + setval(lookup("$"), putn(getpid())); + + path = lookup("PATH"); + if (path->value == null) { + if (geteuid() == 0) + setval(path, "/sbin:/bin:/usr/sbin:/usr/bin"); + else + setval(path, "/bin:/usr/bin"); + } + export(path); + + ifs = lookup("IFS"); + if (ifs->value == null) + setval(ifs, " \t\n"); + +#ifdef MSHDEBUG + mshdbg_var = lookup("MSHDEBUG"); + if (mshdbg_var->value == null) + setval(mshdbg_var, "0"); +#endif + + prompt = lookup("PS1"); +#ifdef CONFIG_FEATURE_SH_FANCY_PROMPT + if (prompt->value == null) +#endif + setval(prompt, DEFAULT_USER_PROMPT); + if (geteuid() == 0) { + setval(prompt, DEFAULT_ROOT_PROMPT); + prompt->status &= ~EXPORT; + } + cprompt = lookup("PS2"); +#ifdef CONFIG_FEATURE_SH_FANCY_PROMPT + if (cprompt->value == null) +#endif + setval(cprompt, "> "); + + iof = filechar; + cflag = 0; + name = *argv++; + if (--argc >= 1) { + if (argv[0][0] == '-' && argv[0][1] != '\0') { + for (s = argv[0] + 1; *s; s++) + switch (*s) { + case 'c': + prompt->status &= ~EXPORT; + cprompt->status &= ~EXPORT; + setval(prompt, ""); + setval(cprompt, ""); + cflag = 1; + if (--argc > 0) + PUSHIO(aword, *++argv, iof = nlchar); + break; + + case 'q': + qflag = SIG_DFL; + break; + + case 's': + /* standard input */ + break; + + case 't': + prompt->status &= ~EXPORT; + setval(prompt, ""); + iof = linechar; + break; + + case 'i': + interactive++; + default: + if (*s >= 'a' && *s <= 'z') + flag[(int) *s]++; + } + } else { + argv--; + argc++; + } + + if (iof == filechar && --argc > 0) { + setval(prompt, ""); + setval(cprompt, ""); + prompt->status &= ~EXPORT; + cprompt->status &= ~EXPORT; + +/* Shell is non-interactive, activate printf-based debug */ +#ifdef MSHDEBUG + mshdbg = (int) (((char) (mshdbg_var->value[0])) - '0'); + if (mshdbg < 0) + mshdbg = 0; +#endif + DBGPRINTF(("MSH_MAIN: calling newfile()\n")); + + if (newfile(name = *++argv)) + exit(1); /* Exit on error */ + } + } + + setdash(); + + /* This won't be true if PUSHIO has been called, say from newfile() above */ + if (e.iop < iostack) { + PUSHIO(afile, 0, iof); + if (isatty(0) && isatty(1) && !cflag) { + interactive++; +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET +#ifdef MSHDEBUG + printf("\n\n%s Built-in shell (msh with debug)\n", BB_BANNER); +#else + printf("\n\n%s Built-in shell (msh)\n", BB_BANNER); +#endif + printf("Enter 'help' for a list of built-in commands.\n\n"); +#endif + } + } + + signal(SIGQUIT, qflag); + if (name && name[0] == '-') { + interactive++; + if ((f = open(".profile", 0)) >= 0) + next(remap(f)); + if ((f = open("/etc/profile", 0)) >= 0) + next(remap(f)); + } + if (interactive) + signal(SIGTERM, sig); + + if (signal(SIGINT, SIG_IGN) != SIG_IGN) + signal(SIGINT, onintr); + dolv = argv; + dolc = argc; + dolv[0] = name; + if (dolc > 1) { + for (ap = ++argv; --argc > 0;) { + if (assign(*ap = *argv++, !COPYV)) { + dolc--; /* keyword */ + } else { + ap++; + } + } + } + setval(lookup("#"), putn((--dolc < 0) ? (dolc = 0) : dolc)); + + DBGPRINTF(("MSH_MAIN: begin FOR loop, interactive %d, e.iop %p, iostack %p\n", interactive, e.iop, iostack)); + + for (;;) { + if (interactive && e.iop <= iostack) { +#ifdef CONFIG_FEATURE_COMMAND_EDITING + current_prompt = prompt->value; +#else + prs(prompt->value); +#endif + } + onecommand(); + /* Ensure that getenv("PATH") stays current */ + setenv("PATH", path->value, 1); + } + + DBGPRINTF(("MSH_MAIN: returning.\n")); +} + +static void setdash(void) +{ + char *cp; + int c; + char m['z' - 'a' + 1]; + + cp = m; + for (c = 'a'; c <= 'z'; c++) + if (flag[(int) c]) + *cp++ = c; + *cp = 0; + setval(lookup("-"), m); +} + +static int newfile(char *s) +{ + int f; + + DBGPRINTF7(("NEWFILE: opening %s\n", s)); + + if (strcmp(s, "-") != 0) { + DBGPRINTF(("NEWFILE: s is %s\n", s)); + f = open(s, 0); + if (f < 0) { + prs(s); + err(": cannot open"); + return 1; + } + } else + f = 0; + + next(remap(f)); + return 0; +} + + +struct op *scantree(struct op *head) +{ + struct op *dotnode; + + if (head == NULL) + return NULL; + + if (head->left != NULL) { + dotnode = scantree(head->left); + if (dotnode) + return dotnode; + } + + if (head->right != NULL) { + dotnode = scantree(head->right); + if (dotnode) + return dotnode; + } + + if (head->words == NULL) + return NULL; + + DBGPRINTF5(("SCANTREE: checking node %p\n", head)); + + if ((head->type != TDOT) && (strcmp(".", head->words[0]) == 0)) { + DBGPRINTF5(("SCANTREE: dot found in node %p\n", head)); + return head; + } + + return NULL; +} + + +static void onecommand(void) +{ + int i; + jmp_buf m1; + + DBGPRINTF(("ONECOMMAND: enter, outtree=%p\n", outtree)); + + while (e.oenv) + quitenv(); + + areanum = 1; + freehere(areanum); + freearea(areanum); + garbage(); + wdlist = 0; + iolist = 0; + e.errpt = 0; + e.linep = line; + yynerrs = 0; + multiline = 0; + inparse = 1; + intr = 0; + execflg = 0; + + setjmp(failpt = m1); /* Bruce Evans' fix */ + if (setjmp(failpt = m1) || yyparse() || intr) { + + DBGPRINTF(("ONECOMMAND: this is not good.\n")); + + while (e.oenv) + quitenv(); + scraphere(); + if (!interactive && intr) + leave(); + inparse = 0; + intr = 0; + return; + } + + inparse = 0; + brklist = 0; + intr = 0; + execflg = 0; + + if (!flag['n']) { + DBGPRINTF(("ONECOMMAND: calling execute, t=outtree=%p\n", + outtree)); + execute(outtree, NOPIPE, NOPIPE, 0); + } + + if (!interactive && intr) { + execflg = 0; + leave(); + } + + if ((i = trapset) != 0) { + trapset = 0; + runtrap(i); + } +} + +static void fail(void) +{ + longjmp(failpt, 1); + /* NOTREACHED */ +} + +static void leave(void) +{ + DBGPRINTF(("LEAVE: leave called!\n")); + + if (execflg) + fail(); + scraphere(); + freehere(1); + runtrap(0); + _exit(exstat); + /* NOTREACHED */ +} + +static void warn(char *s) +{ + if (*s) { + prs(s); + exstat = -1; + } + prs("\n"); + if (flag['e']) + leave(); +} + +static void err(char *s) +{ + warn(s); + if (flag['n']) + return; + if (!interactive) + leave(); + if (e.errpt) + longjmp(e.errpt, 1); + closeall(); + e.iop = e.iobase = iostack; +} + +static int newenv(int f) +{ + struct env *ep; + + DBGPRINTF(("NEWENV: f=%d (indicates quitenv and return)\n", f)); + + if (f) { + quitenv(); + return 1; + } + + ep = (struct env *) space(sizeof(*ep)); + if (ep == NULL) { + while (e.oenv) + quitenv(); + fail(); + } + *ep = e; + e.oenv = ep; + e.errpt = errpt; + + return 0; +} + +static void quitenv(void) +{ + struct env *ep; + int fd; + + DBGPRINTF(("QUITENV: e.oenv=%p\n", e.oenv)); + + if ((ep = e.oenv) != NULL) { + fd = e.iofd; + e = *ep; + /* should close `'d files */ + DELETE(ep); + while (--fd >= e.iofd) + close(fd); + } +} + +/* + * Is any character from s1 in s2? + */ +static int anys(char *s1, char *s2) +{ + while (*s1) + if (any(*s1++, s2)) + return 1; + return 0; +} + +/* + * Is character c in s? + */ +static int any(int c, char *s) +{ + while (*s) + if (*s++ == c) + return 1; + return 0; +} + +static char *putn(int n) +{ + return itoa(n); +} + +static void next(int f) +{ + PUSHIO(afile, f, filechar); +} + +static void onintr(int s) /* ANSI C requires a parameter */ +{ + signal(SIGINT, onintr); + intr = 1; + if (interactive) { + if (inparse) { + prs("\n"); + fail(); + } + } else if (heedint) { + execflg = 0; + leave(); + } +} + +static char *space(int n) +{ + char *cp; + + if ((cp = getcell(n)) == 0) + err("out of string space"); + return cp; +} + +static char *strsave(char *s, int a) +{ + char *cp, *xp; + + if ((cp = space(strlen(s) + 1)) != NULL) { + setarea((char *) cp, a); + for (xp = cp; (*xp++ = *s++) != '\0';); + return cp; + } + return ""; +} + +/* + * trap handling + */ +static void sig(int i) +{ + trapset = i; + signal(i, sig); +} + +static void runtrap(int i) +{ + char *trapstr; + + if ((trapstr = trap[i]) == NULL) + return; + + if (i == 0) + trap[i] = 0; + + RUN(aword, trapstr, nlchar); +} + +/* -------- var.c -------- */ + +/* + * Find the given name in the dictionary + * and return its value. If the name was + * not previously there, enter it now and + * return a null value. + */ +static struct var *lookup(char *n) +{ + struct var *vp; + char *cp; + int c; + static struct var dummy; + + if (isdigit(*n)) { + dummy.name = n; + for (c = 0; isdigit(*n) && c < 1000; n++) + c = c * 10 + *n - '0'; + dummy.status = RONLY; + dummy.value = c <= dolc ? dolv[c] : null; + return &dummy; + } + for (vp = vlist; vp; vp = vp->next) + if (eqname(vp->name, n)) + return vp; + cp = findeq(n); + vp = (struct var *) space(sizeof(*vp)); + if (vp == 0 || (vp->name = space((int) (cp - n) + 2)) == 0) { + dummy.name = dummy.value = ""; + return &dummy; + } + for (cp = vp->name; (*cp = *n++) && *cp != '='; cp++); + if (*cp == 0) + *cp = '='; + *++cp = 0; + setarea((char *) vp, 0); + setarea((char *) vp->name, 0); + vp->value = null; + vp->next = vlist; + vp->status = GETCELL; + vlist = vp; + return vp; +} + +/* + * give variable at `vp' the value `val'. + */ +static void setval(struct var *vp, char *val) +{ + nameval(vp, val, (char *) NULL); +} + +/* + * if name is not NULL, it must be + * a prefix of the space `val', + * and end with `='. + * this is all so that exporting + * values is reasonably painless. + */ +static void nameval(struct var *vp, char *val, char *name) +{ + char *cp, *xp; + char *nv; + int fl; + + if (vp->status & RONLY) { + for (xp = vp->name; *xp && *xp != '=';) + putc(*xp++, stderr); + err(" is read-only"); + return; + } + fl = 0; + if (name == NULL) { + xp = space(strlen(vp->name) + strlen(val) + 2); + if (xp == 0) + return; + /* make string: name=value */ + setarea((char *) xp, 0); + name = xp; + for (cp = vp->name; (*xp = *cp++) && *xp != '='; xp++); + if (*xp++ == 0) + xp[-1] = '='; + nv = xp; + for (cp = val; (*xp++ = *cp++) != '\0';); + val = nv; + fl = GETCELL; + } + if (vp->status & GETCELL) + freecell(vp->name); /* form new string `name=value' */ + vp->name = name; + vp->value = val; + vp->status |= fl; +} + +static void export(struct var *vp) +{ + vp->status |= EXPORT; +} + +static void ronly(struct var *vp) +{ + if (isalpha(vp->name[0]) || vp->name[0] == '_') /* not an internal symbol */ + vp->status |= RONLY; +} + +static int isassign(char *s) +{ + DBGPRINTF7(("ISASSIGN: enter, s=%s\n", s)); + + if (!isalpha((int) *s) && *s != '_') + return 0; + for (; *s != '='; s++) + if (*s == 0 || (!isalnum(*s) && *s != '_')) + return 0; + + return 1; +} + +static int assign(char *s, int cf) +{ + char *cp; + struct var *vp; + + DBGPRINTF7(("ASSIGN: enter, s=%s, cf=%d\n", s, cf)); + + if (!isalpha(*s) && *s != '_') + return 0; + for (cp = s; *cp != '='; cp++) + if (*cp == 0 || (!isalnum(*cp) && *cp != '_')) + return 0; + vp = lookup(s); + nameval(vp, ++cp, cf == COPYV ? (char *) NULL : s); + if (cf != COPYV) + vp->status &= ~GETCELL; + return 1; +} + +static int checkname(char *cp) +{ + DBGPRINTF7(("CHECKNAME: enter, cp=%s\n", cp)); + + if (!isalpha(*cp++) && *(cp - 1) != '_') + return 0; + while (*cp) + if (!isalnum(*cp++) && *(cp - 1) != '_') + return 0; + return 1; +} + +static void putvlist(int f, int out) +{ + struct var *vp; + + for (vp = vlist; vp; vp = vp->next) + if (vp->status & f && (isalpha(*vp->name) || *vp->name == '_')) { + if (vp->status & EXPORT) + write(out, "export ", 7); + if (vp->status & RONLY) + write(out, "readonly ", 9); + write(out, vp->name, (int) (findeq(vp->name) - vp->name)); + write(out, "\n", 1); + } +} + +static int eqname(char *n1, char *n2) +{ + for (; *n1 != '=' && *n1 != 0; n1++) + if (*n2++ != *n1) + return 0; + return *n2 == 0 || *n2 == '='; +} + +static char *findeq(char *cp) +{ + while (*cp != '\0' && *cp != '=') + cp++; + return cp; +} + +/* -------- gmatch.c -------- */ +/* + * int gmatch(string, pattern) + * char *string, *pattern; + * + * Match a pattern as in sh(1). + */ + +#define CMASK 0377 +#define QUOTE 0200 +#define QMASK (CMASK&~QUOTE) +#define NOT '!' /* might use ^ */ + +static int gmatch(char *s, char *p) +{ + int sc, pc; + + if (s == NULL || p == NULL) + return 0; + while ((pc = *p++ & CMASK) != '\0') { + sc = *s++ & QMASK; + switch (pc) { + case '[': + if ((p = cclass(p, sc)) == NULL) + return 0; + break; + + case '?': + if (sc == 0) + return 0; + break; + + case '*': + s--; + do { + if (*p == '\0' || gmatch(s, p)) + return 1; + } while (*s++ != '\0'); + return 0; + + default: + if (sc != (pc & ~QUOTE)) + return 0; + } + } + return *s == 0; +} + +static char *cclass(char *p, int sub) +{ + int c, d, not, found; + + if ((not = *p == NOT) != 0) + p++; + found = not; + do { + if (*p == '\0') + return NULL; + c = *p & CMASK; + if (p[1] == '-' && p[2] != ']') { + d = p[2] & CMASK; + p++; + } else + d = c; + if (c == sub || (c <= sub && sub <= d)) + found = !not; + } while (*++p != ']'); + return found ? p + 1 : NULL; +} + + +/* -------- area.c -------- */ + +/* + * All memory between (char *)areabot and (char *)(areatop+1) is + * exclusively administered by the area management routines. + * It is assumed that sbrk() and brk() manipulate the high end. + */ + +#define sbrk(X) ({ void * __q = (void *)-1; if (brkaddr + (int)(X) < brktop) { __q = brkaddr; brkaddr+=(int)(X); } __q;}) + +static void initarea(void) +{ + brkaddr = xmalloc(AREASIZE); + brktop = brkaddr + AREASIZE; + + while ((long) sbrk(0) & ALIGN) + sbrk(1); + areabot = (struct region *) sbrk(REGSIZE); + + areabot->next = areabot; + areabot->area = BUSY; + areatop = areabot; + areanxt = areabot; +} + +char *getcell(unsigned nbytes) +{ + int nregio; + struct region *p, *q; + int i; + + if (nbytes == 0) { + puts("getcell(0)"); + abort(); + } + /* silly and defeats the algorithm */ + /* + * round upwards and add administration area + */ + nregio = (nbytes + (REGSIZE - 1)) / REGSIZE + 1; + for (p = areanxt;;) { + if (p->area > areanum) { + /* + * merge free cells + */ + while ((q = p->next)->area > areanum && q != areanxt) + p->next = q->next; + /* + * exit loop if cell big enough + */ + if (q >= p + nregio) + goto found; + } + p = p->next; + if (p == areanxt) + break; + } + i = nregio >= GROWBY ? nregio : GROWBY; + p = (struct region *) sbrk(i * REGSIZE); + if (p == (struct region *) -1) + return NULL; + p--; + if (p != areatop) { + puts("not contig"); + abort(); /* allocated areas are contiguous */ + } + q = p + i; + p->next = q; + p->area = FREE; + q->next = areabot; + q->area = BUSY; + areatop = q; + found: + /* + * we found a FREE area big enough, pointed to by 'p', and up to 'q' + */ + areanxt = p + nregio; + if (areanxt < q) { + /* + * split into requested area and rest + */ + if (areanxt + 1 > q) { + puts("OOM"); + abort(); /* insufficient space left for admin */ + } + areanxt->next = q; + areanxt->area = FREE; + p->next = areanxt; + } + p->area = areanum; + return (char *) (p + 1); +} + +static void freecell(char *cp) +{ + struct region *p; + + if ((p = (struct region *) cp) != NULL) { + p--; + if (p < areanxt) + areanxt = p; + p->area = FREE; + } +} + +static void freearea(int a) +{ + struct region *p, *top; + + top = areatop; + for (p = areabot; p != top; p = p->next) + if (p->area >= a) + p->area = FREE; +} + +static void setarea(char *cp, int a) +{ + struct region *p; + + if ((p = (struct region *) cp) != NULL) + (p - 1)->area = a; +} + +int getarea(char *cp) +{ + return ((struct region *) cp - 1)->area; +} + +static void garbage(void) +{ + struct region *p, *q, *top; + + top = areatop; + for (p = areabot; p != top; p = p->next) { + if (p->area > areanum) { + while ((q = p->next)->area > areanum) + p->next = q->next; + areanxt = p; + } + } +#ifdef SHRINKBY + if (areatop >= q + SHRINKBY && q->area > areanum) { + brk((char *) (q + 1)); + q->next = areabot; + q->area = BUSY; + areatop = q; + } +#endif +} + +/* -------- csyn.c -------- */ +/* + * shell: syntax (C version) + */ + +int yyparse(void) +{ + DBGPRINTF7(("YYPARSE: enter...\n")); + + startl = 1; + peeksym = 0; + yynerrs = 0; + outtree = c_list(); + musthave('\n', 0); + return (yynerrs != 0); +} + +static struct op *pipeline(int cf) +{ + struct op *t, *p; + int c; + + DBGPRINTF7(("PIPELINE: enter, cf=%d\n", cf)); + + t = command(cf); + + DBGPRINTF9(("PIPELINE: t=%p\n", t)); + + if (t != NULL) { + while ((c = yylex(0)) == '|') { + if ((p = command(CONTIN)) == NULL) { + DBGPRINTF8(("PIPELINE: error!\n")); + SYNTAXERR; + } + + if (t->type != TPAREN && t->type != TCOM) { + /* shell statement */ + t = block(TPAREN, t, NOBLOCK, NOWORDS); + } + + t = block(TPIPE, t, p, NOWORDS); + } + peeksym = c; + } + + DBGPRINTF7(("PIPELINE: returning t=%p\n", t)); + return t; +} + +static struct op *andor(void) +{ + struct op *t, *p; + int c; + + DBGPRINTF7(("ANDOR: enter...\n")); + + t = pipeline(0); + + DBGPRINTF9(("ANDOR: t=%p\n", t)); + + if (t != NULL) { + while ((c = yylex(0)) == LOGAND || c == LOGOR) { + if ((p = pipeline(CONTIN)) == NULL) { + DBGPRINTF8(("ANDOR: error!\n")); + SYNTAXERR; + } + + t = block(c == LOGAND ? TAND : TOR, t, p, NOWORDS); + } /* WHILE */ + + peeksym = c; + } + + DBGPRINTF7(("ANDOR: returning t=%p\n", t)); + return t; +} + +static struct op *c_list(void) +{ + struct op *t, *p; + int c; + + DBGPRINTF7(("C_LIST: enter...\n")); + + t = andor(); + + if (t != NULL) { + if ((peeksym = yylex(0)) == '&') + t = block(TASYNC, t, NOBLOCK, NOWORDS); + + while ((c = yylex(0)) == ';' || c == '&' + || (multiline && c == '\n')) { + + if ((p = andor()) == NULL) + return t; + + if ((peeksym = yylex(0)) == '&') + p = block(TASYNC, p, NOBLOCK, NOWORDS); + + t = list(t, p); + } /* WHILE */ + + peeksym = c; + } + /* IF */ + DBGPRINTF7(("C_LIST: returning t=%p\n", t)); + return t; +} + +static int synio(int cf) +{ + struct ioword *iop; + int i; + int c; + + DBGPRINTF7(("SYNIO: enter, cf=%d\n", cf)); + + if ((c = yylex(cf)) != '<' && c != '>') { + peeksym = c; + return 0; + } + + i = yylval.i; + musthave(WORD, 0); + iop = io(iounit, i, yylval.cp); + iounit = IODEFAULT; + + if (i & IOHERE) + markhere(yylval.cp, iop); + + DBGPRINTF7(("SYNIO: returning 1\n")); + return 1; +} + +static void musthave(int c, int cf) +{ + if ((peeksym = yylex(cf)) != c) { + DBGPRINTF7(("MUSTHAVE: error!\n")); + SYNTAXERR; + } + + peeksym = 0; +} + +static struct op *simple(void) +{ + struct op *t; + + t = NULL; + for (;;) { + switch (peeksym = yylex(0)) { + case '<': + case '>': + (void) synio(0); + break; + + case WORD: + if (t == NULL) { + t = newtp(); + t->type = TCOM; + } + peeksym = 0; + word(yylval.cp); + break; + + default: + return t; + } + } +} + +static struct op *nested(int type, int mark) +{ + struct op *t; + + DBGPRINTF3(("NESTED: enter, type=%d, mark=%d\n", type, mark)); + + multiline++; + t = c_list(); + musthave(mark, 0); + multiline--; + return block(type, t, NOBLOCK, NOWORDS); +} + +static struct op *command(int cf) +{ + struct op *t; + struct wdblock *iosave; + int c; + + DBGPRINTF(("COMMAND: enter, cf=%d\n", cf)); + + iosave = iolist; + iolist = NULL; + + if (multiline) + cf |= CONTIN; + + while (synio(cf)) + cf = 0; + + c = yylex(cf); + + switch (c) { + default: + peeksym = c; + if ((t = simple()) == NULL) { + if (iolist == NULL) + return NULL; + t = newtp(); + t->type = TCOM; + } + break; + + case '(': + t = nested(TPAREN, ')'); + break; + + case '{': + t = nested(TBRACE, '}'); + break; + + case FOR: + t = newtp(); + t->type = TFOR; + musthave(WORD, 0); + startl = 1; + t->str = yylval.cp; + multiline++; + t->words = wordlist(); + if ((c = yylex(0)) != '\n' && c != ';') + peeksym = c; + t->left = dogroup(0); + multiline--; + break; + + case WHILE: + case UNTIL: + multiline++; + t = newtp(); + t->type = c == WHILE ? TWHILE : TUNTIL; + t->left = c_list(); + t->right = dogroup(1); + t->words = NULL; + multiline--; + break; + + case CASE: + t = newtp(); + t->type = TCASE; + musthave(WORD, 0); + t->str = yylval.cp; + startl++; + multiline++; + musthave(IN, CONTIN); + startl++; + + t->left = caselist(); + + musthave(ESAC, 0); + multiline--; + break; + + case IF: + multiline++; + t = newtp(); + t->type = TIF; + t->left = c_list(); + t->right = thenpart(); + musthave(FI, 0); + multiline--; + break; + + case DOT: + t = newtp(); + t->type = TDOT; + + musthave(WORD, 0); /* gets name of file */ + DBGPRINTF7(("COMMAND: DOT clause, yylval.cp is %s\n", yylval.cp)); + + word(yylval.cp); /* add word to wdlist */ + word(NOWORD); /* terminate wdlist */ + t->words = copyw(); /* dup wdlist */ + break; + + } + + while (synio(0)); + + t = namelist(t); + iolist = iosave; + + DBGPRINTF(("COMMAND: returning %p\n", t)); + + return t; +} + +static struct op *dowholefile(int type, int mark) +{ + struct op *t; + + DBGPRINTF(("DOWHOLEFILE: enter, type=%d, mark=%d\n", type, mark)); + + multiline++; + t = c_list(); + multiline--; + t = block(type, t, NOBLOCK, NOWORDS); + DBGPRINTF(("DOWHOLEFILE: return t=%p\n", t)); + return t; +} + +static struct op *dogroup(int onlydone) +{ + int c; + struct op *mylist; + + c = yylex(CONTIN); + if (c == DONE && onlydone) + return NULL; + if (c != DO) + SYNTAXERR; + mylist = c_list(); + musthave(DONE, 0); + return mylist; +} + +static struct op *thenpart(void) +{ + int c; + struct op *t; + + if ((c = yylex(0)) != THEN) { + peeksym = c; + return NULL; + } + t = newtp(); + t->type = 0; + t->left = c_list(); + if (t->left == NULL) + SYNTAXERR; + t->right = elsepart(); + return t; +} + +static struct op *elsepart(void) +{ + int c; + struct op *t; + + switch (c = yylex(0)) { + case ELSE: + if ((t = c_list()) == NULL) + SYNTAXERR; + return t; + + case ELIF: + t = newtp(); + t->type = TELIF; + t->left = c_list(); + t->right = thenpart(); + return t; + + default: + peeksym = c; + return NULL; + } +} + +static struct op *caselist(void) +{ + struct op *t; + + t = NULL; + while ((peeksym = yylex(CONTIN)) != ESAC) { + DBGPRINTF(("CASELIST, doing yylex, peeksym=%d\n", peeksym)); + t = list(t, casepart()); + } + + DBGPRINTF(("CASELIST, returning t=%p\n", t)); + return t; +} + +static struct op *casepart(void) +{ + struct op *t; + + DBGPRINTF7(("CASEPART: enter...\n")); + + t = newtp(); + t->type = TPAT; + t->words = pattern(); + musthave(')', 0); + t->left = c_list(); + if ((peeksym = yylex(CONTIN)) != ESAC) + musthave(BREAK, CONTIN); + + DBGPRINTF7(("CASEPART: made newtp(TPAT, t=%p)\n", t)); + + return t; +} + +static char **pattern(void) +{ + int c, cf; + + cf = CONTIN; + do { + musthave(WORD, cf); + word(yylval.cp); + cf = 0; + } while ((c = yylex(0)) == '|'); + peeksym = c; + word(NOWORD); + + return copyw(); +} + +static char **wordlist(void) +{ + int c; + + if ((c = yylex(0)) != IN) { + peeksym = c; + return NULL; + } + startl = 0; + while ((c = yylex(0)) == WORD) + word(yylval.cp); + word(NOWORD); + peeksym = c; + return copyw(); +} + +/* + * supporting functions + */ +static struct op *list(struct op *t1, struct op *t2) +{ + DBGPRINTF7(("LIST: enter, t1=%p, t2=%p\n", t1, t2)); + + if (t1 == NULL) + return t2; + if (t2 == NULL) + return t1; + + return block(TLIST, t1, t2, NOWORDS); +} + +static struct op *block(int type, struct op *t1, struct op *t2, char **wp) +{ + struct op *t; + + DBGPRINTF7(("BLOCK: enter, type=%d (%s)\n", type, T_CMD_NAMES[type])); + + t = newtp(); + t->type = type; + t->left = t1; + t->right = t2; + t->words = wp; + + DBGPRINTF7(("BLOCK: inserted %p between %p and %p\n", t, t1, + t2)); + + return t; +} + +/* See if given string is a shell multiline (FOR, IF, etc) */ +static int rlookup(char *n) +{ + const struct res *rp; + + DBGPRINTF7(("RLOOKUP: enter, n is %s\n", n)); + + for (rp = restab; rp->r_name; rp++) + if (strcmp(rp->r_name, n) == 0) { + DBGPRINTF7(("RLOOKUP: match, returning %d\n", rp->r_val)); + return rp->r_val; /* Return numeric code for shell multiline */ + } + + DBGPRINTF7(("RLOOKUP: NO match, returning 0\n")); + return 0; /* Not a shell multiline */ +} + +static struct op *newtp(void) +{ + struct op *t; + + t = (struct op *) tree(sizeof(*t)); + t->type = 0; + t->words = NULL; + t->ioact = NULL; + t->left = NULL; + t->right = NULL; + t->str = NULL; + + DBGPRINTF3(("NEWTP: allocated %p\n", t)); + + return t; +} + +static struct op *namelist(struct op *t) +{ + + DBGPRINTF7(("NAMELIST: enter, t=%p, type %s, iolist=%p\n", t, + T_CMD_NAMES[t->type], iolist)); + + if (iolist) { + iolist = addword((char *) NULL, iolist); + t->ioact = copyio(); + } else + t->ioact = NULL; + + if (t->type != TCOM) { + if (t->type != TPAREN && t->ioact != NULL) { + t = block(TPAREN, t, NOBLOCK, NOWORDS); + t->ioact = t->left->ioact; + t->left->ioact = NULL; + } + return t; + } + + word(NOWORD); + t->words = copyw(); + + + return t; +} + +static char **copyw(void) +{ + char **wd; + + wd = getwords(wdlist); + wdlist = 0; + return wd; +} + +static void word(char *cp) +{ + wdlist = addword(cp, wdlist); +} + +static struct ioword **copyio(void) +{ + struct ioword **iop; + + iop = (struct ioword **) getwords(iolist); + iolist = 0; + return iop; +} + +static struct ioword *io(int u, int f, char *cp) +{ + struct ioword *iop; + + iop = (struct ioword *) tree(sizeof(*iop)); + iop->io_unit = u; + iop->io_flag = f; + iop->io_name = cp; + iolist = addword((char *) iop, iolist); + return iop; +} + +static void zzerr(void) +{ + yyerror("syntax error"); +} + +static void yyerror(char *s) +{ + yynerrs++; + if (interactive && e.iop <= iostack) { + multiline = 0; + while (eofc() == 0 && yylex(0) != '\n'); + } + err(s); + fail(); +} + +static int yylex(int cf) +{ + int c, c1; + int atstart; + + if ((c = peeksym) > 0) { + peeksym = 0; + if (c == '\n') + startl = 1; + return c; + } + + + nlseen = 0; + atstart = startl; + startl = 0; + yylval.i = 0; + e.linep = line; + +/* MALAMO */ + line[LINELIM - 1] = '\0'; + + loop: + while ((c = my_getc(0)) == ' ' || c == '\t') /* Skip whitespace */ + ; + + switch (c) { + default: + if (any(c, "0123456789")) { + unget(c1 = my_getc(0)); + if (c1 == '<' || c1 == '>') { + iounit = c - '0'; + goto loop; + } + *e.linep++ = c; + c = c1; + } + break; + + case '#': /* Comment, skip to next newline or End-of-string */ + while ((c = my_getc(0)) != 0 && c != '\n'); + unget(c); + goto loop; + + case 0: + DBGPRINTF5(("YYLEX: return 0, c=%d\n", c)); + return c; + + case '$': + DBGPRINTF9(("YYLEX: found $\n")); + *e.linep++ = c; + if ((c = my_getc(0)) == '{') { + if ((c = collect(c, '}')) != '\0') + return c; + goto pack; + } + break; + + case '`': + case '\'': + case '"': + if ((c = collect(c, c)) != '\0') + return c; + goto pack; + + case '|': + case '&': + case ';': + startl = 1; + /* If more chars process them, else return NULL char */ + if ((c1 = dual(c)) != '\0') + return c1; + else + return c; + + case '^': + startl = 1; + return '|'; + case '>': + case '<': + diag(c); + return c; + + case '\n': + nlseen++; + gethere(); + startl = 1; + if (multiline || cf & CONTIN) { + if (interactive && e.iop <= iostack) { +#ifdef CONFIG_FEATURE_COMMAND_EDITING + current_prompt = cprompt->value; +#else + prs(cprompt->value); +#endif + } + if (cf & CONTIN) + goto loop; + } + return c; + + case '(': + case ')': + startl = 1; + return c; + } + + unget(c); + + pack: + while ((c = my_getc(0)) != 0 && !any(c, "`$ '\"\t;&<>()|^\n")) { + if (e.linep >= elinep) + err("word too long"); + else + *e.linep++ = c; + }; + + unget(c); + + if (any(c, "\"'`$")) + goto loop; + + *e.linep++ = '\0'; + + if (atstart && (c = rlookup(line)) != 0) { + startl = 1; + return c; + } + + yylval.cp = strsave(line, areanum); + return WORD; +} + + +static int collect(int c, int c1) +{ + char s[2]; + + DBGPRINTF8(("COLLECT: enter, c=%d, c1=%d\n", c, c1)); + + *e.linep++ = c; + while ((c = my_getc(c1)) != c1) { + if (c == 0) { + unget(c); + s[0] = c1; + s[1] = 0; + prs("no closing "); + yyerror(s); + return YYERRCODE; + } + if (interactive && c == '\n' && e.iop <= iostack) { +#ifdef CONFIG_FEATURE_COMMAND_EDITING + current_prompt = cprompt->value; +#else + prs(cprompt->value); +#endif + } + *e.linep++ = c; + } + + *e.linep++ = c; + + DBGPRINTF8(("COLLECT: return 0, line is %s\n", line)); + + return 0; +} + +/* "multiline commands" helper func */ +/* see if next 2 chars form a shell multiline */ +static int dual(int c) +{ + char s[3]; + char *cp = s; + + DBGPRINTF8(("DUAL: enter, c=%d\n", c)); + + *cp++ = c; /* c is the given "peek" char */ + *cp++ = my_getc(0); /* get next char of input */ + *cp = 0; /* add EOS marker */ + + c = rlookup(s); /* see if 2 chars form a shell multiline */ + if (c == 0) + unget(*--cp); /* String is not a shell multiline, put peek char back */ + + return c; /* String is multiline, return numeric multiline (restab) code */ +} + +static void diag(int ec) +{ + int c; + + DBGPRINTF8(("DIAG: enter, ec=%d\n", ec)); + + c = my_getc(0); + if (c == '>' || c == '<') { + if (c != ec) + zzerr(); + yylval.i = ec == '>' ? IOWRITE | IOCAT : IOHERE; + c = my_getc(0); + } else + yylval.i = ec == '>' ? IOWRITE : IOREAD; + if (c != '&' || yylval.i == IOHERE) + unget(c); + else + yylval.i |= IODUP; +} + +static char *tree(unsigned size) +{ + char *t; + + if ((t = getcell(size)) == NULL) { + DBGPRINTF2(("TREE: getcell(%d) failed!\n", size)); + prs("command line too complicated\n"); + fail(); + /* NOTREACHED */ + } + return t; +} + +/* VARARGS1 */ +/* ARGSUSED */ + +/* -------- exec.c -------- */ + +/* + * execute tree + */ + + +static int execute(struct op *t, int *pin, int *pout, int act) +{ + struct op *t1; + volatile int i, rv, a; + char *cp, **wp, **wp2; + struct var *vp; + struct op *outtree_save; + struct brkcon bc; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) ℘ +#endif + + if (t == NULL) { + DBGPRINTF4(("EXECUTE: enter, t==null, returning.\n")); + return 0; + } + + DBGPRINTF(("EXECUTE: t=%p, t->type=%d (%s), t->words is %s\n", t, + t->type, T_CMD_NAMES[t->type], + ((t->words == NULL) ? "NULL" : t->words[0]))); + + rv = 0; + a = areanum++; + wp = (wp2 = t->words) != NULL + ? eval(wp2, t->type == TCOM ? DOALL : DOALL & ~DOKEY) + : NULL; + + switch (t->type) { + case TDOT: + DBGPRINTF3(("EXECUTE: TDOT\n")); + + outtree_save = outtree; + + newfile(evalstr(t->words[0], DOALL)); + + t->left = dowholefile(TLIST, 0); + t->right = NULL; + + outtree = outtree_save; + + if (t->left) + rv = execute(t->left, pin, pout, 0); + if (t->right) + rv = execute(t->right, pin, pout, 0); + break; + + case TPAREN: + rv = execute(t->left, pin, pout, 0); + break; + + case TCOM: + { + rv = forkexec(t, pin, pout, act, wp); + } + break; + + case TPIPE: + { + int pv[2]; + + if ((rv = openpipe(pv)) < 0) + break; + pv[0] = remap(pv[0]); + pv[1] = remap(pv[1]); + (void) execute(t->left, pin, pv, 0); + rv = execute(t->right, pv, pout, 0); + } + break; + + case TLIST: + (void) execute(t->left, pin, pout, 0); + rv = execute(t->right, pin, pout, 0); + break; + + case TASYNC: + { + int hinteractive = interactive; + + DBGPRINTF7(("EXECUTE: TASYNC clause, calling vfork()...\n")); + + i = vfork(); + if (i != 0) { + interactive = hinteractive; + if (i != -1) { + setval(lookup("!"), putn(i)); + if (pin != NULL) + closepipe(pin); + if (interactive) { + prs(putn(i)); + prs("\n"); + } + } else + rv = -1; + setstatus(rv); + } else { + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + if (interactive) + signal(SIGTERM, SIG_DFL); + interactive = 0; + if (pin == NULL) { + close(0); + open(bb_dev_null, 0); + } + _exit(execute(t->left, pin, pout, FEXEC)); + } + } + break; + + case TOR: + case TAND: + rv = execute(t->left, pin, pout, 0); + if ((t1 = t->right) != NULL && (rv == 0) == (t->type == TAND)) + rv = execute(t1, pin, pout, 0); + break; + + case TFOR: + if (wp == NULL) { + wp = dolv + 1; + if ((i = dolc) < 0) + i = 0; + } else { + i = -1; + while (*wp++ != NULL); + } + vp = lookup(t->str); + while (setjmp(bc.brkpt)) + if (isbreak) + goto broken; + brkset(&bc); + for (t1 = t->left; i-- && *wp != NULL;) { + setval(vp, *wp++); + rv = execute(t1, pin, pout, 0); + } + brklist = brklist->nextlev; + break; + + case TWHILE: + case TUNTIL: + while (setjmp(bc.brkpt)) + if (isbreak) + goto broken; + brkset(&bc); + t1 = t->left; + while ((execute(t1, pin, pout, 0) == 0) == (t->type == TWHILE)) + rv = execute(t->right, pin, pout, 0); + brklist = brklist->nextlev; + break; + + case TIF: + case TELIF: + if (t->right != NULL) { + rv = !execute(t->left, pin, pout, 0) ? + execute(t->right->left, pin, pout, 0) : + execute(t->right->right, pin, pout, 0); + } + break; + + case TCASE: + if ((cp = evalstr(t->str, DOSUB | DOTRIM)) == 0) + cp = ""; + + DBGPRINTF7(("EXECUTE: TCASE, t->str is %s, cp is %s\n", + ((t->str == NULL) ? "NULL" : t->str), + ((cp == NULL) ? "NULL" : cp))); + + if ((t1 = findcase(t->left, cp)) != NULL) { + DBGPRINTF7(("EXECUTE: TCASE, calling execute(t=%p, t1=%p)...\n", t, t1)); + rv = execute(t1, pin, pout, 0); + DBGPRINTF7(("EXECUTE: TCASE, back from execute(t=%p, t1=%p)...\n", t, t1)); + } + break; + + case TBRACE: +/* + if (iopp = t->ioact) + while (*iopp) + if (iosetup(*iopp++, pin!=NULL, pout!=NULL)) { + rv = -1; + break; + } +*/ + if (rv >= 0 && (t1 = t->left)) + rv = execute(t1, pin, pout, 0); + break; + + }; + + broken: + t->words = wp2; + isbreak = 0; + freehere(areanum); + freearea(areanum); + areanum = a; + if (interactive && intr) { + closeall(); + fail(); + } + + if ((i = trapset) != 0) { + trapset = 0; + runtrap(i); + } + + DBGPRINTF(("EXECUTE: returning from t=%p, rv=%d\n", t, rv)); + return rv; +} + +static int +forkexec(struct op *t, int *pin, int *pout, int act, char **wp) +{ + pid_t newpid; + int i, rv; + int (*shcom) (struct op *) = NULL; + int f; + char *cp = NULL; + struct ioword **iopp; + int resetsig; + char **owp; + int forked = 0; + + int *hpin = pin; + int *hpout = pout; + char *hwp; + int hinteractive; + int hintr; + struct brkcon *hbrklist; + int hexecflg; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &pin; + (void) &pout; + (void) ℘ + (void) &shcom; + (void) &cp; + (void) &resetsig; + (void) &owp; +#endif + + DBGPRINTF(("FORKEXEC: t=%p, pin %p, pout %p, act %d\n", t, pin, + pout, act)); + DBGPRINTF7(("FORKEXEC: t->words is %s\n", + ((t->words == NULL) ? "NULL" : t->words[0]))); + + owp = wp; + resetsig = 0; + rv = -1; /* system-detected error */ + if (t->type == TCOM) { + while ((cp = *wp++) != NULL); + cp = *wp; + + /* strip all initial assignments */ + /* not correct wrt PATH=yyy command etc */ + if (flag['x']) { + DBGPRINTF9(("FORKEXEC: echo'ing, cp=%p, wp=%p, owp=%p\n", + cp, wp, owp)); + echo(cp ? wp : owp); + } + + if (cp == NULL && t->ioact == NULL) { + while ((cp = *owp++) != NULL && assign(cp, COPYV)); + DBGPRINTF(("FORKEXEC: returning setstatus()\n")); + return setstatus(0); + } else if (cp != NULL) { + shcom = inbuilt(cp); + } + } + + t->words = wp; + f = act; + + DBGPRINTF(("FORKEXEC: shcom %p, f&FEXEC 0x%x, owp %p\n", shcom, + f & FEXEC, owp)); + + if (shcom == NULL && (f & FEXEC) == 0) { + /* Save values in case the child process alters them */ + hpin = pin; + hpout = pout; + hwp = *wp; + hinteractive = interactive; + hintr = intr; + hbrklist = brklist; + hexecflg = execflg; + + DBGPRINTF3(("FORKEXEC: calling vfork()...\n")); + + newpid = vfork(); + + if (newpid == -1) { + DBGPRINTF(("FORKEXEC: ERROR, cannot vfork()!\n")); + return -1; + } + + + if (newpid > 0) { /* Parent */ + + /* Restore values */ + pin = hpin; + pout = hpout; + *wp = hwp; + interactive = hinteractive; + intr = hintr; + brklist = hbrklist; + execflg = hexecflg; + +/* moved up + if (i == -1) + return rv; +*/ + + if (pin != NULL) + closepipe(pin); + + return (pout == NULL ? setstatus(waitfor(newpid, 0)) : 0); + } + + /* Must be the child process, pid should be 0 */ + DBGPRINTF(("FORKEXEC: child process, shcom=%p\n", shcom)); + + if (interactive) { + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + resetsig = 1; + } + interactive = 0; + intr = 0; + forked = 1; + brklist = 0; + execflg = 0; + } + + + if (owp != NULL) + while ((cp = *owp++) != NULL && assign(cp, COPYV)) + if (shcom == NULL) + export(lookup(cp)); + +#ifdef COMPIPE + if ((pin != NULL || pout != NULL) && shcom != NULL && shcom != doexec) { + err("piping to/from shell builtins not yet done"); + if (forked) + _exit(-1); + return -1; + } +#endif + + if (pin != NULL) { + dup2(pin[0], 0); + closepipe(pin); + } + if (pout != NULL) { + dup2(pout[1], 1); + closepipe(pout); + } + + if ((iopp = t->ioact) != NULL) { + if (shcom != NULL && shcom != doexec) { + prs(cp); + err(": cannot redirect shell command"); + if (forked) + _exit(-1); + return -1; + } + while (*iopp) + if (iosetup(*iopp++, pin != NULL, pout != NULL)) { + if (forked) + _exit(rv); + return rv; + } + } + + if (shcom) { + i = setstatus((*shcom) (t)); + if (forked) + _exit(i); + DBGPRINTF(("FORKEXEC: returning i=%d\n", i)); + return i; + } + + /* should use FIOCEXCL */ + for (i = FDBASE; i < NOFILE; i++) + close(i); + if (resetsig) { + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + } + + if (t->type == TPAREN) + _exit(execute(t->left, NOPIPE, NOPIPE, FEXEC)); + if (wp[0] == NULL) + _exit(0); + + cp = rexecve(wp[0], wp, makenv(0, NULL)); + prs(wp[0]); + prs(": "); + err(cp); + if (!execflg) + trap[0] = NULL; + + DBGPRINTF(("FORKEXEC: calling leave(), pid=%d\n", newpid)); + + leave(); + /* NOTREACHED */ + _exit(1); +} + +/* + * 0< 1> are ignored as required + * within pipelines. + */ +static int iosetup(struct ioword *iop, int pipein, int pipeout) +{ + int u = -1; + char *cp = NULL, *msg; + + DBGPRINTF(("IOSETUP: iop %p, pipein %i, pipeout %i\n", iop, + pipein, pipeout)); + + if (iop->io_unit == IODEFAULT) /* take default */ + iop->io_unit = iop->io_flag & (IOREAD | IOHERE) ? 0 : 1; + + if (pipein && iop->io_unit == 0) + return 0; + + if (pipeout && iop->io_unit == 1) + return 0; + + msg = iop->io_flag & (IOREAD | IOHERE) ? "open" : "create"; + if ((iop->io_flag & IOHERE) == 0) { + cp = iop->io_name; + if ((cp = evalstr(cp, DOSUB | DOTRIM)) == NULL) + return 1; + } + + if (iop->io_flag & IODUP) { + if (cp[1] || (!isdigit(*cp) && *cp != '-')) { + prs(cp); + err(": illegal >& argument"); + return 1; + } + if (*cp == '-') + iop->io_flag = IOCLOSE; + iop->io_flag &= ~(IOREAD | IOWRITE); + } + switch (iop->io_flag) { + case IOREAD: + u = open(cp, 0); + break; + + case IOHERE: + case IOHERE | IOXHERE: + u = herein(iop->io_name, iop->io_flag & IOXHERE); + cp = "here file"; + break; + + case IOWRITE | IOCAT: + if ((u = open(cp, 1)) >= 0) { + lseek(u, (long) 0, SEEK_END); + break; + } + case IOWRITE: + u = creat(cp, 0666); + break; + + case IODUP: + u = dup2(*cp - '0', iop->io_unit); + break; + + case IOCLOSE: + close(iop->io_unit); + return 0; + } + if (u < 0) { + prs(cp); + prs(": cannot "); + warn(msg); + return 1; + } else { + if (u != iop->io_unit) { + dup2(u, iop->io_unit); + close(u); + } + } + return 0; +} + +static void echo(char **wp) +{ + int i; + + prs("+"); + for (i = 0; wp[i]; i++) { + if (i) + prs(" "); + prs(wp[i]); + } + prs("\n"); +} + +static struct op **find1case(struct op *t, char *w) +{ + struct op *t1; + struct op **tp; + char **wp, *cp; + + + if (t == NULL) { + DBGPRINTF3(("FIND1CASE: enter, t==NULL, returning.\n")); + return NULL; + } + + DBGPRINTF3(("FIND1CASE: enter, t->type=%d (%s)\n", t->type, + T_CMD_NAMES[t->type])); + + if (t->type == TLIST) { + if ((tp = find1case(t->left, w)) != NULL) { + DBGPRINTF3(("FIND1CASE: found one to the left, returning tp=%p\n", tp)); + return tp; + } + t1 = t->right; /* TPAT */ + } else + t1 = t; + + for (wp = t1->words; *wp;) + if ((cp = evalstr(*wp++, DOSUB)) && gmatch(w, cp)) { + DBGPRINTF3(("FIND1CASE: returning &t1->left= %p.\n", + &t1->left)); + return &t1->left; + } + + DBGPRINTF(("FIND1CASE: returning NULL\n")); + return NULL; +} + +static struct op *findcase(struct op *t, char *w) +{ + struct op **tp; + + tp = find1case(t, w); + return tp != NULL ? *tp : NULL; +} + +/* + * Enter a new loop level (marked for break/continue). + */ +static void brkset(struct brkcon *bc) +{ + bc->nextlev = brklist; + brklist = bc; +} + +/* + * Wait for the last process created. + * Print a message for each process found + * that was killed by a signal. + * Ignore interrupt signals while waiting + * unless `canintr' is true. + */ +static int waitfor(int lastpid, int canintr) +{ + int pid, rv; + int s; + int oheedint = heedint; + + heedint = 0; + rv = 0; + do { + pid = wait(&s); + if (pid == -1) { + if (errno != EINTR || canintr) + break; + } else { + if ((rv = WAITSIG(s)) != 0) { + if (rv < NSIGNAL) { + if (signame[rv] != NULL) { + if (pid != lastpid) { + prn(pid); + prs(": "); + } + prs(signame[rv]); + } + } else { + if (pid != lastpid) { + prn(pid); + prs(": "); + } + prs("Signal "); + prn(rv); + prs(" "); + } + if (WAITCORE(s)) + prs(" - core dumped"); + if (rv >= NSIGNAL || signame[rv]) + prs("\n"); + rv = -1; + } else + rv = WAITVAL(s); + } + } while (pid != lastpid); + heedint = oheedint; + if (intr) { + if (interactive) { + if (canintr) + intr = 0; + } else { + if (exstat == 0) + exstat = rv; + onintr(0); + } + } + return rv; +} + +static int setstatus(int s) +{ + exstat = s; + setval(lookup("?"), putn(s)); + return s; +} + +/* + * PATH-searching interface to execve. + * If getenv("PATH") were kept up-to-date, + * execvp might be used. + */ +static char *rexecve(char *c, char **v, char **envp) +{ + int i; + char *sp, *tp; + int eacces = 0, asis = 0; + char *name = c; + + if (ENABLE_FEATURE_SH_STANDALONE_SHELL) { + optind = 1; + if (find_applet_by_name(name)) { + /* We have to exec here since we vforked. Running + * run_applet_by_name() won't work and bad things + * will happen. */ + execve(CONFIG_BUSYBOX_EXEC_PATH, v, envp); + } + } + + DBGPRINTF(("REXECVE: c=%p, v=%p, envp=%p\n", c, v, envp)); + + sp = any('/', c) ? "" : path->value; + asis = *sp == '\0'; + while (asis || *sp != '\0') { + asis = 0; + tp = e.linep; + for (; *sp != '\0'; tp++) + if ((*tp = *sp++) == ':') { + asis = *sp == '\0'; + break; + } + if (tp != e.linep) + *tp++ = '/'; + for (i = 0; (*tp++ = c[i++]) != '\0';); + + DBGPRINTF3(("REXECVE: e.linep is %s\n", e.linep)); + + execve(e.linep, v, envp); + + switch (errno) { + case ENOEXEC: + *v = e.linep; + tp = *--v; + *v = e.linep; + execve(DEFAULT_SHELL, v, envp); + *v = tp; + return "no Shell"; + + case ENOMEM: + return (char *) bb_msg_memory_exhausted; + + case E2BIG: + return "argument list too long"; + + case EACCES: + eacces++; + break; + } + } + return errno == ENOENT ? "not found" : "cannot execute"; +} + +/* + * Run the command produced by generator `f' + * applied to stream `arg'. + */ +static int run(struct ioarg *argp, int (*f) (struct ioarg *)) +{ + struct op *otree; + struct wdblock *swdlist; + struct wdblock *siolist; + jmp_buf ev, rt; + xint *ofail; + int rv; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &rv; +#endif + + DBGPRINTF(("RUN: enter, areanum %d, outtree %p, failpt %p\n", + areanum, outtree, failpt)); + + areanum++; + swdlist = wdlist; + siolist = iolist; + otree = outtree; + ofail = failpt; + rv = -1; + + if (newenv(setjmp(errpt = ev)) == 0) { + wdlist = 0; + iolist = 0; + pushio(argp, f); + e.iobase = e.iop; + yynerrs = 0; + if (setjmp(failpt = rt) == 0 && yyparse() == 0) + rv = execute(outtree, NOPIPE, NOPIPE, 0); + quitenv(); + } else { + DBGPRINTF(("RUN: error from newenv()!\n")); + } + + wdlist = swdlist; + iolist = siolist; + failpt = ofail; + outtree = otree; + freearea(areanum--); + + return rv; +} + +/* -------- do.c -------- */ + +/* + * built-in commands: doX + */ + +static int dohelp(struct op *t) +{ + int col; + const struct builtincmd *x; + + printf("\nBuilt-in commands:\n"); + printf("-------------------\n"); + + for (col = 0, x = builtincmds; x->builtinfunc != NULL; x++) { + if (!x->name) + continue; + col += printf("%s%s", ((col == 0) ? "\t" : " "), x->name); + if (col > 60) { + puts(""); + col = 0; + } + } +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + { + int i; + const struct BB_applet *applet; + extern const struct BB_applet applets[]; + extern const size_t NUM_APPLETS; + + for (i = 0, applet = applets; i < NUM_APPLETS; applet++, i++) { + if (!applet->name) + continue; + + col += printf("%s%s", ((col == 0) ? "\t" : " "), applet->name); + if (col > 60) { + puts(""); + col = 0; + } + } + } +#endif + printf("\n\n"); + return EXIT_SUCCESS; +} + + + +static int dolabel(struct op *t) +{ + return 0; +} + +static int dochdir(struct op *t) +{ + char *cp, *er; + + if ((cp = t->words[1]) == NULL && (cp = homedir->value) == NULL) + er = ": no home directory"; + else if (chdir(cp) < 0) + er = ": bad directory"; + else + return 0; + prs(cp != NULL ? cp : "cd"); + err(er); + return 1; +} + +static int doshift(struct op *t) +{ + int n; + + n = t->words[1] ? getn(t->words[1]) : 1; + if (dolc < n) { + err("nothing to shift"); + return 1; + } + dolv[n] = dolv[0]; + dolv += n; + dolc -= n; + setval(lookup("#"), putn(dolc)); + return 0; +} + +/* + * execute login and newgrp directly + */ +static int dologin(struct op *t) +{ + char *cp; + + if (interactive) { + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + } + cp = rexecve(t->words[0], t->words, makenv(0, NULL)); + prs(t->words[0]); + prs(": "); + err(cp); + return 1; +} + +static int doumask(struct op *t) +{ + int i, n; + char *cp; + + if ((cp = t->words[1]) == NULL) { + i = umask(0); + umask(i); + for (n = 3 * 4; (n -= 3) >= 0;) + putc('0' + ((i >> n) & 07), stderr); + putc('\n', stderr); + } else { + for (n = 0; *cp >= '0' && *cp <= '9'; cp++) + n = n * 8 + (*cp - '0'); + umask(n); + } + return 0; +} + +static int doexec(struct op *t) +{ + int i; + jmp_buf ex; + xint *ofail; + + t->ioact = NULL; + for (i = 0; (t->words[i] = t->words[i + 1]) != NULL; i++); + if (i == 0) + return 1; + execflg = 1; + ofail = failpt; + if (setjmp(failpt = ex) == 0) + execute(t, NOPIPE, NOPIPE, FEXEC); + failpt = ofail; + execflg = 0; + return 1; +} + +static int dodot(struct op *t) +{ + int i; + char *sp, *tp; + char *cp; + int maltmp; + + DBGPRINTF(("DODOT: enter, t=%p, tleft %p, tright %p, e.linep is %s\n", t, t->left, t->right, ((e.linep == NULL) ? "NULL" : e.linep))); + + if ((cp = t->words[1]) == NULL) { + DBGPRINTF(("DODOT: bad args, ret 0\n")); + return 0; + } else { + DBGPRINTF(("DODOT: cp is %s\n", cp)); + } + + sp = any('/', cp) ? ":" : path->value; + + DBGPRINTF(("DODOT: sp is %s, e.linep is %s\n", + ((sp == NULL) ? "NULL" : sp), + ((e.linep == NULL) ? "NULL" : e.linep))); + + while (*sp) { + tp = e.linep; + while (*sp && (*tp = *sp++) != ':') + tp++; + if (tp != e.linep) + *tp++ = '/'; + + for (i = 0; (*tp++ = cp[i++]) != '\0';); + + /* Original code */ + if ((i = open(e.linep, 0)) >= 0) { + exstat = 0; + maltmp = remap(i); + DBGPRINTF(("DODOT: remap=%d, exstat=%d, e.iofd %d, i %d, e.linep is %s\n", maltmp, exstat, e.iofd, i, e.linep)); + + next(maltmp); /* Basically a PUSHIO */ + + DBGPRINTF(("DODOT: returning exstat=%d\n", exstat)); + + return exstat; + } + + } /* While */ + + prs(cp); + err(": not found"); + + return -1; +} + +static int dowait(struct op *t) +{ + int i; + char *cp; + + if ((cp = t->words[1]) != NULL) { + i = getn(cp); + if (i == 0) + return 0; + } else + i = -1; + setstatus(waitfor(i, 1)); + return 0; +} + +static int doread(struct op *t) +{ + char *cp, **wp; + int nb = 0; + int nl = 0; + + if (t->words[1] == NULL) { + err("Usage: read name ..."); + return 1; + } + for (wp = t->words + 1; *wp; wp++) { + for (cp = e.linep; !nl && cp < elinep - 1; cp++) + if ((nb = read(0, cp, sizeof(*cp))) != sizeof(*cp) || + (nl = (*cp == '\n')) || (wp[1] && any(*cp, ifs->value))) + break; + *cp = 0; + if (nb <= 0) + break; + setval(lookup(*wp), e.linep); + } + return nb <= 0; +} + +static int doeval(struct op *t) +{ + return RUN(awordlist, t->words + 1, wdchar); +} + +static int dotrap(struct op *t) +{ + int n, i; + int resetsig; + + if (t->words[1] == NULL) { + for (i = 0; i <= _NSIG; i++) + if (trap[i]) { + prn(i); + prs(": "); + prs(trap[i]); + prs("\n"); + } + return 0; + } + resetsig = isdigit(*t->words[1]); + for (i = resetsig ? 1 : 2; t->words[i] != NULL; ++i) { + n = getsig(t->words[i]); + freecell(trap[n]); + trap[n] = 0; + if (!resetsig) { + if (*t->words[1] != '\0') { + trap[n] = strsave(t->words[1], 0); + setsig(n, sig); + } else + setsig(n, SIG_IGN); + } else { + if (interactive) + if (n == SIGINT) + setsig(n, onintr); + else + setsig(n, n == SIGQUIT ? SIG_IGN : SIG_DFL); + else + setsig(n, SIG_DFL); + } + } + return 0; +} + +static int getsig(char *s) +{ + int n; + + if ((n = getn(s)) < 0 || n > _NSIG) { + err("trap: bad signal number"); + n = 0; + } + return n; +} + +static void setsig(int n, sighandler_t f) +{ + if (n == 0) + return; + if (signal(n, SIG_IGN) != SIG_IGN || ourtrap[n]) { + ourtrap[n] = 1; + signal(n, f); + } +} + +static int getn(char *as) +{ + char *s; + int n, m; + + s = as; + m = 1; + if (*s == '-') { + m = -1; + s++; + } + for (n = 0; isdigit(*s); s++) + n = (n * 10) + (*s - '0'); + if (*s) { + prs(as); + err(": bad number"); + } + return n * m; +} + +static int dobreak(struct op *t) +{ + return brkcontin(t->words[1], 1); +} + +static int docontinue(struct op *t) +{ + return brkcontin(t->words[1], 0); +} + +static int brkcontin(char *cp, int val) +{ + struct brkcon *bc; + int nl; + + nl = cp == NULL ? 1 : getn(cp); + if (nl <= 0) + nl = 999; + do { + if ((bc = brklist) == NULL) + break; + brklist = bc->nextlev; + } while (--nl); + if (nl) { + err("bad break/continue level"); + return 1; + } + isbreak = val; + longjmp(bc->brkpt, 1); + /* NOTREACHED */ +} + +static int doexit(struct op *t) +{ + char *cp; + + execflg = 0; + if ((cp = t->words[1]) != NULL) + setstatus(getn(cp)); + + DBGPRINTF(("DOEXIT: calling leave(), t=%p\n", t)); + + leave(); + /* NOTREACHED */ + return 0; +} + +static int doexport(struct op *t) +{ + rdexp(t->words + 1, export, EXPORT); + return 0; +} + +static int doreadonly(struct op *t) +{ + rdexp(t->words + 1, ronly, RONLY); + return 0; +} + +static void rdexp(char **wp, void (*f) (struct var *), int key) +{ + DBGPRINTF6(("RDEXP: enter, wp=%p, func=%p, key=%d\n", wp, f, key)); + DBGPRINTF6(("RDEXP: *wp=%s\n", *wp)); + + if (*wp != NULL) { + for (; *wp != NULL; wp++) { + if (isassign(*wp)) { + char *cp; + + assign(*wp, COPYV); + for (cp = *wp; *cp != '='; cp++); + *cp = '\0'; + } + if (checkname(*wp)) + (*f) (lookup(*wp)); + else + badid(*wp); + } + } else + putvlist(key, 1); +} + +static void badid(char *s) +{ + prs(s); + err(": bad identifier"); +} + +static int doset(struct op *t) +{ + struct var *vp; + char *cp; + int n; + + if ((cp = t->words[1]) == NULL) { + for (vp = vlist; vp; vp = vp->next) + varput(vp->name, 1); + return 0; + } + if (*cp == '-') { + /* bad: t->words++; */ + for (n = 0; (t->words[n] = t->words[n + 1]) != NULL; n++); + if (*++cp == 0) + flag['x'] = flag['v'] = 0; + else + for (; *cp; cp++) + switch (*cp) { + case 'e': + if (!interactive) + flag['e']++; + break; + + default: + if (*cp >= 'a' && *cp <= 'z') + flag[(int) *cp]++; + break; + } + setdash(); + } + if (t->words[1]) { + t->words[0] = dolv[0]; + for (n = 1; t->words[n]; n++) + setarea((char *) t->words[n], 0); + dolc = n - 1; + dolv = t->words; + setval(lookup("#"), putn(dolc)); + setarea((char *) (dolv - 1), 0); + } + return 0; +} + +static void varput(char *s, int out) +{ + if (isalnum(*s) || *s == '_') { + write(out, s, strlen(s)); + write(out, "\n", 1); + } +} + + +/* + * Copyright (c) 1999 Herbert Xu <herbert@debian.org> + * This file contains code for the times builtin. + */ +static int dotimes(struct op *t) +{ + struct tms buf; + long int clk_tck = sysconf(_SC_CLK_TCK); + + times(&buf); + printf("%dm%fs %dm%fs\n%dm%fs %dm%fs\n", + (int) (buf.tms_utime / clk_tck / 60), + ((double) buf.tms_utime) / clk_tck, + (int) (buf.tms_stime / clk_tck / 60), + ((double) buf.tms_stime) / clk_tck, + (int) (buf.tms_cutime / clk_tck / 60), + ((double) buf.tms_cutime) / clk_tck, + (int) (buf.tms_cstime / clk_tck / 60), + ((double) buf.tms_cstime) / clk_tck); + return 0; +} + + +static int (*inbuilt(char *s)) (struct op *) { + const struct builtincmd *bp; + + for (bp = builtincmds; bp->name != NULL; bp++) + if (strcmp(bp->name, s) == 0) + return bp->builtinfunc; + + return NULL; +} + +/* -------- eval.c -------- */ + +/* + * ${} + * `command` + * blank interpretation + * quoting + * glob + */ + +static char **eval(char **ap, int f) +{ + struct wdblock *wb; + char **wp; + char **wf; + jmp_buf ev; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) ℘ + (void) ≈ +#endif + + DBGPRINTF4(("EVAL: enter, f=%d\n", f)); + + wp = NULL; + wb = NULL; + wf = NULL; + if (newenv(setjmp(errpt = ev)) == 0) { + while (*ap && isassign(*ap)) + expand(*ap++, &wb, f & ~DOGLOB); + if (flag['k']) { + for (wf = ap; *wf; wf++) { + if (isassign(*wf)) + expand(*wf, &wb, f & ~DOGLOB); + } + } + for (wb = addword((char *) 0, wb); *ap; ap++) { + if (!flag['k'] || !isassign(*ap)) + expand(*ap, &wb, f & ~DOKEY); + } + wb = addword((char *) 0, wb); + wp = getwords(wb); + quitenv(); + } else + gflg = 1; + + return gflg ? (char **) NULL : wp; +} + +/* + * Make the exported environment from the exported + * names in the dictionary. Keyword assignments + * will already have been done. + */ +static char **makenv(int all, struct wdblock *wb) +{ + struct var *vp; + + DBGPRINTF5(("MAKENV: enter, all=%d\n", all)); + + for (vp = vlist; vp; vp = vp->next) + if (all || vp->status & EXPORT) + wb = addword(vp->name, wb); + wb = addword((char *) 0, wb); + return getwords(wb); +} + +static char *evalstr(char *cp, int f) +{ + struct wdblock *wb; + + DBGPRINTF6(("EVALSTR: enter, cp=%p, f=%d\n", cp, f)); + + wb = NULL; + if (expand(cp, &wb, f)) { + if (wb == NULL || wb->w_nword == 0 + || (cp = wb->w_words[0]) == NULL) + cp = ""; + DELETE(wb); + } else + cp = NULL; + return cp; +} + +static int expand(char *cp, struct wdblock **wbp, int f) +{ + jmp_buf ev; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &cp; +#endif + + DBGPRINTF3(("EXPAND: enter, f=%d\n", f)); + + gflg = 0; + + if (cp == NULL) + return 0; + + if (!anys("$`'\"", cp) && + !anys(ifs->value, cp) && ((f & DOGLOB) == 0 || !anys("[*?", cp))) { + cp = strsave(cp, areanum); + if (f & DOTRIM) + unquote(cp); + *wbp = addword(cp, *wbp); + return 1; + } + if (newenv(setjmp(errpt = ev)) == 0) { + PUSHIO(aword, cp, strchar); + e.iobase = e.iop; + while ((cp = blank(f)) && gflg == 0) { + e.linep = cp; + cp = strsave(cp, areanum); + if ((f & DOGLOB) == 0) { + if (f & DOTRIM) + unquote(cp); + *wbp = addword(cp, *wbp); + } else + *wbp = glob(cp, *wbp); + } + quitenv(); + } else + gflg = 1; + return gflg == 0; +} + +/* + * Blank interpretation and quoting + */ +static char *blank(int f) +{ + int c, c1; + char *sp; + int scanequals, foundequals; + + DBGPRINTF3(("BLANK: enter, f=%d\n", f)); + + sp = e.linep; + scanequals = f & DOKEY; + foundequals = 0; + + loop: + switch (c = subgetc('"', foundequals)) { + case 0: + if (sp == e.linep) + return 0; + *e.linep++ = 0; + return sp; + + default: + if (f & DOBLANK && any(c, ifs->value)) + goto loop; + break; + + case '"': + case '\'': + scanequals = 0; + if (INSUB()) + break; + for (c1 = c; (c = subgetc(c1, 1)) != c1;) { + if (c == 0) + break; + if (c == '\'' || !any(c, "$`\"")) + c |= QUOTE; + *e.linep++ = c; + } + c = 0; + } + unget(c); + if (!isalpha(c) && c != '_') + scanequals = 0; + for (;;) { + c = subgetc('"', foundequals); + if (c == 0 || + f & (DOBLANK && any(c, ifs->value)) || + (!INSUB() && any(c, "\"'"))) { + scanequals = 0; + unget(c); + if (any(c, "\"'")) + goto loop; + break; + } + if (scanequals) { + if (c == '=') { + foundequals = 1; + scanequals = 0; + } else if (!isalnum(c) && c != '_') + scanequals = 0; + } + *e.linep++ = c; + } + *e.linep++ = 0; + return sp; +} + +/* + * Get characters, substituting for ` and $ + */ +static int subgetc(char ec, int quoted) +{ + char c; + + DBGPRINTF3(("SUBGETC: enter, quoted=%d\n", quoted)); + + again: + c = my_getc(ec); + if (!INSUB() && ec != '\'') { + if (c == '`') { + if (grave(quoted) == 0) + return 0; + e.iop->task = XGRAVE; + goto again; + } + if (c == '$' && (c = dollar(quoted)) == 0) { + e.iop->task = XDOLL; + goto again; + } + } + return c; +} + +/* + * Prepare to generate the string returned by ${} substitution. + */ +static int dollar(int quoted) +{ + int otask; + struct io *oiop; + char *dolp; + char *s, c, *cp = NULL; + struct var *vp; + + DBGPRINTF3(("DOLLAR: enter, quoted=%d\n", quoted)); + + c = readc(); + s = e.linep; + if (c != '{') { + *e.linep++ = c; + if (isalpha(c) || c == '_') { + while ((c = readc()) != 0 && (isalnum(c) || c == '_')) + if (e.linep < elinep) + *e.linep++ = c; + unget(c); + } + c = 0; + } else { + oiop = e.iop; + otask = e.iop->task; + + e.iop->task = XOTHER; + while ((c = subgetc('"', 0)) != 0 && c != '}' && c != '\n') + if (e.linep < elinep) + *e.linep++ = c; + if (oiop == e.iop) + e.iop->task = otask; + if (c != '}') { + err("unclosed ${"); + gflg++; + return c; + } + } + if (e.linep >= elinep) { + err("string in ${} too long"); + gflg++; + e.linep -= 10; + } + *e.linep = 0; + if (*s) + for (cp = s + 1; *cp; cp++) + if (any(*cp, "=-+?")) { + c = *cp; + *cp++ = 0; + break; + } + if (s[1] == 0 && (*s == '*' || *s == '@')) { + if (dolc > 1) { + /* currently this does not distinguish $* and $@ */ + /* should check dollar */ + e.linep = s; + PUSHIO(awordlist, dolv + 1, dolchar); + return 0; + } else { /* trap the nasty ${=} */ + s[0] = '1'; + s[1] = 0; + } + } + vp = lookup(s); + if ((dolp = vp->value) == null) { + switch (c) { + case '=': + if (isdigit(*s)) { + err("cannot use ${...=...} with $n"); + gflg++; + break; + } + setval(vp, cp); + dolp = vp->value; + break; + + case '-': + dolp = strsave(cp, areanum); + break; + + case '?': + if (*cp == 0) { + prs("missing value for "); + err(s); + } else + err(cp); + gflg++; + break; + } + } else if (c == '+') + dolp = strsave(cp, areanum); + if (flag['u'] && dolp == null) { + prs("unset variable: "); + err(s); + gflg++; + } + e.linep = s; + PUSHIO(aword, dolp, quoted ? qstrchar : strchar); + return 0; +} + +/* + * Run the command in `...` and read its output. + */ + +static int grave(int quoted) +{ + char *cp; + int i; + int j; + int pf[2]; + static char child_cmd[LINELIM]; + char *src; + char *dest; + int count; + int ignore; + int ignore_once; + char *argument_list[4]; + struct wdblock *wb = NULL; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &cp; +#endif + + for (cp = e.iop->argp->aword; *cp != '`'; cp++) + if (*cp == 0) { + err("no closing `"); + return 0; + } + + /* string copy with dollar expansion */ + src = e.iop->argp->aword; + dest = child_cmd; + count = 0; + ignore = 0; + ignore_once = 0; + while ((*src != '`') && (count < LINELIM)) { + if (*src == '\'') + ignore = !ignore; + if (*src == '\\') + ignore_once = 1; + if (*src == '$' && !ignore && !ignore_once) { + struct var *vp; + char var_name[LINELIM]; + char alt_value[LINELIM]; + int var_index = 0; + int alt_index = 0; + char operator = 0; + int braces = 0; + char *value; + + src++; + if (*src == '{') { + braces = 1; + src++; + } + + var_name[var_index++] = *src++; + while (isalnum(*src) || *src=='_') + var_name[var_index++] = *src++; + var_name[var_index] = 0; + + if (braces) { + switch (*src) { + case '}': + break; + case '-': + case '=': + case '+': + case '?': + operator = * src; + break; + default: + err("unclosed ${\n"); + return 0; + } + if (operator) { + src++; + while (*src && (*src != '}')) { + alt_value[alt_index++] = *src++; + } + alt_value[alt_index] = 0; + if (*src != '}') { + err("unclosed ${\n"); + return 0; + } + } + src++; + } + + if (isalpha(*var_name)) { + /* let subshell handle it instead */ + + char *namep = var_name; + + *dest++ = '$'; + if (braces) + *dest++ = '{'; + while (*namep) + *dest++ = *namep++; + if (operator) { + char *altp = alt_value; + *dest++ = operator; + while (*altp) + *dest++ = *altp++; + } + if (braces) + *dest++ = '}'; + + wb = addword(lookup(var_name)->name, wb); + } else { + /* expand */ + + vp = lookup(var_name); + if (vp->value != null) + value = (operator == '+') ? + alt_value : vp->value; + else if (operator == '?') { + err(alt_value); + return 0; + } else if (alt_index && (operator != '+')) { + value = alt_value; + if (operator == '=') + setval(vp, value); + } else + continue; + + while (*value && (count < LINELIM)) { + *dest++ = *value++; + count++; + } + } + } else { + *dest++ = *src++; + count++; + ignore_once = 0; + } + } + *dest = '\0'; + + if (openpipe(pf) < 0) + return 0; + + while ((i = vfork()) == -1 && errno == EAGAIN); + + DBGPRINTF3(("GRAVE: i is %p\n", io)); + + if (i < 0) { + closepipe(pf); + err((char *) bb_msg_memory_exhausted); + return 0; + } + if (i != 0) { + waitpid(i, NULL, 0); + e.iop->argp->aword = ++cp; + close(pf[1]); + PUSHIO(afile, remap(pf[0]), + (int (*)(struct ioarg *)) ((quoted) ? qgravechar : + gravechar)); + return 1; + } + /* allow trapped signals */ + /* XXX - Maybe this signal stuff should go as well? */ + for (j = 0; j <= _NSIG; j++) + if (ourtrap[j] && signal(j, SIG_IGN) != SIG_IGN) + signal(j, SIG_DFL); + + dup2(pf[1], 1); + closepipe(pf); + + argument_list[0] = (char *) DEFAULT_SHELL; + argument_list[1] = "-c"; + argument_list[2] = child_cmd; + argument_list[3] = 0; + + cp = rexecve(argument_list[0], argument_list, makenv(1, wb)); + prs(argument_list[0]); + prs(": "); + err(cp); + _exit(1); +} + + +static char *unquote(char *as) +{ + char *s; + + if ((s = as) != NULL) + while (*s) + *s++ &= ~QUOTE; + return as; +} + +/* -------- glob.c -------- */ + +/* + * glob + */ + +#define scopy(x) strsave((x), areanum) +#define BLKSIZ 512 +#define NDENT ((BLKSIZ+sizeof(struct dirent)-1)/sizeof(struct dirent)) + +static struct wdblock *cl, *nl; +static char spcl[] = "[?*"; + +static struct wdblock *glob(char *cp, struct wdblock *wb) +{ + int i; + char *pp; + + if (cp == 0) + return wb; + i = 0; + for (pp = cp; *pp; pp++) + if (any(*pp, spcl)) + i++; + else if (!any(*pp & ~QUOTE, spcl)) + *pp &= ~QUOTE; + if (i != 0) { + for (cl = addword(scopy(cp), (struct wdblock *) 0); anyspcl(cl); + cl = nl) { + nl = newword(cl->w_nword * 2); + for (i = 0; i < cl->w_nword; i++) { /* for each argument */ + for (pp = cl->w_words[i]; *pp; pp++) + if (any(*pp, spcl)) { + globname(cl->w_words[i], pp); + break; + } + if (*pp == '\0') + nl = addword(scopy(cl->w_words[i]), nl); + } + for (i = 0; i < cl->w_nword; i++) + DELETE(cl->w_words[i]); + DELETE(cl); + } + for (i = 0; i < cl->w_nword; i++) + unquote(cl->w_words[i]); + glob0((char *) cl->w_words, cl->w_nword, sizeof(char *), xstrcmp); + if (cl->w_nword) { + for (i = 0; i < cl->w_nword; i++) + wb = addword(cl->w_words[i], wb); + DELETE(cl); + return wb; + } + } + wb = addword(unquote(cp), wb); + return wb; +} + +static void globname(char *we, char *pp) +{ + char *np, *cp; + char *name, *gp, *dp; + int k; + DIR *dirp; + struct dirent *de; + char dname[NAME_MAX + 1]; + struct stat dbuf; + + for (np = we; np != pp; pp--) + if (pp[-1] == '/') + break; + for (dp = cp = space((int) (pp - np) + 3); np < pp;) + *cp++ = *np++; + *cp++ = '.'; + *cp = '\0'; + for (gp = cp = space(strlen(pp) + 1); *np && *np != '/';) + *cp++ = *np++; + *cp = '\0'; + dirp = opendir(dp); + if (dirp == 0) { + DELETE(dp); + DELETE(gp); + return; + } + dname[NAME_MAX] = '\0'; + while ((de = readdir(dirp)) != NULL) { + /* XXX Hmmm... What this could be? (abial) */ + /* + if (ent[j].d_ino == 0) + continue; + */ + strncpy(dname, de->d_name, NAME_MAX); + if (dname[0] == '.') + if (*gp != '.') + continue; + for (k = 0; k < NAME_MAX; k++) + if (any(dname[k], spcl)) + dname[k] |= QUOTE; + if (gmatch(dname, gp)) { + name = generate(we, pp, dname, np); + if (*np && !anys(np, spcl)) { + if (stat(name, &dbuf)) { + DELETE(name); + continue; + } + } + nl = addword(name, nl); + } + } + closedir(dirp); + DELETE(dp); + DELETE(gp); +} + +/* + * generate a pathname as below. + * start..end1 / middle end + * the slashes come for free + */ +static char *generate(char *start1, char *end1, char *middle, char *end) +{ + char *p; + char *op, *xp; + + p = op = + space((int) (end1 - start1) + strlen(middle) + strlen(end) + 2); + for (xp = start1; xp != end1;) + *op++ = *xp++; + for (xp = middle; (*op++ = *xp++) != '\0';); + op--; + for (xp = end; (*op++ = *xp++) != '\0';); + return p; +} + +static int anyspcl(struct wdblock *wb) +{ + int i; + char **wd; + + wd = wb->w_words; + for (i = 0; i < wb->w_nword; i++) + if (anys(spcl, *wd++)) + return 1; + return 0; +} + +static int xstrcmp(char *p1, char *p2) +{ + return strcmp(*(char **) p1, *(char **) p2); +} + +/* -------- word.c -------- */ + +static struct wdblock *newword(int nw) +{ + struct wdblock *wb; + + wb = (struct wdblock *) space(sizeof(*wb) + nw * sizeof(char *)); + wb->w_bsize = nw; + wb->w_nword = 0; + return wb; +} + +static struct wdblock *addword(char *wd, struct wdblock *wb) +{ + struct wdblock *wb2; + int nw; + + if (wb == NULL) + wb = newword(NSTART); + if ((nw = wb->w_nword) >= wb->w_bsize) { + wb2 = newword(nw * 2); + memcpy((char *) wb2->w_words, (char *) wb->w_words, + nw * sizeof(char *)); + wb2->w_nword = nw; + DELETE(wb); + wb = wb2; + } + wb->w_words[wb->w_nword++] = wd; + return wb; +} + +static +char **getwords(struct wdblock *wb) +{ + char **wd; + int nb; + + if (wb == NULL) + return NULL; + if (wb->w_nword == 0) { + DELETE(wb); + return NULL; + } + wd = (char **) space(nb = sizeof(*wd) * wb->w_nword); + memcpy((char *) wd, (char *) wb->w_words, nb); + DELETE(wb); /* perhaps should done by caller */ + return wd; +} + +static int (*func) (char *, char *); +static int globv; + +static void glob0(char *a0, unsigned a1, int a2, int (*a3) (char *, char *)) +{ + func = a3; + globv = a2; + glob1(a0, a0 + a1 * a2); +} + +static void glob1(char *base, char *lim) +{ + char *i, *j; + int v2; + char *lptr, *hptr; + int c; + unsigned n; + + + v2 = globv; + + top: + if ((n = (int) (lim - base)) <= v2) + return; + n = v2 * (n / (2 * v2)); + hptr = lptr = base + n; + i = base; + j = lim - v2; + for (;;) { + if (i < lptr) { + if ((c = (*func) (i, lptr)) == 0) { + glob2(i, lptr -= v2); + continue; + } + if (c < 0) { + i += v2; + continue; + } + } + + begin: + if (j > hptr) { + if ((c = (*func) (hptr, j)) == 0) { + glob2(hptr += v2, j); + goto begin; + } + if (c > 0) { + if (i == lptr) { + glob3(i, hptr += v2, j); + i = lptr += v2; + goto begin; + } + glob2(i, j); + j -= v2; + i += v2; + continue; + } + j -= v2; + goto begin; + } + + + if (i == lptr) { + if (lptr - base >= lim - hptr) { + glob1(hptr + v2, lim); + lim = lptr; + } else { + glob1(base, lptr); + base = hptr + v2; + } + goto top; + } + + + glob3(j, lptr -= v2, i); + j = hptr -= v2; + } +} + +static void glob2(char *i, char *j) +{ + char *index1, *index2, c; + int m; + + m = globv; + index1 = i; + index2 = j; + do { + c = *index1; + *index1++ = *index2; + *index2++ = c; + } while (--m); +} + +static void glob3(char *i, char *j, char *k) +{ + char *index1, *index2, *index3; + int c; + int m; + + m = globv; + index1 = i; + index2 = j; + index3 = k; + do { + c = *index1; + *index1++ = *index3; + *index3++ = *index2; + *index2++ = c; + } while (--m); +} + +/* -------- io.c -------- */ + +/* + * shell IO + */ + +static int my_getc(int ec) +{ + int c; + + if (e.linep > elinep) { + while ((c = readc()) != '\n' && c); + err("input line too long"); + gflg++; + return c; + } + c = readc(); + if ((ec != '\'') && (ec != '`') && (e.iop->task != XGRAVE)) { + if (c == '\\') { + c = readc(); + if (c == '\n' && ec != '\"') + return my_getc(ec); + c |= QUOTE; + } + } + return c; +} + +static void unget(int c) +{ + if (e.iop >= e.iobase) + e.iop->peekc = c; +} + +static int eofc(void) +{ + return e.iop < e.iobase || (e.iop->peekc == 0 && e.iop->prev == 0); +} + +static int readc(void) +{ + int c; + + RCPRINTF(("READC: e.iop %p, e.iobase %p\n", e.iop, e.iobase)); + + for (; e.iop >= e.iobase; e.iop--) { + RCPRINTF(("READC: e.iop %p, peekc 0x%x\n", e.iop, e.iop->peekc)); + if ((c = e.iop->peekc) != '\0') { + e.iop->peekc = 0; + return c; + } else { + if (e.iop->prev != 0) { + if ((c = (*e.iop->iofn) (e.iop->argp, e.iop)) != '\0') { + if (c == -1) { + e.iop++; + continue; + } + if (e.iop == iostack) + ioecho(c); + return (e.iop->prev = c); + } else if (e.iop->task == XIO && e.iop->prev != '\n') { + e.iop->prev = 0; + if (e.iop == iostack) + ioecho('\n'); + return '\n'; + } + } + if (e.iop->task == XIO) { + if (multiline) { + return e.iop->prev = 0; + } + if (interactive && e.iop == iostack + 1) { +#ifdef CONFIG_FEATURE_COMMAND_EDITING + current_prompt = prompt->value; +#else + prs(prompt->value); +#endif + } + } + } + + } /* FOR */ + + if (e.iop >= iostack) { + RCPRINTF(("READC: return 0, e.iop %p\n", e.iop)); + return 0; + } + + DBGPRINTF(("READC: leave()...\n")); + leave(); + + /* NOTREACHED */ + return 0; +} + +static void ioecho(char c) +{ + if (flag['v']) + write(2, &c, sizeof c); +} + + +static void pushio(struct ioarg *argp, int (*fn) (struct ioarg *)) +{ + DBGPRINTF(("PUSHIO: argp %p, argp->afid 0x%x, e.iop %p\n", argp, + argp->afid, e.iop)); + + /* Set env ptr for io source to next array spot and check for array overflow */ + if (++e.iop >= &iostack[NPUSH]) { + e.iop--; + err("Shell input nested too deeply"); + gflg++; + return; + } + + /* We did not overflow the NPUSH array spots so setup data structs */ + + e.iop->iofn = (int (*)(struct ioarg *, struct io *)) fn; /* Store data source func ptr */ + + if (argp->afid != AFID_NOBUF) + e.iop->argp = argp; + else { + + e.iop->argp = ioargstack + (e.iop - iostack); /* MAL - index into stack */ + *e.iop->argp = *argp; /* copy data from temp area into stack spot */ + + /* MAL - mainbuf is for 1st data source (command line?) and all nested use a single shared buffer? */ + + if (e.iop == &iostack[0]) + e.iop->argp->afbuf = &mainbuf; + else + e.iop->argp->afbuf = &sharedbuf; + + /* MAL - if not a termimal AND (commandline OR readable file) then give it a buffer id? */ + /* This line appears to be active when running scripts from command line */ + if ((isatty(e.iop->argp->afile) == 0) + && (e.iop == &iostack[0] + || lseek(e.iop->argp->afile, 0L, SEEK_CUR) != -1)) { + if (++bufid == AFID_NOBUF) /* counter rollover check, AFID_NOBUF = 11111111 */ + bufid = AFID_ID; /* AFID_ID = 0 */ + + e.iop->argp->afid = bufid; /* assign buffer id */ + } + + DBGPRINTF(("PUSHIO: iostack %p, e.iop %p, afbuf %p\n", + iostack, e.iop, e.iop->argp->afbuf)); + DBGPRINTF(("PUSHIO: mbuf %p, sbuf %p, bid %d, e.iop %p\n", + &mainbuf, &sharedbuf, bufid, e.iop)); + + } + + e.iop->prev = ~'\n'; + e.iop->peekc = 0; + e.iop->xchar = 0; + e.iop->nlcount = 0; + + if (fn == filechar || fn == linechar) + e.iop->task = XIO; + else if (fn == (int (*)(struct ioarg *)) gravechar + || fn == (int (*)(struct ioarg *)) qgravechar) + e.iop->task = XGRAVE; + else + e.iop->task = XOTHER; + + return; +} + +static struct io *setbase(struct io *ip) +{ + struct io *xp; + + xp = e.iobase; + e.iobase = ip; + return xp; +} + +/* + * Input generating functions + */ + +/* + * Produce the characters of a string, then a newline, then EOF. + */ +static int nlchar(struct ioarg *ap) +{ + int c; + + if (ap->aword == NULL) + return 0; + if ((c = *ap->aword++) == 0) { + ap->aword = NULL; + return '\n'; + } + return c; +} + +/* + * Given a list of words, produce the characters + * in them, with a space after each word. + */ +static int wdchar(struct ioarg *ap) +{ + char c; + char **wl; + + if ((wl = ap->awordlist) == NULL) + return 0; + if (*wl != NULL) { + if ((c = *(*wl)++) != 0) + return c & 0177; + ap->awordlist++; + return ' '; + } + ap->awordlist = NULL; + return '\n'; +} + +/* + * Return the characters of a list of words, + * producing a space between them. + */ +static int dolchar(struct ioarg *ap) +{ + char *wp; + + if ((wp = *ap->awordlist++) != NULL) { + PUSHIO(aword, wp, *ap->awordlist == NULL ? strchar : xxchar); + return -1; + } + return 0; +} + +static int xxchar(struct ioarg *ap) +{ + int c; + + if (ap->aword == NULL) + return 0; + if ((c = *ap->aword++) == '\0') { + ap->aword = NULL; + return ' '; + } + return c; +} + +/* + * Produce the characters from a single word (string). + */ +static int strchar(struct ioarg *ap) +{ + int c; + + if (ap->aword == NULL || (c = *ap->aword++) == 0) + return 0; + return c; +} + +/* + * Produce quoted characters from a single word (string). + */ +static int qstrchar(struct ioarg *ap) +{ + int c; + + if (ap->aword == NULL || (c = *ap->aword++) == 0) + return 0; + return c | QUOTE; +} + +/* + * Return the characters from a file. + */ +static int filechar(struct ioarg *ap) +{ + int i; + char c; + struct iobuf *bp = ap->afbuf; + + if (ap->afid != AFID_NOBUF) { + if ((i = ap->afid != bp->id) || bp->bufp == bp->ebufp) { + + if (i) + lseek(ap->afile, ap->afpos, SEEK_SET); + + i = safe_read(ap->afile, bp->buf, sizeof(bp->buf)); + + if (i <= 0) { + closef(ap->afile); + return 0; + } + + bp->id = ap->afid; + bp->ebufp = (bp->bufp = bp->buf) + i; + } + + ap->afpos++; + return *bp->bufp++ & 0177; + } +#ifdef CONFIG_FEATURE_COMMAND_EDITING + if (interactive && isatty(ap->afile)) { + static char mycommand[BUFSIZ]; + static int position = 0, size = 0; + + while (size == 0 || position >= size) { + cmdedit_read_input(current_prompt, mycommand); + size = strlen(mycommand); + position = 0; + } + c = mycommand[position]; + position++; + return c; + } else +#endif + + { + i = safe_read(ap->afile, &c, sizeof(c)); + return i == sizeof(c) ? (c & 0x7f) : (closef(ap->afile), 0); + } +} + +/* + * Return the characters from a here temp file. + */ +static int herechar(struct ioarg *ap) +{ + char c; + + + if (read(ap->afile, &c, sizeof(c)) != sizeof(c)) { + close(ap->afile); + c = 0; + } + return c; + +} + +/* + * Return the characters produced by a process (`...`). + * Quote them if required, and remove any trailing newline characters. + */ +static int gravechar(struct ioarg *ap, struct io *iop) +{ + int c; + + if ((c = qgravechar(ap, iop) & ~QUOTE) == '\n') + c = ' '; + return c; +} + +static int qgravechar(struct ioarg *ap, struct io *iop) +{ + int c; + + DBGPRINTF3(("QGRAVECHAR: enter, ap=%p, iop=%p\n", ap, iop)); + + if (iop->xchar) { + if (iop->nlcount) { + iop->nlcount--; + return '\n' | QUOTE; + } + c = iop->xchar; + iop->xchar = 0; + } else if ((c = filechar(ap)) == '\n') { + iop->nlcount = 1; + while ((c = filechar(ap)) == '\n') + iop->nlcount++; + iop->xchar = c; + if (c == 0) + return c; + iop->nlcount--; + c = '\n'; + } + return c != 0 ? c | QUOTE : 0; +} + +/* + * Return a single command (usually the first line) from a file. + */ +static int linechar(struct ioarg *ap) +{ + int c; + + if ((c = filechar(ap)) == '\n') { + if (!multiline) { + closef(ap->afile); + ap->afile = -1; /* illegal value */ + } + } + return c; +} + +static void prs(const char *s) +{ + if (*s) + write(2, s, strlen(s)); +} + +static void prn(unsigned u) +{ + prs(itoa(u)); +} + +static void closef(int i) +{ + if (i > 2) + close(i); +} + +static void closeall(void) +{ + int u; + + for (u = NUFILE; u < NOFILE;) + close(u++); +} + + +/* + * remap fd into Shell's fd space + */ +static int remap(int fd) +{ + int i; + int map[NOFILE]; + int newfd; + + + DBGPRINTF(("REMAP: fd=%d, e.iofd=%d\n", fd, e.iofd)); + + if (fd < e.iofd) { + for (i = 0; i < NOFILE; i++) + map[i] = 0; + + do { + map[fd] = 1; + newfd = dup(fd); + fd = newfd; + } while (fd >= 0 && fd < e.iofd); + + for (i = 0; i < NOFILE; i++) + if (map[i]) + close(i); + + if (fd < 0) + err("too many files open in shell"); + } + + return fd; +} + +static int openpipe(int *pv) +{ + int i; + + if ((i = pipe(pv)) < 0) + err("can't create pipe - try again"); + return i; +} + +static void closepipe(int *pv) +{ + if (pv != NULL) { + close(*pv++); + close(*pv); + } +} + +/* -------- here.c -------- */ + +/* + * here documents + */ + +static void markhere(char *s, struct ioword *iop) +{ + struct here *h, *lh; + + DBGPRINTF7(("MARKHERE: enter, s=%p\n", s)); + + h = (struct here *) space(sizeof(struct here)); + if (h == 0) + return; + + h->h_tag = evalstr(s, DOSUB); + if (h->h_tag == 0) + return; + + h->h_iop = iop; + iop->io_name = 0; + h->h_next = NULL; + if (inhere == 0) + inhere = h; + else + for (lh = inhere; lh != NULL; lh = lh->h_next) + if (lh->h_next == 0) { + lh->h_next = h; + break; + } + iop->io_flag |= IOHERE | IOXHERE; + for (s = h->h_tag; *s; s++) + if (*s & QUOTE) { + iop->io_flag &= ~IOXHERE; + *s &= ~QUOTE; + } + h->h_dosub = iop->io_flag & IOXHERE; +} + +static void gethere(void) +{ + struct here *h, *hp; + + DBGPRINTF7(("GETHERE: enter...\n")); + + /* Scan here files first leaving inhere list in place */ + for (hp = h = inhere; h != NULL; hp = h, h = h->h_next) + readhere(&h->h_iop->io_name, h->h_tag, h->h_dosub ? 0 : '\''); + + /* Make inhere list active - keep list intact for scraphere */ + if (hp != NULL) { + hp->h_next = acthere; + acthere = inhere; + inhere = NULL; + } +} + +static void readhere(char **name, char *s, int ec) +{ + int tf; + char tname[30] = ".msh_XXXXXX"; + int c; + jmp_buf ev; + char myline[LINELIM + 1]; + char *thenext; + + DBGPRINTF7(("READHERE: enter, name=%p, s=%p\n", name, s)); + + tf = mkstemp(tname); + if (tf < 0) + return; + + *name = strsave(tname, areanum); + if (newenv(setjmp(errpt = ev)) != 0) + unlink(tname); + else { + pushio(e.iop->argp, (int (*)(struct ioarg *)) e.iop->iofn); + e.iobase = e.iop; + for (;;) { + if (interactive && e.iop <= iostack) { +#ifdef CONFIG_FEATURE_COMMAND_EDITING + current_prompt = cprompt->value; +#else + prs(cprompt->value); +#endif + } + thenext = myline; + while ((c = my_getc(ec)) != '\n' && c) { + if (ec == '\'') + c &= ~QUOTE; + if (thenext >= &myline[LINELIM]) { + c = 0; + break; + } + *thenext++ = c; + } + *thenext = 0; + if (strcmp(s, myline) == 0 || c == 0) + break; + *thenext++ = '\n'; + write(tf, myline, (int) (thenext - myline)); + } + if (c == 0) { + prs("here document `"); + prs(s); + err("' unclosed"); + } + quitenv(); + } + close(tf); +} + +/* + * open here temp file. + * if unquoted here, expand here temp file into second temp file. + */ +static int herein(char *hname, int xdoll) +{ + int hf; + int tf; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &tf; +#endif + if (hname == NULL) + return -1; + + DBGPRINTF7(("HEREIN: hname is %s, xdoll=%d\n", hname, xdoll)); + + hf = open(hname, 0); + if (hf < 0) + return -1; + + if (xdoll) { + char c; + char tname[30] = ".msh_XXXXXX"; + jmp_buf ev; + + tf = mkstemp(tname); + if (tf < 0) + return -1; + if (newenv(setjmp(errpt = ev)) == 0) { + PUSHIO(afile, hf, herechar); + setbase(e.iop); + while ((c = subgetc(0, 0)) != 0) { + c &= ~QUOTE; + write(tf, &c, sizeof c); + } + quitenv(); + } else + unlink(tname); + close(tf); + tf = open(tname, 0); + unlink(tname); + return tf; + } else + return hf; +} + +static void scraphere(void) +{ + struct here *h; + + DBGPRINTF7(("SCRAPHERE: enter...\n")); + + for (h = inhere; h != NULL; h = h->h_next) { + if (h->h_iop && h->h_iop->io_name) + unlink(h->h_iop->io_name); + } + inhere = NULL; +} + +/* unlink here temp files before a freearea(area) */ +static void freehere(int area) +{ + struct here *h, *hl; + + DBGPRINTF6(("FREEHERE: enter, area=%d\n", area)); + + hl = NULL; + for (h = acthere; h != NULL; h = h->h_next) + if (getarea((char *) h) >= area) { + if (h->h_iop->io_name != NULL) + unlink(h->h_iop->io_name); + if (hl == NULL) + acthere = h->h_next; + else + hl->h_next = h->h_next; + } else + hl = h; +} + + + +/* + * Copyright (c) 1987,1997, Prentice Hall + * All rights reserved. + * + * Redistribution and use of the MINIX operating system in source and + * binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * Neither the name of Prentice Hall nor the names of the software + * authors or contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS, AUTHORS, AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL PRENTICE HALL OR ANY AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ diff --git a/sysklogd/Config.in b/sysklogd/Config.in new file mode 100644 index 000000000..9875b3c12 --- /dev/null +++ b/sysklogd/Config.in @@ -0,0 +1,111 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "System Logging Utilities" + +config SYSLOGD + bool "syslogd" + default n + help + The syslogd utility is used to record logs of all the + significant events that occur on a system. Every + message that is logged records the date and time of the + event, and will generally also record the name of the + application that generated the message. When used in + conjunction with klogd, messages from the Linux kernel + can also be recorded. This is terribly useful, + especially for finding what happened when something goes + wrong. And something almost always will go wrong if + you wait long enough.... + +config FEATURE_ROTATE_LOGFILE + bool "Rotate message files" + default n + depends on SYSLOGD + help + This enables syslogd to rotate the message files + on his own. No need to use an external rotatescript. + +config FEATURE_REMOTE_LOG + bool "Remote Log support" + default n + depends on SYSLOGD + help + When you enable this feature, the syslogd utility can + be used to send system log messages to another system + connected via a network. This allows the remote + machine to log all the system messages, which can be + terribly useful for reducing the number of serial + cables you use. It can also be a very good security + measure to prevent system logs from being tampered with + by an intruder. + +config FEATURE_IPC_SYSLOG + bool "Circular Buffer support" + default n + depends on SYSLOGD + help + When you enable this feature, the syslogd utility will + use a circular buffer to record system log messages. + When the buffer is filled it will continue to overwrite + the oldest messages. This can be very useful for + systems with little or no permanent storage, since + otherwise system logs can eventually fill up your + entire filesystem, which may cause your system to + break badly. + +config FEATURE_IPC_SYSLOG_BUFFER_SIZE + int " Circular buffer size in Kbytes (minimum 4KB)" + default 16 + depends on FEATURE_IPC_SYSLOG + help + This option sets the size of the circular buffer + used to record system log messages. + +config LOGREAD + bool "logread" + default y + depends on FEATURE_IPC_SYSLOG + help + If you enabled Circular Buffer support, you almost + certainly want to enable this feature as well. This + utility will allow you to read the messages that are + stored in the syslogd circular buffer. + +config FEATURE_LOGREAD_REDUCED_LOCKING + bool "logread double buffering" + default n + depends on LOGREAD + help + 'logread' ouput to slow serial terminals can have + side effects on syslog because of the semaphore. + This option make logread to double buffer copy + from circular buffer, minimizing semaphore + contention at some minor memory expense. + +config KLOGD + bool "klogd" + default n + depends on SYSLOGD + select FEATURE_SYSLOG + help + klogd is a utility which intercepts and logs all + messages from the Linux kernel and sends the messages + out to the 'syslogd' utility so they can be logged. If + you wish to record the messages produced by the kernel, + you should enable this option. + +config LOGGER + bool "logger" + default n + select FEATURE_SYSLOG + help + The logger utility allows you to send arbitrary text + messages to the system log (i.e. the 'syslogd' utility) so + they can be logged. This is generally used to help locate + problems that occur within programs and scripts. + +endmenu + diff --git a/sysklogd/Kbuild b/sysklogd/Kbuild new file mode 100644 index 000000000..0d5b2b929 --- /dev/null +++ b/sysklogd/Kbuild @@ -0,0 +1,11 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org> +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_KLOGD) += klogd.o +lib-$(CONFIG_LOGGER) += logger.o +lib-$(CONFIG_LOGREAD) += logread.o +lib-$(CONFIG_SYSLOGD) += syslogd.o diff --git a/sysklogd/klogd.c b/sysklogd/klogd.c new file mode 100644 index 000000000..f735d9fc1 --- /dev/null +++ b/sysklogd/klogd.c @@ -0,0 +1,123 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini klogd implementation for busybox + * + * Copyright (C) 2001 by Gennady Feldman <gfeldman@gena01.com>. + * Changes: Made this a standalone busybox module which uses standalone + * syslog() client interface. + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Copyright (C) 2000 by Karl M. Hegbloom <karlheg@debian.org> + * + * "circular buffer" Copyright (C) 2000 by Gennady Feldman <gfeldman@gena01.com> + * + * Maintainer: Gennady Feldman <gfeldman@gena01.com> as of Mar 12, 2001 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include <sys/syslog.h> +#include <sys/klog.h> + +static void klogd_signal(int sig ATTRIBUTE_UNUSED) +{ + klogctl(7, NULL, 0); + klogctl(0, 0, 0); + /* logMessage(0, "Kernel log daemon exiting."); */ + syslog(LOG_NOTICE, "Kernel log daemon exiting."); + exit(EXIT_SUCCESS); +} + +#define OPT_LEVEL 1 +#define OPT_FOREGROUND 2 + +#define KLOGD_LOGBUF_SIZE 4096 + +int klogd_main(int argc, char **argv) +{ + RESERVE_CONFIG_BUFFER(log_buffer, KLOGD_LOGBUF_SIZE); + int console_log_level = -1; + int priority = LOG_INFO; + int i, n, lastc; + char *start; + + { + unsigned opt; + + /* do normal option parsing */ + opt = getopt32(argc, argv, "c:n", &start); + + if (opt & OPT_LEVEL) { + /* Valid levels are between 1 and 8 */ + console_log_level = xatoul_range(start, 1, 8); + } + + if (!(opt & OPT_FOREGROUND)) { +#ifdef BB_NOMMU + vfork_daemon_rexec(0, 1, argc, argv, "-n"); +#else + xdaemon(0, 1); +#endif + } + } + + openlog("kernel", 0, LOG_KERN); + + /* Set up sig handlers */ + signal(SIGINT, klogd_signal); + signal(SIGKILL, klogd_signal); + signal(SIGTERM, klogd_signal); + signal(SIGHUP, SIG_IGN); + + /* "Open the log. Currently a NOP." */ + klogctl(1, NULL, 0); + + /* Set level of kernel console messaging.. */ + if (console_log_level != -1) + klogctl(8, NULL, console_log_level); + + syslog(LOG_NOTICE, "klogd started: %s", BB_BANNER); + + while (1) { + /* Use kernel syscalls */ + memset(log_buffer, '\0', KLOGD_LOGBUF_SIZE); + n = klogctl(2, log_buffer, KLOGD_LOGBUF_SIZE); + if (n < 0) { + if (errno == EINTR) + continue; + syslog(LOG_ERR, "klogd: Error from sys_sycall: %d - %m.\n", + errno); + exit(EXIT_FAILURE); + } + + /* klogctl buffer parsing modelled after code in dmesg.c */ + start = &log_buffer[0]; + lastc = '\0'; + for (i = 0; i < n; i++) { + if (lastc == '\0' && log_buffer[i] == '<') { + priority = 0; + i++; + while (log_buffer[i] >= '0' && log_buffer[i] <= '9') { + priority = priority * 10 + (log_buffer[i] - '0'); + i++; + } + if (log_buffer[i] == '>') + i++; + start = &log_buffer[i]; + } + if (log_buffer[i] == '\n') { + log_buffer[i] = '\0'; /* zero terminate this message */ + syslog(priority, "%s", start); + start = &log_buffer[i + 1]; + priority = LOG_INFO; + } + lastc = log_buffer[i]; + } + } + if (ENABLE_FEATURE_CLEAN_UP) + RELEASE_CONFIG_BUFFER(log_buffer); + + return EXIT_SUCCESS; +} diff --git a/sysklogd/logger.c b/sysklogd/logger.c new file mode 100644 index 000000000..8901bd79f --- /dev/null +++ b/sysklogd/logger.c @@ -0,0 +1,179 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini logger implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> + +#if !defined CONFIG_SYSLOGD + +#define SYSLOG_NAMES +#include <sys/syslog.h> + +#else +#include <sys/syslog.h> +# ifndef __dietlibc__ + /* We have to do this since the header file defines static + * structures. Argh.... bad libc, bad, bad... + */ + typedef struct _code { + char *c_name; + int c_val; + } CODE; + extern CODE prioritynames[]; + extern CODE facilitynames[]; +# endif +#endif + +/* Decode a symbolic name to a numeric value + * this function is based on code + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Original copyright notice is retained at the end of this file. + */ +static int decode(char *name, CODE * codetab) +{ + CODE *c; + + if (isdigit(*name)) + return atoi(name); + for (c = codetab; c->c_name; c++) { + if (!strcasecmp(name, c->c_name)) { + return c->c_val; + } + } + + return -1; +} + +/* Decode a symbolic name to a numeric value + * this function is based on code + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Original copyright notice is retained at the end of this file. + */ +static int pencode(char *s) +{ + char *save; + int lev, fac = LOG_USER; + + for (save = s; *s && *s != '.'; ++s); + if (*s) { + *s = '\0'; + fac = decode(save, facilitynames); + if (fac < 0) + bb_error_msg_and_die("unknown facility name: %s", save); + *s++ = '.'; + } else { + s = save; + } + lev = decode(s, prioritynames); + if (lev < 0) + bb_error_msg_and_die("unknown priority name: %s", save); + return ((lev & LOG_PRIMASK) | (fac & LOG_FACMASK)); +} + + +int logger_main(int argc, char **argv) +{ + unsigned opt; + char *opt_p, *opt_t; + int pri = LOG_USER | LOG_NOTICE; + int option = 0; + int c, i; + char buf[1024], name[128]; + + /* Fill out the name string early (may be overwritten later) */ + bb_getpwuid(name, geteuid(), sizeof(name)); + + /* Parse any options */ + opt = getopt32(argc, argv, "p:st:", &opt_p, &opt_t); + if (opt & 0x1) pri = pencode(opt_p); // -p + if (opt & 0x2) option |= LOG_PERROR; // -s + if (opt & 0x4) safe_strncpy(name, opt_t, sizeof(name)); // -t + + openlog(name, option, 0); + if (optind == argc) { + do { + /* read from stdin */ + i = 0; + while ((c = getc(stdin)) != EOF && c != '\n' && + i < (sizeof(buf)-1)) { + buf[i++] = c; + } + if (i > 0) { + buf[i++] = '\0'; + syslog(pri, "%s", buf); + } + } while (c != EOF); + } else { + char *message = NULL; + int len = argc - optind; /* for the space between the args + and '\0' */ + opt = len; + argv += optind; + for (i = 0; i < opt; i++) { + len += strlen(*argv); + message = xrealloc(message, len); + if(!i) + message[0] = '\0'; + else + strcat(message, " "); + strcat(message, *argv); + argv++; + } + syslog(pri, "%s", message); + } + + closelog(); + return EXIT_SUCCESS; +} + + +/*- + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * This is the original license statement for the decode and pencode functions. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change + * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change> + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/sysklogd/logread.c b/sysklogd/logread.c new file mode 100644 index 000000000..9cc6561a4 --- /dev/null +++ b/sysklogd/logread.c @@ -0,0 +1,171 @@ +/* vi: set sw=4 ts=4: */ +/* + * circular buffer syslog implementation for busybox + * + * Copyright (C) 2000 by Gennady Feldman <gfeldman@gena01.com> + * + * Maintainer: Gennady Feldman <gfeldman@gena01.com> as of Mar 12, 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + + +#include "busybox.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ipc.h> +#include <sys/types.h> +#include <sys/sem.h> +#include <sys/shm.h> +#include <signal.h> +#include <setjmp.h> +#include <unistd.h> + +static const long KEY_ID = 0x414e4547; /*"GENA"*/ + +static struct shbuf_ds { + int size; // size of data written + int head; // start of message list + int tail; // end of message list + char data[1]; // data/messages +} *buf = NULL; // shared memory pointer + + +// Semaphore operation structures +static struct sembuf SMrup[1] = {{0, -1, IPC_NOWAIT | SEM_UNDO}}; // set SMrup +static struct sembuf SMrdn[2] = {{1, 0}, {0, +1, SEM_UNDO}}; // set SMrdn + +static int log_shmid = -1; // ipc shared memory id +static int log_semid = -1; // ipc semaphore id +static jmp_buf jmp_env; + +static void error_exit(const char *str); +static void interrupted(int sig); + +/* + * sem_up - up()'s a semaphore. + */ +static void sem_up(int semid) +{ + if ( semop(semid, SMrup, 1) == -1 ) + error_exit("semop[SMrup]"); +} + +/* + * sem_down - down()'s a semaphore + */ +static void sem_down(int semid) +{ + if ( semop(semid, SMrdn, 2) == -1 ) + error_exit("semop[SMrdn]"); +} + +int logread_main(int argc, char **argv) +{ + int i; + int follow=0; + + if (argc == 2 && argv[1][0]=='-' && argv[1][1]=='f') { + follow = 1; + } else { + /* no options, no getopt */ + if (argc > 1) + bb_show_usage(); + } + + // handle interrupt signal + if (setjmp(jmp_env)) goto output_end; + + // attempt to redefine ^C signal + signal(SIGINT, interrupted); + + if ( (log_shmid = shmget(KEY_ID, 0, 0)) == -1) + error_exit("Can't find circular buffer"); + + // Attach shared memory to our char* + if ( (buf = shmat(log_shmid, NULL, SHM_RDONLY)) == NULL) + error_exit("Can't get access to circular buffer from syslogd"); + + if ( (log_semid = semget(KEY_ID, 0, 0)) == -1) + error_exit("Can't get access to semaphore(s) for circular buffer from syslogd"); + + // Suppose atomic memory move + i = follow ? buf->tail : buf->head; + + do { +#ifdef CONFIG_FEATURE_LOGREAD_REDUCED_LOCKING + char *buf_data; + int log_len,j; +#endif + + sem_down(log_semid); + + //printf("head: %i tail: %i size: %i\n",buf->head,buf->tail,buf->size); + if (buf->head == buf->tail || i==buf->tail) { + if (follow) { + sem_up(log_semid); + sleep(1); /* TODO: replace me with a sleep_on */ + continue; + } else { + printf("<empty syslog>\n"); + } + } + + // Read Memory +#ifdef CONFIG_FEATURE_LOGREAD_REDUCED_LOCKING + log_len = buf->tail - i; + if (log_len < 0) + log_len += buf->size; + buf_data = (char *)xmalloc(log_len); + + if (buf->tail < i) { + memcpy(buf_data, buf->data+i, buf->size-i); + memcpy(buf_data+buf->size-i, buf->data, buf->tail); + } else { + memcpy(buf_data, buf->data+i, buf->tail-i); + } + i = buf->tail; + +#else + while ( i != buf->tail) { + printf("%s", buf->data+i); + i+= strlen(buf->data+i) + 1; + if (i >= buf->size ) + i=0; + } +#endif + // release the lock on the log chain + sem_up(log_semid); + +#ifdef CONFIG_FEATURE_LOGREAD_REDUCED_LOCKING + for (j=0; j < log_len; j+=strlen(buf_data+j)+1) { + printf("%s", buf_data+j); + if (follow) + fflush(stdout); + } + free(buf_data); +#endif + fflush(stdout); + } while (follow); + +output_end: + if (log_shmid != -1) + shmdt(buf); + + return EXIT_SUCCESS; +} + +static void interrupted(int sig){ + signal(SIGINT, SIG_IGN); + longjmp(jmp_env, 1); +} + +static void error_exit(const char *str){ + bb_perror_msg(str); + //release all acquired resources + if (log_shmid != -1) + shmdt(buf); + + exit(1); +} diff --git a/sysklogd/syslogd.c b/sysklogd/syslogd.c new file mode 100644 index 000000000..453cbda35 --- /dev/null +++ b/sysklogd/syslogd.c @@ -0,0 +1,643 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini syslogd implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Copyright (C) 2000 by Karl M. Hegbloom <karlheg@debian.org> + * + * "circular buffer" Copyright (C) 2001 by Gennady Feldman <gfeldman@gena01.com> + * + * Maintainer: Gennady Feldman <gfeldman@gena01.com> as of Mar 12, 2001 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include <paths.h> +#include <stdbool.h> +#include <sys/un.h> + +/* SYSLOG_NAMES defined to pull some extra junk from syslog.h */ +#define SYSLOG_NAMES +#include <sys/syslog.h> +#include <sys/uio.h> + +/* Path to the unix socket */ +static char lfile[MAXPATHLEN]; + +/* Path for the file where all log messages are written */ +static const char *logFilePath = "/var/log/messages"; + +#ifdef CONFIG_FEATURE_ROTATE_LOGFILE +/* max size of message file before being rotated */ +static int logFileSize = 200 * 1024; + +/* number of rotated message files */ +static int logFileRotate = 1; +#endif + +/* interval between marks in seconds */ +static int MarkInterval = 20 * 60; + +/* level of messages to be locally logged */ +static int logLevel = 8; + +/* localhost's name */ +static char LocalHostName[64]; + +#ifdef CONFIG_FEATURE_REMOTE_LOG +#include <netinet/in.h> +/* udp socket for logging to remote host */ +static int remotefd = -1; +static struct sockaddr_in remoteaddr; + +#endif + +/* options */ +/* Correct regardless of combination of CONFIG_xxx */ +enum { + OPTBIT_mark = 0, // -m + OPTBIT_nofork, // -n + OPTBIT_outfile, // -O + OPTBIT_loglevel, // -l + OPTBIT_small, // -S + USE_FEATURE_ROTATE_LOGFILE(OPTBIT_filesize ,) // -s + USE_FEATURE_ROTATE_LOGFILE(OPTBIT_rotatecnt ,) // -b + USE_FEATURE_REMOTE_LOG( OPTBIT_remote ,) // -R + USE_FEATURE_REMOTE_LOG( OPTBIT_localtoo ,) // -L + USE_FEATURE_IPC_SYSLOG( OPTBIT_circularlog,) // -C + + OPT_mark = 1 << OPTBIT_mark , + OPT_nofork = 1 << OPTBIT_nofork , + OPT_outfile = 1 << OPTBIT_outfile , + OPT_loglevel = 1 << OPTBIT_loglevel, + OPT_small = 1 << OPTBIT_small , + OPT_filesize = USE_FEATURE_ROTATE_LOGFILE((1 << OPTBIT_filesize )) + 0, + OPT_rotatecnt = USE_FEATURE_ROTATE_LOGFILE((1 << OPTBIT_rotatecnt )) + 0, + OPT_remotelog = USE_FEATURE_REMOTE_LOG( (1 << OPTBIT_remote )) + 0, + OPT_locallog = USE_FEATURE_REMOTE_LOG( (1 << OPTBIT_localtoo )) + 0, + OPT_circularlog = USE_FEATURE_IPC_SYSLOG( (1 << OPTBIT_circularlog)) + 0, +}; +#define OPTION_STR "m:nO:l:S" \ + USE_FEATURE_ROTATE_LOGFILE("s:" ) \ + USE_FEATURE_ROTATE_LOGFILE("b:" ) \ + USE_FEATURE_REMOTE_LOG( "R:" ) \ + USE_FEATURE_REMOTE_LOG( "L" ) \ + USE_FEATURE_IPC_SYSLOG( "C::") +#define OPTION_DECL *opt_m, *opt_l \ + USE_FEATURE_ROTATE_LOGFILE(,*opt_s) \ + USE_FEATURE_ROTATE_LOGFILE(,*opt_b) \ + USE_FEATURE_REMOTE_LOG( ,*opt_R) \ + USE_FEATURE_IPC_SYSLOG( ,*opt_C = NULL) +#define OPTION_PARAM &opt_m, &logFilePath, &opt_l \ + USE_FEATURE_ROTATE_LOGFILE(,&opt_s) \ + USE_FEATURE_ROTATE_LOGFILE(,&opt_b) \ + USE_FEATURE_REMOTE_LOG( ,&opt_R) \ + USE_FEATURE_IPC_SYSLOG( ,&opt_C) + +#define MAXLINE 1024 /* maximum line length */ + +/* circular buffer variables/structures */ +#ifdef CONFIG_FEATURE_IPC_SYSLOG + +#if CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE < 4 +#error Sorry, you must set the syslogd buffer size to at least 4KB. +#error Please check CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE +#endif + +#include <sys/ipc.h> +#include <sys/sem.h> +#include <sys/shm.h> + +/* our shared key */ +#define KEY_ID ((long)0x414e4547) /* "GENA" */ + +// Semaphore operation structures +static struct shbuf_ds { + int size; // size of data written + int head; // start of message list + int tail; // end of message list + char data[1]; // data/messages +} *shbuf = NULL; // shared memory pointer + +static struct sembuf SMwup[1] = { {1, -1, IPC_NOWAIT} }; // set SMwup +static struct sembuf SMwdn[3] = { {0, 0}, {1, 0}, {1, +1} }; // set SMwdn + +static int shmid = -1; // ipc shared memory id +static int s_semid = -1; // ipc semaphore id +static int shm_size = ((CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE)*1024); // default shm size + +static void ipcsyslog_cleanup(void) +{ + puts("Exiting syslogd!"); + if (shmid != -1) { + shmdt(shbuf); + } + + if (shmid != -1) { + shmctl(shmid, IPC_RMID, NULL); + } + if (s_semid != -1) { + semctl(s_semid, 0, IPC_RMID, 0); + } +} + +static void ipcsyslog_init(void) +{ + if (shbuf == NULL) { + shmid = shmget(KEY_ID, shm_size, IPC_CREAT | 1023); + if (shmid == -1) { + bb_perror_msg_and_die("shmget"); + } + + shbuf = shmat(shmid, NULL, 0); + if (!shbuf) { + bb_perror_msg_and_die("shmat"); + } + + shbuf->size = shm_size - sizeof(*shbuf); + shbuf->head = shbuf->tail = 0; + + // we'll trust the OS to set initial semval to 0 (let's hope) + s_semid = semget(KEY_ID, 2, IPC_CREAT | IPC_EXCL | 1023); + if (s_semid == -1) { + if (errno == EEXIST) { + s_semid = semget(KEY_ID, 2, 0); + if (s_semid == -1) { + bb_perror_msg_and_die("semget"); + } + } else { + bb_perror_msg_and_die("semget"); + } + } + } else { + printf("Buffer already allocated just grab the semaphore?"); + } +} + +/* write message to buffer */ +static void circ_message(const char *msg) +{ + int l = strlen(msg) + 1; /* count the whole message w/ '\0' included */ + const char * const fail_msg = "Can't find the terminator token%s?\n"; + + if (semop(s_semid, SMwdn, 3) == -1) { + bb_perror_msg_and_die("SMwdn"); + } + + /* + * Circular Buffer Algorithm: + * -------------------------- + * + * Start-off w/ empty buffer of specific size SHM_SIZ + * Start filling it up w/ messages. I use '\0' as separator to break up messages. + * This is also very handy since we can do printf on message. + * + * Once the buffer is full we need to get rid of the first message in buffer and + * insert the new message. (Note: if the message being added is >1 message then + * we will need to "remove" >1 old message from the buffer). The way this is done + * is the following: + * When we reach the end of the buffer we set a mark and start from the beginning. + * Now what about the beginning and end of the buffer? Well we have the "head" + * index/pointer which is the starting point for the messages and we have "tail" + * index/pointer which is the ending point for the messages. When we "display" the + * messages we start from the beginning and continue until we reach "tail". If we + * reach end of buffer, then we just start from the beginning (offset 0). "head" and + * "tail" are actually offsets from the beginning of the buffer. + * + * Note: This algorithm uses Linux IPC mechanism w/ shared memory and semaphores to provide + * a threadsafe way of handling shared memory operations. + */ + if ((shbuf->tail + l) < shbuf->size) { + /* before we append the message we need to check the HEAD so that we won't + overwrite any of the message that we still need and adjust HEAD to point + to the next message! */ + if (shbuf->tail < shbuf->head) { + if ((shbuf->tail + l) >= shbuf->head) { + /* we need to move the HEAD to point to the next message + * Theoretically we have enough room to add the whole message to the + * buffer, because of the first outer IF statement, so we don't have + * to worry about overflows here! + */ + /* we need to know how many bytes we are overwriting to make enough room */ + int k = shbuf->tail + l - shbuf->head; + char *c = + memchr(shbuf->data + shbuf->head + k, '\0', + shbuf->size - (shbuf->head + k)); + if (c != NULL) { /* do a sanity check just in case! */ + /* we need to convert pointer to offset + skip the '\0' + since we need to point to the beginning of the next message */ + shbuf->head = c - shbuf->data + 1; + /* Note: HEAD is only used to "retrieve" messages, it's not used + when writing messages into our buffer */ + } else { /* show an error message to know we messed up? */ + printf(fail_msg,""); + shbuf->head = 0; + } + } + } + + /* in other cases no overflows have been done yet, so we don't care! */ + /* we should be ok to append the message now */ + strncpy(shbuf->data + shbuf->tail, msg, l); /* append our message */ + shbuf->tail += l; /* count full message w/ '\0' terminating char */ + } else { + /* we need to break up the message and "circle" it around */ + char *c; + int k = shbuf->tail + l - shbuf->size; /* count # of bytes we don't fit */ + + /* We need to move HEAD! This is always the case since we are going + * to "circle" the message. */ + c = memchr(shbuf->data + k, '\0', shbuf->size - k); + + if (c != NULL) { /* if we don't have '\0'??? weird!!! */ + /* move head pointer */ + shbuf->head = c - shbuf->data + 1; + + /* now write the first part of the message */ + strncpy(shbuf->data + shbuf->tail, msg, l - k - 1); + + /* ALWAYS terminate end of buffer w/ '\0' */ + shbuf->data[shbuf->size - 1] = '\0'; + + /* now write out the rest of the string to the beginning of the buffer */ + strcpy(shbuf->data, &msg[l - k - 1]); + + /* we need to place the TAIL at the end of the message */ + shbuf->tail = k + 1; + } else { + printf(fail_msg, " from the beginning"); + shbuf->head = shbuf->tail = 0; /* reset buffer, since it's probably corrupted */ + } + + } + if (semop(s_semid, SMwup, 1) == -1) { + bb_perror_msg_and_die("SMwup"); + } + +} +#else +void ipcsyslog_cleanup(void); +void ipcsyslog_init(void); +void circ_message(const char *msg); +#endif /* CONFIG_FEATURE_IPC_SYSLOG */ + +/* Note: There is also a function called "message()" in init.c */ +/* Print a message to the log file. */ +static void message(char *fmt, ...) __attribute__ ((format(printf, 1, 2))); +static void message(char *fmt, ...) +{ + int fd = -1; + struct flock fl; + va_list arguments; + + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 1; + +#ifdef CONFIG_FEATURE_IPC_SYSLOG + if ((option_mask32 & OPT_circularlog) && shbuf) { + char b[1024]; + + va_start(arguments, fmt); + vsnprintf(b, sizeof(b) - 1, fmt, arguments); + va_end(arguments); + circ_message(b); + + } else +#endif + fd = device_open(logFilePath, O_WRONLY | O_CREAT + | O_NOCTTY | O_APPEND | O_NONBLOCK); + if (fd >= 0) { + fl.l_type = F_WRLCK; + fcntl(fd, F_SETLKW, &fl); + +#ifdef CONFIG_FEATURE_ROTATE_LOGFILE + if (ENABLE_FEATURE_ROTATE_LOGFILE && logFileSize > 0 ) { + struct stat statf; + int r = fstat(fd, &statf); + if (!r && (statf.st_mode & S_IFREG) + && (lseek(fd,0,SEEK_END) > logFileSize)) { + if (logFileRotate > 0) { + int i = strlen(logFilePath) + 4; + char oldFile[i]; + char newFile[i]; + for (i=logFileRotate-1; i>0; i--) { + sprintf(oldFile, "%s.%d", logFilePath, i-1); + sprintf(newFile, "%s.%d", logFilePath, i); + rename(oldFile, newFile); + } + sprintf(newFile, "%s.%d", logFilePath, 0); + fl.l_type = F_UNLCK; + fcntl(fd, F_SETLKW, &fl); + close(fd); + rename(logFilePath, newFile); + fd = device_open(logFilePath, + O_WRONLY | O_CREAT | O_NOCTTY | O_APPEND | + O_NONBLOCK); + fl.l_type = F_WRLCK; + fcntl(fd, F_SETLKW, &fl); + } else { + ftruncate(fd, 0); + } + } + } +#endif + va_start(arguments, fmt); + vdprintf(fd, fmt, arguments); + va_end(arguments); + fl.l_type = F_UNLCK; + fcntl(fd, F_SETLKW, &fl); + close(fd); + } else { + /* Always send console messages to /dev/console so people will see them. */ + fd = device_open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY | O_NONBLOCK); + if (fd >= 0) { + va_start(arguments, fmt); + vdprintf(fd, fmt, arguments); + va_end(arguments); + close(fd); + } else { + fprintf(stderr, "Bummer, can't print: "); + va_start(arguments, fmt); + vfprintf(stderr, fmt, arguments); + fflush(stderr); + va_end(arguments); + } + } +} + +static void logMessage(int pri, char *msg) +{ + time_t now; + char *timestamp; + char res[20]; + CODE *c_pri, *c_fac; + + if (pri != 0) { + c_fac = facilitynames; + while (c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri) << 3)) + c_fac++; + c_pri = prioritynames; + while (c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri))) + c_pri++; + if (c_fac->c_name == NULL || c_pri->c_name == NULL) { + snprintf(res, sizeof(res), "<%d>", pri); + } else { + snprintf(res, sizeof(res), "%s.%s", c_fac->c_name, c_pri->c_name); + } + } + + if (strlen(msg) < 16 || msg[3] != ' ' || msg[6] != ' ' || + msg[9] != ':' || msg[12] != ':' || msg[15] != ' ') { + time(&now); + timestamp = ctime(&now) + 4; + timestamp[15] = '\0'; + } else { + timestamp = msg; + timestamp[15] = '\0'; + msg += 16; + } + + /* todo: supress duplicates */ + +#ifdef CONFIG_FEATURE_REMOTE_LOG + if (option_mask32 & OPT_remotelog) { + char line[MAXLINE + 1]; + /* trying connect the socket */ + if (-1 == remotefd) { + remotefd = socket(AF_INET, SOCK_DGRAM, 0); + } + /* if we have a valid socket, send the message */ + if (-1 != remotefd) { + snprintf(line, sizeof(line), "<%d>%s", pri, msg); + /* send message to remote logger, ignore possible error */ + sendto(remotefd, line, strlen(line), 0, + (struct sockaddr *) &remoteaddr, sizeof(remoteaddr)); + } + } + + if (option_mask32 & OPT_locallog) +#endif + { + /* now spew out the message to wherever it is supposed to go */ + if (pri == 0 || LOG_PRI(pri) < logLevel) { + if (option_mask32 & OPT_small) + message("%s %s\n", timestamp, msg); + else + message("%s %s %s %s\n", timestamp, LocalHostName, res, msg); + } + } +} + +static void quit_signal(int sig) +{ + logMessage(LOG_SYSLOG | LOG_INFO, "System log daemon exiting."); + unlink(lfile); + if (ENABLE_FEATURE_IPC_SYSLOG) + ipcsyslog_cleanup(); + + exit(1); +} + +static void domark(int sig) +{ + if (MarkInterval > 0) { + logMessage(LOG_SYSLOG | LOG_INFO, "-- MARK --"); + alarm(MarkInterval); + } +} + +/* This must be a #define, since when CONFIG_DEBUG and BUFFERS_GO_IN_BSS are + * enabled, we otherwise get a "storage size isn't constant error. */ +static int serveConnection(char *tmpbuf, int n_read) +{ + char *p = tmpbuf; + + while (p < tmpbuf + n_read) { + + int pri = (LOG_USER | LOG_NOTICE); + int num_lt = 0; + char line[MAXLINE + 1]; + unsigned char c; + char *q = line; + + while ((c = *p) && q < &line[sizeof(line) - 1]) { + if (c == '<' && num_lt == 0) { + /* Parse the magic priority number. */ + num_lt++; + pri = 0; + while (isdigit(*++p)) { + pri = 10 * pri + (*p - '0'); + } + if (pri & ~(LOG_FACMASK | LOG_PRIMASK)) { + pri = (LOG_USER | LOG_NOTICE); + } + } else if (c == '\n') { + *q++ = ' '; + } else if (iscntrl(c) && (c < 0177)) { + *q++ = '^'; + *q++ = c ^ 0100; + } else { + *q++ = c; + } + p++; + } + *q = '\0'; + p++; + /* Now log it */ + logMessage(pri, line); + } + return n_read; +} + +static void doSyslogd(void) ATTRIBUTE_NORETURN; +static void doSyslogd(void) +{ + struct sockaddr_un sunx; + socklen_t addrLength; + + int sock_fd; + fd_set fds; + + /* Set up signal handlers. */ + signal(SIGINT, quit_signal); + signal(SIGTERM, quit_signal); + signal(SIGQUIT, quit_signal); + signal(SIGHUP, SIG_IGN); + signal(SIGCHLD, SIG_IGN); +#ifdef SIGCLD + signal(SIGCLD, SIG_IGN); +#endif + signal(SIGALRM, domark); + alarm(MarkInterval); + + /* Create the syslog file so realpath() can work. */ + if (realpath(_PATH_LOG, lfile) != NULL) { + unlink(lfile); + } + + memset(&sunx, 0, sizeof(sunx)); + sunx.sun_family = AF_UNIX; + strncpy(sunx.sun_path, lfile, sizeof(sunx.sun_path)); + sock_fd = xsocket(AF_UNIX, SOCK_DGRAM, 0); + addrLength = sizeof(sunx.sun_family) + strlen(sunx.sun_path); + if (bind(sock_fd, (struct sockaddr *) &sunx, addrLength) < 0) { + bb_perror_msg_and_die("cannot connect to socket %s", lfile); + } + + if (chmod(lfile, 0666) < 0) { + bb_perror_msg_and_die("cannot set permission on %s", lfile); + } + if (ENABLE_FEATURE_IPC_SYSLOG && (option_mask32 & OPT_circularlog)) { + ipcsyslog_init(); + } + + logMessage(LOG_SYSLOG | LOG_INFO, "syslogd started: " "BusyBox v" BB_VER ); + + for (;;) { + FD_ZERO(&fds); + FD_SET(sock_fd, &fds); + + if (select(sock_fd + 1, &fds, NULL, NULL, NULL) < 0) { + if (errno == EINTR) { + /* alarm may have happened. */ + continue; + } + bb_perror_msg_and_die("select"); + } + + if (FD_ISSET(sock_fd, &fds)) { + int i; +#if MAXLINE > BUFSIZ +# define TMP_BUF_SZ BUFSIZ +#else +# define TMP_BUF_SZ MAXLINE +#endif +#define tmpbuf bb_common_bufsiz1 + + if ((i = recv(sock_fd, tmpbuf, TMP_BUF_SZ, 0)) > 0) { + tmpbuf[i] = '\0'; + serveConnection(tmpbuf, i); + } else { + bb_perror_msg_and_die("UNIX socket error"); + } + } /* FD_ISSET() */ + } /* for main loop */ +} + + +int syslogd_main(int argc, char **argv) +{ + char OPTION_DECL; + char *p; + + /* do normal option parsing */ + getopt32(argc, argv, OPTION_STR, OPTION_PARAM); + if (option_mask32 & OPT_mark) MarkInterval = xatoul_range(opt_m, 0, INT_MAX/60) * 60; // -m + //if (option_mask32 & OPT_nofork) // -n + //if (option_mask32 & OPT_outfile) // -O + if (option_mask32 & OPT_loglevel) { // -l + logLevel = xatoi_u(opt_l); + /* Valid levels are between 1 and 8 */ + if (logLevel < 1 || logLevel > 8) + bb_show_usage(); + } + //if (option_mask32 & OPT_small) // -S +#if ENABLE_FEATURE_ROTATE_LOGFILE + if (option_mask32 & OPT_filesize) logFileSize = xatoul_range(opt_s, 0, INT_MAX/1024) * 1024; // -s + if (option_mask32 & OPT_rotatecnt) { // -b + logFileRotate = xatoi_u(opt_b); + if (logFileRotate > 99) logFileRotate = 99; + } +#endif +#if ENABLE_FEATURE_REMOTE_LOG + if (option_mask32 & OPT_remotelog) { // -R + int port = 514; + char *host = xstrdup(opt_R); + p = strchr(host, ':'); + if (p) { + port = xatou16(p + 1); + *p = '\0'; + } + remoteaddr.sin_family = AF_INET; + /* FIXME: looks ip4-specific. need to do better */ + remoteaddr.sin_addr = *(struct in_addr *) *(xgethostbyname(host)->h_addr_list); + remoteaddr.sin_port = htons(port); + free(host); + } + //if (option_mask32 & OPT_locallog) // -L +#endif +#if ENABLE_FEATURE_IPC_SYSLOG + if (option_mask32 & OPT_circularlog) { // -C + if (opt_C) { + shm_size = xatoul_range(opt_C, 4, INT_MAX/1024) * 1024; + } + } +#endif + + /* If they have not specified remote logging, then log locally */ + if (ENABLE_FEATURE_REMOTE_LOG && !(option_mask32 & OPT_remotelog)) + option_mask32 |= OPT_locallog; + + /* Store away localhost's name before the fork */ + gethostname(LocalHostName, sizeof(LocalHostName)); + p = strchr(LocalHostName, '.'); + if (p) { + *p = '\0'; + } + + umask(0); + + if (!(option_mask32 & OPT_nofork)) { +#ifdef BB_NOMMU + vfork_daemon_rexec(0, 1, argc, argv, "-n"); +#else + xdaemon(0, 1); +#endif + } + doSyslogd(); + + return EXIT_SUCCESS; +} diff --git a/testsuite/README b/testsuite/README new file mode 100644 index 000000000..377f20ef8 --- /dev/null +++ b/testsuite/README @@ -0,0 +1,34 @@ +Update: doesn't work as described. Try "make check" from parent dir... +* * * + +To run the test suite, change to this directory and run "./runtest". It will +run all of the test cases, and list those with unexpected outcomes. Adding the +-v option will cause it to show expected outcomes as well. To only run the test +cases for particular applets, specify them as parameters to runtest. + +The test cases for an applet reside in the subdirectory of the applet name. The +name of the test case should be the assertion that is tested. The test case +should be a shell fragment that returns successfully if the test case passes, +and unsuccessfully otherwise. + +If the test case relies on a certain feature, it should include the string +"FEATURE: " followed by the name of the feature in a comment. If it is always +expected to fail, it should include the string "XFAIL" in a comment. + +For the entire testsuite, the copyright is as follows: + +Copyright (C) 2001, 2002 Matt Kraai + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. diff --git a/testsuite/TODO b/testsuite/TODO new file mode 100644 index 000000000..b8957f488 --- /dev/null +++ b/testsuite/TODO @@ -0,0 +1,26 @@ +This testsuite is quite obviously a work in progress. As such, +there are a number of good extensions. If you are looking for +something to do, feel free to tackle one or more of the following: + +Moving to the new format. + The old way was "lots of little tests files in a directory", which + doesn't interact well with source control systems. The new test + format is command.tests files that use testing.sh. + +Every busybox applet needs a corresponding applet.tests. + +Full SUSv3 test suite. + Let's make the Linux Test Project jealous, shall we? Don't just + audit programs for standards compliance, _prove_ it with a regression + test harness. + + http://www.opengroup.org/onlinepubs/009695399/utilities/ + +Some tests need root access. + It's hard to test things like mount or init as a normal user. + Possibly User Mode Linux could be used for this, or perhaps + Erik's buildroot. + +libbb unit testing + Being able to test the functions of libbb individually may + help to prevent regressions. diff --git a/testsuite/all_sourcecode.tests b/testsuite/all_sourcecode.tests new file mode 100755 index 000000000..c115878fe --- /dev/null +++ b/testsuite/all_sourcecode.tests @@ -0,0 +1,78 @@ +#!/bin/sh + +# Tests for the sourcecode base itself. +# Copyright 2006 by Mike Frysinger <vapier@gentoo.org> +# Licensed under GPL v2, see file LICENSE for details. + +[ -n "$srcdir" ] || srcdir=$(pwd) +. testing.sh + + +# +# if we don't have the sourcecode available, let's just bail +# +[ -s "$srcdir/../Makefile" ] || exit 0 +[ -s "$srcdir/../include/applets.h" ] || exit 0 + + +# +# verify the applet order is correct in applets.h, otherwise +# applets won't be called properly. +# +sed -n -e '/^USE_[A-Z]*(APPLET(/{s:.*(::;s:,.*::;s:"::g;p}' \ + $srcdir/../include/applets.h > applet.order.current +LC_ALL=C sort applet.order.current > applet.order.correct +testing "Applet order" "diff -u applet.order.current applet.order.correct" "" "" "" +rm -f applet.order.current applet.order.correct + + +# +# check for misc common typos +# +find $srcdir/../ \ + '(' -type d -a '(' -name .svn -o -name testsuite ')' -prune ')' \ + -o '(' -type f -a -print0 ')' | xargs -0 \ + grep -I \ + -e '\<compatability\>' \ + -e '\<compatable\>' \ + -e '\<fordeground\>' \ + -e '\<depency\>' -e '\<dependancy\>' -e '\<dependancies\>' \ + -e '\<defalt\>' \ + -e '\<remaing\>' \ + -e '\<queueing\>' \ + -e '\<detatch\>' \ + -e '\<sempahore\>' \ + -e '\<reprenstative\>' \ + -e '\<overriden\>' \ + -e '\<readed\>' \ + -e '\<formated\>' \ + -e '\<algorithic\>' \ + -e '\<deamon\>' \ + -e '\<derefernce\>' \ + -e '\<acomadate\>' \ + | sed -e "s:^$srcdir/\.\./::g" > src.typos +testing "Common typos" "cat src.typos" "" "" "" +rm -f src.typos + + +# +# don't allow obsolete functions +# +find $srcdir/.. '(' -name '*.c' -o -name '*.h' ')' -print0 | xargs -0 \ + grep -E -e '\<(bcmp|bcopy|bzero|getwd|index|mktemp|rindex|utimes)\>[[:space:]]*\(' \ + | sed -e "s:^$srcdir/\.\./::g" > src.obsolete.funcs +testing "Obsolete function usage" "cat src.obsolete.funcs" "" "" "" +rm -f src.obsolete.funcs + + +# +# don't allow obsolete headers +# +find $srcdir/.. '(' -name '*.c' -o -name '*.h' ')' -print0 | xargs -0 \ + grep -E -e '\<(malloc|memory|sys/(errno|fcntl|signal|stropts|termios|unistd))\.h\>' \ + | sed -e "s:^$srcdir/\.\./::g" > src.obsolete.headers +testing "Obsolete headers" "cat src.obsolete.headers" "" "" "" +rm -f src.obsolete.headers + + +exit $FAILCOUNT diff --git a/testsuite/basename/basename-does-not-remove-identical-extension b/testsuite/basename/basename-does-not-remove-identical-extension new file mode 100644 index 000000000..4448fdec4 --- /dev/null +++ b/testsuite/basename/basename-does-not-remove-identical-extension @@ -0,0 +1 @@ +test xfoo = x`busybox basename foo foo` diff --git a/testsuite/basename/basename-works b/testsuite/basename/basename-works new file mode 100644 index 000000000..38907d4c1 --- /dev/null +++ b/testsuite/basename/basename-works @@ -0,0 +1,2 @@ +test x$(basename $(pwd)) = x$(busybox basename $(pwd)) + diff --git a/testsuite/bunzip2/bunzip2-reads-from-standard-input b/testsuite/bunzip2/bunzip2-reads-from-standard-input new file mode 100644 index 000000000..e212a1207 --- /dev/null +++ b/testsuite/bunzip2/bunzip2-reads-from-standard-input @@ -0,0 +1,2 @@ +echo foo | bzip2 | busybox bunzip2 > output +echo foo | cmp - output diff --git a/testsuite/bunzip2/bunzip2-removes-compressed-file b/testsuite/bunzip2/bunzip2-removes-compressed-file new file mode 100644 index 000000000..f1d15503e --- /dev/null +++ b/testsuite/bunzip2/bunzip2-removes-compressed-file @@ -0,0 +1,3 @@ +echo foo | bzip2 >foo.bz2 +busybox bunzip2 foo.bz2 +test ! -f foo.bz2 diff --git a/testsuite/bunzip2/bzcat-does-not-remove-compressed-file b/testsuite/bunzip2/bzcat-does-not-remove-compressed-file new file mode 100644 index 000000000..7d4016ec5 --- /dev/null +++ b/testsuite/bunzip2/bzcat-does-not-remove-compressed-file @@ -0,0 +1,3 @@ +echo foo | bzip2 >foo.bz2 +busybox bzcat foo.bz2 +test -f foo.bz2 diff --git a/testsuite/busybox.tests b/testsuite/busybox.tests new file mode 100755 index 000000000..26536c656 --- /dev/null +++ b/testsuite/busybox.tests @@ -0,0 +1,46 @@ +#!/bin/sh + +# Tests for busybox applet itself. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +. testing.sh + +HELPDUMP=`busybox` + +# We need to test under calling the binary under other names. + + +testing "busybox --help busybox" "busybox --help busybox" "$HELPDUMP\n\n" "" "" + +ln -s `which busybox` busybox-suffix +for i in busybox ./busybox-suffix +do + # The gratuitous "\n"s are due to a shell idiosyncrasy: + # environment variables seem to strip trailing whitespace. + + testing "" "$i" "$HELPDUMP\n\n" "" "" + + testing "$i unknown" "$i unknown 2>&1" \ + "unknown: applet not found\n" "" "" + + testing "$i --help" "$i --help 2>&1" "$HELPDUMP\n\n" "" "" + + optional CAT + testing "" "$i cat" "moo" "" "moo" + testing "$i --help cat" "$i --help cat 2>&1 | grep prints" \ + "Concatenates FILE(s) and prints them to stdout.\n" "" "" + optional "" + + testing "$i --help unknown" "$i --help unknown 2>&1" \ + "unknown: applet not found\n" "" "" +done +rm busybox-suffix + +ln -s `which busybox` unknown + +testing "busybox as unknown name" "./unknown 2>&1" \ + "unknown: applet not found\n" "" "" +rm unknown + +exit $FAILCOUNT diff --git a/testsuite/cat/cat-prints-a-file b/testsuite/cat/cat-prints-a-file new file mode 100644 index 000000000..e3f35a86e --- /dev/null +++ b/testsuite/cat/cat-prints-a-file @@ -0,0 +1,3 @@ +echo I WANT > foo +busybox cat foo >bar +cmp foo bar diff --git a/testsuite/cat/cat-prints-a-file-and-standard-input b/testsuite/cat/cat-prints-a-file-and-standard-input new file mode 100644 index 000000000..bc9231882 --- /dev/null +++ b/testsuite/cat/cat-prints-a-file-and-standard-input @@ -0,0 +1,7 @@ +echo I WANT > foo +echo SOMETHING | busybox cat foo - >bar +cat >baz <<EOF +I WANT +SOMETHING +EOF +cmp bar baz diff --git a/testsuite/cmp/cmp-detects-difference b/testsuite/cmp/cmp-detects-difference new file mode 100644 index 000000000..b9bb628f1 --- /dev/null +++ b/testsuite/cmp/cmp-detects-difference @@ -0,0 +1,9 @@ +echo foo >foo +echo bar >bar +set +e +busybox cmp -s foo bar +if [ $? != 0 ] ; then + exit 0; +fi + +exit 1; diff --git a/testsuite/cp/cp-a-files-to-dir b/testsuite/cp/cp-a-files-to-dir new file mode 100644 index 000000000..39f8f8103 --- /dev/null +++ b/testsuite/cp/cp-a-files-to-dir @@ -0,0 +1,14 @@ +echo file number one > file1 +echo file number two > file2 +ln -s file2 link1 +mkdir dir1 +touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3 +mkdir there +busybox cp -a file1 file2 link1 dir1 there +test -f there/file1 +test -f there/file2 +test ! -s there/dir1/file3 +test -L there/link1 +test xfile2 = x`readlink there/link1` +test ! dir1/file3 -ot there/dir1/file3 +test ! dir1/file3 -nt there/dir1/file3 diff --git a/testsuite/cp/cp-a-preserves-links b/testsuite/cp/cp-a-preserves-links new file mode 100644 index 000000000..0c0cd9653 --- /dev/null +++ b/testsuite/cp/cp-a-preserves-links @@ -0,0 +1,5 @@ +touch foo +ln -s foo bar +busybox cp -a bar baz +test -L baz +test xfoo = x`readlink baz` diff --git a/testsuite/cp/cp-copies-empty-file b/testsuite/cp/cp-copies-empty-file new file mode 100644 index 000000000..ad25aa12e --- /dev/null +++ b/testsuite/cp/cp-copies-empty-file @@ -0,0 +1,3 @@ +touch foo +busybox cp foo bar +cmp foo bar diff --git a/testsuite/cp/cp-copies-large-file b/testsuite/cp/cp-copies-large-file new file mode 100644 index 000000000..c2225c6d8 --- /dev/null +++ b/testsuite/cp/cp-copies-large-file @@ -0,0 +1,3 @@ +dd if=/dev/zero of=foo seek=10k count=1 2>/dev/null +busybox cp foo bar +cmp foo bar diff --git a/testsuite/cp/cp-copies-small-file b/testsuite/cp/cp-copies-small-file new file mode 100644 index 000000000..d52a887c0 --- /dev/null +++ b/testsuite/cp/cp-copies-small-file @@ -0,0 +1,3 @@ +echo I WANT > foo +busybox cp foo bar +cmp foo bar diff --git a/testsuite/cp/cp-d-files-to-dir b/testsuite/cp/cp-d-files-to-dir new file mode 100644 index 000000000..9571a567e --- /dev/null +++ b/testsuite/cp/cp-d-files-to-dir @@ -0,0 +1,11 @@ +echo file number one > file1 +echo file number two > file2 +touch file3 +ln -s file2 link1 +mkdir there +busybox cp -d file1 file2 file3 link1 there +test -f there/file1 +test -f there/file2 +test ! -s there/file3 +test -L there/link1 +test xfile2 = x`readlink there/link1` diff --git a/testsuite/cp/cp-dir-create-dir b/testsuite/cp/cp-dir-create-dir new file mode 100644 index 000000000..a8d7b50a5 --- /dev/null +++ b/testsuite/cp/cp-dir-create-dir @@ -0,0 +1,4 @@ +mkdir bar +touch bar/baz +busybox cp -R bar foo +test -f foo/baz diff --git a/testsuite/cp/cp-dir-existing-dir b/testsuite/cp/cp-dir-existing-dir new file mode 100644 index 000000000..4c788ba73 --- /dev/null +++ b/testsuite/cp/cp-dir-existing-dir @@ -0,0 +1,5 @@ +mkdir bar +touch bar/baz +mkdir foo +busybox cp -R bar foo +test -f foo/bar/baz diff --git a/testsuite/cp/cp-does-not-copy-unreadable-file b/testsuite/cp/cp-does-not-copy-unreadable-file new file mode 100644 index 000000000..ce11bfab0 --- /dev/null +++ b/testsuite/cp/cp-does-not-copy-unreadable-file @@ -0,0 +1,6 @@ +touch foo +chmod a-r foo +set +e +busybox cp foo bar +set -e +test ! -f bar diff --git a/testsuite/cp/cp-files-to-dir b/testsuite/cp/cp-files-to-dir new file mode 100644 index 000000000..fdb81916f --- /dev/null +++ b/testsuite/cp/cp-files-to-dir @@ -0,0 +1,11 @@ +echo file number one > file1 +echo file number two > file2 +touch file3 +ln -s file2 link1 +mkdir there +busybox cp file1 file2 file3 link1 there +test -f there/file1 +test -f there/file2 +test ! -s there/file3 +test -f there/link1 +cmp there/file2 there/link1 diff --git a/testsuite/cp/cp-follows-links b/testsuite/cp/cp-follows-links new file mode 100644 index 000000000..2d9f05e9f --- /dev/null +++ b/testsuite/cp/cp-follows-links @@ -0,0 +1,4 @@ +touch foo +ln -s foo bar +busybox cp bar baz +test -f baz diff --git a/testsuite/cp/cp-preserves-hard-links b/testsuite/cp/cp-preserves-hard-links new file mode 100644 index 000000000..4de7b85db --- /dev/null +++ b/testsuite/cp/cp-preserves-hard-links @@ -0,0 +1,6 @@ +# FEATURE: CONFIG_FEATURE_PRESERVE_HARDLINKS +touch foo +ln foo bar +mkdir baz +busybox cp -d foo bar baz +test baz/foo -ef baz/bar diff --git a/testsuite/cp/cp-preserves-links b/testsuite/cp/cp-preserves-links new file mode 100644 index 000000000..301dc5fd8 --- /dev/null +++ b/testsuite/cp/cp-preserves-links @@ -0,0 +1,5 @@ +touch foo +ln -s foo bar +busybox cp -d bar baz +test -L baz +test xfoo = x`readlink baz` diff --git a/testsuite/cp/cp-preserves-source-file b/testsuite/cp/cp-preserves-source-file new file mode 100644 index 000000000..f0f5065f4 --- /dev/null +++ b/testsuite/cp/cp-preserves-source-file @@ -0,0 +1,3 @@ +touch foo +busybox cp foo bar +test -f foo diff --git a/testsuite/cut/cut-cuts-a-character b/testsuite/cut/cut-cuts-a-character new file mode 100644 index 000000000..d6c5efa3a --- /dev/null +++ b/testsuite/cut/cut-cuts-a-character @@ -0,0 +1 @@ +test $(echo abcd | busybox cut -c 3) = c diff --git a/testsuite/cut/cut-cuts-a-closed-range b/testsuite/cut/cut-cuts-a-closed-range new file mode 100644 index 000000000..9680b7650 --- /dev/null +++ b/testsuite/cut/cut-cuts-a-closed-range @@ -0,0 +1 @@ +test $(echo abcd | busybox cut -c 1-2) = ab diff --git a/testsuite/cut/cut-cuts-a-field b/testsuite/cut/cut-cuts-a-field new file mode 100644 index 000000000..4c7f44007 --- /dev/null +++ b/testsuite/cut/cut-cuts-a-field @@ -0,0 +1 @@ +test $(echo -e "f1\tf2\tf3" | busybox cut -f 2) = f2 diff --git a/testsuite/cut/cut-cuts-an-open-range b/testsuite/cut/cut-cuts-an-open-range new file mode 100644 index 000000000..1fbf27742 --- /dev/null +++ b/testsuite/cut/cut-cuts-an-open-range @@ -0,0 +1 @@ +test $(echo abcd | busybox cut -c -3) = abc diff --git a/testsuite/cut/cut-cuts-an-unclosed-range b/testsuite/cut/cut-cuts-an-unclosed-range new file mode 100644 index 000000000..a2b0cdb03 --- /dev/null +++ b/testsuite/cut/cut-cuts-an-unclosed-range @@ -0,0 +1 @@ +test $(echo abcd | busybox cut -c 3-) = cd diff --git a/testsuite/date/date-R-works b/testsuite/date/date-R-works new file mode 100644 index 000000000..ec3a06751 --- /dev/null +++ b/testsuite/date/date-R-works @@ -0,0 +1,2 @@ +test x"`date -R`" = x"`busybox date -R`" + diff --git a/testsuite/date/date-format-works b/testsuite/date/date-format-works new file mode 100644 index 000000000..f28d06cfc --- /dev/null +++ b/testsuite/date/date-format-works @@ -0,0 +1 @@ +test x"`date +%d/%m/%y`" = x"`busybox date +%d/%m/%y`" diff --git a/testsuite/date/date-u-works b/testsuite/date/date-u-works new file mode 100644 index 000000000..7d9902a3f --- /dev/null +++ b/testsuite/date/date-u-works @@ -0,0 +1,2 @@ +test x"`date -u`" = x"`busybox date -u`" + diff --git a/testsuite/date/date-works b/testsuite/date/date-works new file mode 100644 index 000000000..2f6dd1eca --- /dev/null +++ b/testsuite/date/date-works @@ -0,0 +1,2 @@ +test x"`date`" = x"`busybox date`" + diff --git a/testsuite/dd/dd-accepts-if b/testsuite/dd/dd-accepts-if new file mode 100644 index 000000000..03d1af853 --- /dev/null +++ b/testsuite/dd/dd-accepts-if @@ -0,0 +1,2 @@ +echo I WANT >foo +test "$(busybox dd if=foo 2>/dev/null)" = "I WANT" diff --git a/testsuite/dd/dd-accepts-of b/testsuite/dd/dd-accepts-of new file mode 100644 index 000000000..84405e622 --- /dev/null +++ b/testsuite/dd/dd-accepts-of @@ -0,0 +1,2 @@ +echo I WANT | busybox dd of=foo 2>/dev/null +echo I WANT | cmp foo - diff --git a/testsuite/dd/dd-copies-from-standard-input-to-standard-output b/testsuite/dd/dd-copies-from-standard-input-to-standard-output new file mode 100644 index 000000000..d890eb04c --- /dev/null +++ b/testsuite/dd/dd-copies-from-standard-input-to-standard-output @@ -0,0 +1 @@ +test "$(echo I WANT | busybox dd 2>/dev/null)" = "I WANT" diff --git a/testsuite/dd/dd-prints-count-to-standard-error b/testsuite/dd/dd-prints-count-to-standard-error new file mode 100644 index 000000000..2187dc027 --- /dev/null +++ b/testsuite/dd/dd-prints-count-to-standard-error @@ -0,0 +1,2 @@ +echo I WANT | busybox dd of=foo >/dev/null 2>bar +grep -q records bar diff --git a/testsuite/dirname/dirname-handles-absolute-path b/testsuite/dirname/dirname-handles-absolute-path new file mode 100644 index 000000000..ca1a51b3a --- /dev/null +++ b/testsuite/dirname/dirname-handles-absolute-path @@ -0,0 +1 @@ +test $(busybox dirname /foo/bar/baz) = /foo/bar diff --git a/testsuite/dirname/dirname-handles-empty-path b/testsuite/dirname/dirname-handles-empty-path new file mode 100644 index 000000000..04134a58f --- /dev/null +++ b/testsuite/dirname/dirname-handles-empty-path @@ -0,0 +1 @@ +test $(busybox dirname '') = . diff --git a/testsuite/dirname/dirname-handles-multiple-slashes b/testsuite/dirname/dirname-handles-multiple-slashes new file mode 100644 index 000000000..286f25361 --- /dev/null +++ b/testsuite/dirname/dirname-handles-multiple-slashes @@ -0,0 +1 @@ +test $(busybox dirname foo/bar///baz) = foo/bar diff --git a/testsuite/dirname/dirname-handles-relative-path b/testsuite/dirname/dirname-handles-relative-path new file mode 100644 index 000000000..ffe4ab459 --- /dev/null +++ b/testsuite/dirname/dirname-handles-relative-path @@ -0,0 +1 @@ +test $(busybox dirname foo/bar/baz) = foo/bar diff --git a/testsuite/dirname/dirname-handles-root b/testsuite/dirname/dirname-handles-root new file mode 100644 index 000000000..6bd62b8a1 --- /dev/null +++ b/testsuite/dirname/dirname-handles-root @@ -0,0 +1 @@ +test $(busybox dirname /) = / diff --git a/testsuite/dirname/dirname-handles-single-component b/testsuite/dirname/dirname-handles-single-component new file mode 100644 index 000000000..24f9ae163 --- /dev/null +++ b/testsuite/dirname/dirname-handles-single-component @@ -0,0 +1 @@ +test $(busybox dirname foo) = . diff --git a/testsuite/dirname/dirname-works b/testsuite/dirname/dirname-works new file mode 100644 index 000000000..f339c8f73 --- /dev/null +++ b/testsuite/dirname/dirname-works @@ -0,0 +1,2 @@ +test x$(dirname $(pwd)) = x$(busybox dirname $(pwd)) + diff --git a/testsuite/du/du-h-works b/testsuite/du/du-h-works new file mode 100644 index 000000000..82041ab33 --- /dev/null +++ b/testsuite/du/du-h-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du -h "$d" > logfile.gnu +busybox du -h "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/du/du-k-works b/testsuite/du/du-k-works new file mode 100644 index 000000000..177a1a2cd --- /dev/null +++ b/testsuite/du/du-k-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du -k "$d" > logfile.gnu +busybox du -k "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/du/du-l-works b/testsuite/du/du-l-works new file mode 100644 index 000000000..61e91400c --- /dev/null +++ b/testsuite/du/du-l-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du -l "$d" > logfile.gnu +busybox du -l "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/du/du-m-works b/testsuite/du/du-m-works new file mode 100644 index 000000000..bc9707350 --- /dev/null +++ b/testsuite/du/du-m-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du -m "$d" > logfile.gnu +busybox du -m "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/du/du-s-works b/testsuite/du/du-s-works new file mode 100644 index 000000000..f0b3bf0ae --- /dev/null +++ b/testsuite/du/du-s-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du -s "$d" > logfile.gnu +busybox du -s "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/du/du-works b/testsuite/du/du-works new file mode 100644 index 000000000..47949c694 --- /dev/null +++ b/testsuite/du/du-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du "$d" > logfile.gnu +busybox du "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/echo/echo-does-not-print-newline b/testsuite/echo/echo-does-not-print-newline new file mode 100644 index 000000000..2ed03caf5 --- /dev/null +++ b/testsuite/echo/echo-does-not-print-newline @@ -0,0 +1 @@ +test `busybox echo -n word | wc -c` -eq 4 diff --git a/testsuite/echo/echo-prints-argument b/testsuite/echo/echo-prints-argument new file mode 100644 index 000000000..479dac89c --- /dev/null +++ b/testsuite/echo/echo-prints-argument @@ -0,0 +1 @@ +test xfubar = x`busybox echo fubar` diff --git a/testsuite/echo/echo-prints-arguments b/testsuite/echo/echo-prints-arguments new file mode 100644 index 000000000..4e4e3b434 --- /dev/null +++ b/testsuite/echo/echo-prints-arguments @@ -0,0 +1 @@ +test "`busybox echo foo bar`" = "foo bar" diff --git a/testsuite/echo/echo-prints-newline b/testsuite/echo/echo-prints-newline new file mode 100644 index 000000000..838671efe --- /dev/null +++ b/testsuite/echo/echo-prints-newline @@ -0,0 +1 @@ +test `busybox echo word | wc -c` -eq 5 diff --git a/testsuite/expr/expr-works b/testsuite/expr/expr-works new file mode 100644 index 000000000..af49ac4d5 --- /dev/null +++ b/testsuite/expr/expr-works @@ -0,0 +1,59 @@ +# busybox expr +busybox expr 1 \| 1 +busybox expr 1 \| 0 +busybox expr 0 \| 1 +busybox expr 1 \& 1 +busybox expr 0 \< 1 +busybox expr 1 \> 0 +busybox expr 0 \<= 1 +busybox expr 1 \<= 1 +busybox expr 1 \>= 0 +busybox expr 1 \>= 1 +busybox expr 1 + 2 +busybox expr 2 - 1 +busybox expr 2 \* 3 +busybox expr 12 / 2 +busybox expr 12 % 5 + + +set +e +busybox expr 0 \| 0 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 1 \& 0 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 0 \& 1 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 0 \& 0 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 1 \< 0 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 0 \> 1 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 1 \<= 0 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 0 \>= 1 +if [ $? != 1 ] ; then + exit 1; +fi; + diff --git a/testsuite/false/false-is-silent b/testsuite/false/false-is-silent new file mode 100644 index 000000000..8a9aa0c7f --- /dev/null +++ b/testsuite/false/false-is-silent @@ -0,0 +1 @@ +busybox false 2>&1 | cmp - /dev/null diff --git a/testsuite/false/false-returns-failure b/testsuite/false/false-returns-failure new file mode 100644 index 000000000..1a061f286 --- /dev/null +++ b/testsuite/false/false-returns-failure @@ -0,0 +1 @@ +! busybox false diff --git a/testsuite/find/find-supports-minus-xdev b/testsuite/find/find-supports-minus-xdev new file mode 100644 index 000000000..4c559a1f4 --- /dev/null +++ b/testsuite/find/find-supports-minus-xdev @@ -0,0 +1 @@ +busybox find . -xdev >/dev/null 2>&1 diff --git a/testsuite/grep.tests b/testsuite/grep.tests new file mode 100755 index 000000000..ef0de482b --- /dev/null +++ b/testsuite/grep.tests @@ -0,0 +1,84 @@ +#!/bin/sh + +# grep tests. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +# AUDIT: + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" +# file input will be file called "input" +# test can create a file "actual" instead of writing to stdout + +# Test exit status + +testing "grep (exit with error)" "grep nonexistent 2> /dev/null ; echo \$?" \ + "1\n" "" "" +testing "grep (exit success)" "grep grep $0 > /dev/null 2>&1 ; echo \$?" "0\n" \ + "" "" +# Test various data sources and destinations + +testing "grep (default to stdin)" "grep two" "two\n" "" \ + "one\ntwo\nthree\nthree\nthree\n" +testing "grep - (specify stdin)" "grep two -" "two\n" "" \ + "one\ntwo\nthree\nthree\nthree\n" +testing "grep input (specify file)" "grep two input" "two\n" \ + "one\ntwo\nthree\nthree\nthree\n" "" + +testing "grep (no newline at EOL)" "grep bug" "bug" "bug" "" + +# Note that this assumes actual is empty. +testing "grep input actual (two files)" "grep two input actual 2> /dev/null" \ + "input:two\n" "one\ntwo\nthree\nthree\nthree\n" "" + +testing "grep - infile (specify stdin and file)" "grep two - input" \ + "(standard input):two\ninput:two\n" "one\ntwo\nthree\n" \ + "one\ntwo\ntoo\nthree\nthree\n" + +# Check if we see the correct return value if both stdin and non-existing file +# are given. +testing "grep - nofile (specify stdin and nonexisting file)" \ + "grep two - nonexistent 2> /dev/null ; echo \$?" \ + "(standard input):two\n(standard input):two\n2\n" \ + "" "one\ntwo\ntwo\nthree\nthree\nthree\n" +testing "grep -q - nofile (specify stdin and nonexisting file, no match)" \ + "grep -q nomatch - nonexistent 2> /dev/null ; echo \$?" \ + "2\n" "" "one\ntwo\ntwo\nthree\nthree\nthree\n" +# SUSv3: If the -q option is specified, the exit status shall be zero +# if an input line is selected, even if an error was detected. +testing "grep -q - nofile (specify stdin and nonexisting file, match)" \ + "grep -q two - nonexistent ; echo \$?" \ + "0\n" "" "one\ntwo\ntwo\nthree\nthree\nthree\n" + +# Test various command line options +# -s no error messages +testing "grep -s nofile (nonexisting file, no match)" \ + "grep -s nomatch nonexistent ; echo \$?" "2\n" "" "" +testing "grep -s nofile - (stdin and nonexisting file, match)" \ + "grep -s domatch nonexistent - ; echo \$?" \ + "(standard input):domatch\n2\n" "" "nomatch\ndomatch\nend\n" + +# This doesn't match GNU behaviour (Binary file input matches) +# acts like GNU grep -a +testing "grep handles binary files" "grep foo input" "foo\n" "\0foo\n\n" "" +# This doesn't match GNU behaviour (Binary file (standard input) matches) +# acts like GNU grep -a +testing "grep handles binary stdin" "grep foo" "foo\n" "" "\0foo\n\n" + +testing "grep matches NUL" "grep . input > /dev/null 2>&1 ; echo \$?" \ + "0\n" "\0\n" "" + +# -e regex +testing "grep handles multiple regexps" "grep -e one -e two input ; echo \$?" \ + "one\ntwo\n0\n" "one\ntwo\n" "" + +optional FEATURE_GREP_EGREP_ALIAS +testing "grep -E supports extended regexps" "grep -E fo+" "foo\n" "" \ + "b\ar\nfoo\nbaz" +testing "grep is also egrep" "egrep foo" "foo\n" "" "foo\nbar\n" +testing "egrep is not case insensitive" \ + "egrep foo ; [ \$? -ne 0 ] && echo yes" "yes\n" "" "FOO\n" + +exit $FAILCOUNT diff --git a/testsuite/gunzip/gunzip-reads-from-standard-input b/testsuite/gunzip/gunzip-reads-from-standard-input new file mode 100644 index 000000000..7c498c0ce --- /dev/null +++ b/testsuite/gunzip/gunzip-reads-from-standard-input @@ -0,0 +1,2 @@ +echo foo | gzip | busybox gunzip > output +echo foo | cmp - output diff --git a/testsuite/gzip/gzip-accepts-multiple-files b/testsuite/gzip/gzip-accepts-multiple-files new file mode 100644 index 000000000..8f0d9c845 --- /dev/null +++ b/testsuite/gzip/gzip-accepts-multiple-files @@ -0,0 +1,3 @@ +touch foo bar +busybox gzip foo bar +test -f foo.gz -a -f bar.gz diff --git a/testsuite/gzip/gzip-accepts-single-minus b/testsuite/gzip/gzip-accepts-single-minus new file mode 100644 index 000000000..8b51fdfed --- /dev/null +++ b/testsuite/gzip/gzip-accepts-single-minus @@ -0,0 +1 @@ +echo foo | busybox gzip - >/dev/null diff --git a/testsuite/gzip/gzip-removes-original-file b/testsuite/gzip/gzip-removes-original-file new file mode 100644 index 000000000..b9cb995bc --- /dev/null +++ b/testsuite/gzip/gzip-removes-original-file @@ -0,0 +1,3 @@ +touch foo +busybox gzip foo +test ! -f foo diff --git a/testsuite/head/head-n-works b/testsuite/head/head-n-works new file mode 100644 index 000000000..db4325556 --- /dev/null +++ b/testsuite/head/head-n-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +head -n 2 "$d/README" > logfile.gnu +busybox head -n 2 "$d/README" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/head/head-works b/testsuite/head/head-works new file mode 100644 index 000000000..56ad3e36b --- /dev/null +++ b/testsuite/head/head-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +head "$d/README" > logfile.gnu +busybox head "$d/README" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/hostid/hostid-works b/testsuite/hostid/hostid-works new file mode 100644 index 000000000..e85698e66 --- /dev/null +++ b/testsuite/hostid/hostid-works @@ -0,0 +1,2 @@ +test x$(hostid) = x$(busybox hostid) + diff --git a/testsuite/hostname/hostname-d-works b/testsuite/hostname/hostname-d-works new file mode 100644 index 000000000..a9aeb92cb --- /dev/null +++ b/testsuite/hostname/hostname-d-works @@ -0,0 +1,2 @@ +test x$(hostname -d) = x$(busybox hostname -d) + diff --git a/testsuite/hostname/hostname-i-works b/testsuite/hostname/hostname-i-works new file mode 100644 index 000000000..68a3e6789 --- /dev/null +++ b/testsuite/hostname/hostname-i-works @@ -0,0 +1,2 @@ +test x$(hostname -i) = x$(busybox hostname -i) + diff --git a/testsuite/hostname/hostname-s-works b/testsuite/hostname/hostname-s-works new file mode 100644 index 000000000..172b94409 --- /dev/null +++ b/testsuite/hostname/hostname-s-works @@ -0,0 +1 @@ +test x$(hostname -s) = x$(busybox hostname -s) diff --git a/testsuite/hostname/hostname-works b/testsuite/hostname/hostname-works new file mode 100644 index 000000000..f51a406ea --- /dev/null +++ b/testsuite/hostname/hostname-works @@ -0,0 +1 @@ +test x$(hostname) = x$(busybox hostname) diff --git a/testsuite/id/id-g-works b/testsuite/id/id-g-works new file mode 100644 index 000000000..671fc5361 --- /dev/null +++ b/testsuite/id/id-g-works @@ -0,0 +1 @@ +test x$(id -g) = x$(busybox id -g) diff --git a/testsuite/id/id-u-works b/testsuite/id/id-u-works new file mode 100644 index 000000000..2358cb0d7 --- /dev/null +++ b/testsuite/id/id-u-works @@ -0,0 +1 @@ +test x$(id -u) = x$(busybox id -u) diff --git a/testsuite/id/id-un-works b/testsuite/id/id-un-works new file mode 100644 index 000000000..db390e733 --- /dev/null +++ b/testsuite/id/id-un-works @@ -0,0 +1 @@ +test x$(id -un) = x$(busybox id -un) diff --git a/testsuite/id/id-ur-works b/testsuite/id/id-ur-works new file mode 100644 index 000000000..6b0fcb038 --- /dev/null +++ b/testsuite/id/id-ur-works @@ -0,0 +1 @@ +test x$(id -ur) = x$(busybox id -ur) diff --git a/testsuite/ln/ln-creates-hard-links b/testsuite/ln/ln-creates-hard-links new file mode 100644 index 000000000..2f6e23f9a --- /dev/null +++ b/testsuite/ln/ln-creates-hard-links @@ -0,0 +1,4 @@ +echo file number one > file1 +busybox ln file1 link1 +test -f file1 +test -f link1 diff --git a/testsuite/ln/ln-creates-soft-links b/testsuite/ln/ln-creates-soft-links new file mode 100644 index 000000000..e875e4c8a --- /dev/null +++ b/testsuite/ln/ln-creates-soft-links @@ -0,0 +1,4 @@ +echo file number one > file1 +busybox ln -s file1 link1 +test -L link1 +test xfile1 = x`readlink link1` diff --git a/testsuite/ln/ln-force-creates-hard-links b/testsuite/ln/ln-force-creates-hard-links new file mode 100644 index 000000000..c96b7d6cf --- /dev/null +++ b/testsuite/ln/ln-force-creates-hard-links @@ -0,0 +1,5 @@ +echo file number one > file1 +echo file number two > link1 +busybox ln -f file1 link1 +test -f file1 +test -f link1 diff --git a/testsuite/ln/ln-force-creates-soft-links b/testsuite/ln/ln-force-creates-soft-links new file mode 100644 index 000000000..cab8d1db7 --- /dev/null +++ b/testsuite/ln/ln-force-creates-soft-links @@ -0,0 +1,5 @@ +echo file number one > file1 +echo file number two > link1 +busybox ln -f -s file1 link1 +test -L link1 +test xfile1 = x`readlink link1` diff --git a/testsuite/ln/ln-preserves-hard-links b/testsuite/ln/ln-preserves-hard-links new file mode 100644 index 000000000..47fb98961 --- /dev/null +++ b/testsuite/ln/ln-preserves-hard-links @@ -0,0 +1,8 @@ +echo file number one > file1 +echo file number two > link1 +set +e +busybox ln file1 link1 +if [ $? != 0 ] ; then + exit 0; +fi +exit 1; diff --git a/testsuite/ln/ln-preserves-soft-links b/testsuite/ln/ln-preserves-soft-links new file mode 100644 index 000000000..a8123ece3 --- /dev/null +++ b/testsuite/ln/ln-preserves-soft-links @@ -0,0 +1,9 @@ +echo file number one > file1 +echo file number two > link1 +set +e +busybox ln -s file1 link1 +if [ $? != 0 ] ; then + exit 0; +fi +exit 1; + diff --git a/testsuite/ls/ls-1-works b/testsuite/ls/ls-1-works new file mode 100644 index 000000000..8ad484fc3 --- /dev/null +++ b/testsuite/ls/ls-1-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +ls -1 "$d" > logfile.gnu +busybox ls -1 "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/ls/ls-h-works b/testsuite/ls/ls-h-works new file mode 100644 index 000000000..7331262c9 --- /dev/null +++ b/testsuite/ls/ls-h-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +ls -h "$d" > logfile.gnu +busybox ls -h "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/ls/ls-l-works b/testsuite/ls/ls-l-works new file mode 100644 index 000000000..efc2b196e --- /dev/null +++ b/testsuite/ls/ls-l-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +LC_ALL=C ls -l "$d" > logfile.gnu +busybox ls -l "$d" > logfile.bb +diff -w logfile.gnu logfile.bb diff --git a/testsuite/ls/ls-s-works b/testsuite/ls/ls-s-works new file mode 100644 index 000000000..6c8bf3627 --- /dev/null +++ b/testsuite/ls/ls-s-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +LC_ALL=C ls -1s "$d" > logfile.gnu +busybox ls -1s "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/md5sum/md5sum-verifies-non-binary-file b/testsuite/md5sum/md5sum-verifies-non-binary-file new file mode 100644 index 000000000..8566a234d --- /dev/null +++ b/testsuite/md5sum/md5sum-verifies-non-binary-file @@ -0,0 +1,3 @@ +touch foo +md5sum foo > bar +busybox md5sum -c bar diff --git a/testsuite/mkdir/mkdir-makes-a-directory b/testsuite/mkdir/mkdir-makes-a-directory new file mode 100644 index 000000000..6ca5c4d52 --- /dev/null +++ b/testsuite/mkdir/mkdir-makes-a-directory @@ -0,0 +1,2 @@ +busybox mkdir foo +test -d foo diff --git a/testsuite/mkdir/mkdir-makes-parent-directories b/testsuite/mkdir/mkdir-makes-parent-directories new file mode 100644 index 000000000..992facb46 --- /dev/null +++ b/testsuite/mkdir/mkdir-makes-parent-directories @@ -0,0 +1,2 @@ +busybox mkdir -p foo/bar +test -d foo -a -d foo/bar diff --git a/testsuite/mount.testroot b/testsuite/mount.testroot new file mode 100755 index 000000000..e18d0461d --- /dev/null +++ b/testsuite/mount.testroot @@ -0,0 +1,183 @@ +#!/bin/sh + +# SUSv3 compliant mount and umount tests. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +if [ -z "$TESTDIR" ] +then + echo 'Need $TESTDIR' + exit 1 +fi + +cd "$TESTDIR" + +. testing.sh + +# If we aren't PID 1, barf. + +#if [ $$ -ne 1 ] +#then +# echo "SKIPPED: mount test requires emulation environment" +# exit 0 +#fi + +# Run tests within the chroot environment. +dochroot bash rm ls ln cat ps mknod mkdir dd grep cmp diff tail \ + mkfs.ext2 mkfs.vfat mount umount losetup wc << EOF +#!/bin/bash + +. /testing.sh + +mknod /dev/loop0 b 7 0 +mknod /dev/loop1 b 7 1 + +# We need /proc to do much. Make sure we can mount it explicitly. + +testing "mount no proc [GNUFAIL]" "mount 2> /dev/null || echo yes" "yes\n" "" "" +testing "mount /proc" "mount -t proc /proc /proc && ls -d /proc/1" \ + "/proc/1\n" "" "" + +# Make sure the last thing in the list is /proc + +testing "mount list1" "mount | tail -n 1" "/proc on /proc type proc (rw)\n" \ + "" "" + + +# Create an ext2 image + +mkdir -p images/{ext2.dir,vfat.dir,test1,test2,test3} +dd if=/dev/zero of=images/ext2.img bs=1M count=1 2> /dev/null +mkfs.ext2 -F -b 1024 images/ext2.img > /dev/null 2> /dev/null +dd if=/dev/zero of=images/vfat.img bs=1M count=1 2> /dev/null +mkfs.vfat images/vfat.img > /dev/null + +# Test mount it + +testing "mount vfat image (explicitly autodetect type)" \ + "mount -t auto images/vfat.img images/vfat.dir && mount | tail -n 1 | grep -o 'vfat.dir type vfat'" \ + "vfat.dir type vfat\n" "" "" +testing "mount ext2 image (autodetect type)" \ + "mount images/ext2.img images/ext2.dir 2> /dev/null && mount | tail -n 1" \ + "/dev/loop1 on /images/ext2.dir type ext2 (rw)\n" "" "" +testing "mount remount ext2 image noatime" \ + "mount -o remount,noatime images/ext2.dir && mount | tail -n 1" \ + "/dev/loop1 on /images/ext2.dir type ext2 (rw,noatime)\n" "" "" +testing "mount remount ext2 image ro remembers noatime" \ + "mount -o remount,ro images/ext2.dir && mount | tail -n 1" \ + "/dev/loop1 on /images/ext2.dir type ext2 (ro,noatime)\n" "" "" + +umount -d images/vfat.dir +umount -d images/ext2.dir + +testing "mount umount freed loop device" \ + "mount images/ext2.img images/ext2.dir && mount | tail -n 1" \ + "/dev/loop0 on /images/ext2.dir type ext2 (rw)\n" "" "" + +testing "mount block device" \ + "mount -t ext2 /dev/loop0 images/test1 && mount | tail -n 1" \ + "/dev/loop0 on /images/test1 type ext2 (rw)\n" "" "" + +umount -d images/ext2.dir images/test1 + +testing "mount remount nonexistent directory" \ + "mount -o remount,noatime images/ext2.dir 2> /dev/null || echo yes" \ + "yes\n" "" "" + +# Fun with mount -a + +testing "mount -a no fstab" "mount -a 2>/dev/null || echo yes" "yes\n" "" "" + +umount /proc + +# The first field is space delimited, the rest tabs. + +cat > /etc/fstab << FSTAB +/proc /proc proc defaults 0 0 +# Autodetect loop, and provide flags with commas in them. +/images/ext2.img /images/ext2.dir ext2 noatime,nodev 0 0 +# autodetect filesystem, flags without commas. +/images/vfat.img /images/vfat.dir auto ro 0 0 +# A block device +/dev/loop2 /images/test1 auto defaults 0 0 +# tmpfs, filesystem specific flag. +walrus /images/test2 tmpfs size=42 0 0 +# Autodetect a bind mount. +/images/test2 /images/test3 auto defaults 0 0 +FSTAB + +# Put something on loop2. +mknod /dev/loop2 b 7 2 +cat images/ext2.img > images/ext2-2.img +losetup /dev/loop2 images/ext2-2.img + +testing "mount -a" "mount -a && echo hello > /images/test2/abc && cat /images/test3/abc && (mount | wc -l)" "hello\n8\n" "" "" + +testing "umount -a" "umount -a && ls /proc" "" "" "" + +#/bin/bash < /dev/tty > /dev/tty 2> /dev/tty +mknod /dev/console c 5 1 +/bin/bash < /dev/console > /dev/console 2> /dev/console +EOF + +exit 0 + +# Run some tests + +losetup nonexistent device (should return error 2) +losetup unbound loop device (should return error 1) +losetup bind file to loop device +losetup bound loop device (display) (should return error 0) +losetup filename (error) +losetup nofile (file not found) +losetup -d +losetup bind with offset +losetup -f (print first loop device) +losetup -f filename (associate file with first loop device) +losetup -o (past end of file) -f filename + +mount -a + with multiple entries in fstab + with duplicate entries in fstab + with relative paths in fstab + with user entries in fstab +mount -o async,sync,atime,noatime,dev,nodev,exec,noexec,loop,suid,nosuid,remount,ro,rw,bind,move +mount -r +mount -o rw -r +mount -w -o ro +mount -t auto + +mount with relative path in fstab +mount block device +mount char device +mount file (autoloop) +mount directory (autobind) + + +testing "umount with no /proc" +testing "umount curdir" + +# The basic tests. These should work even with the small busybox. + +testing "sort" "input" "a\nb\nc\n" "c\na\nb\n" "" +testing "sort #2" "input" "010\n1\n3\n" "3\n1\n010\n" "" +testing "sort stdin" "" "a\nb\nc\n" "" "b\na\nc\n" +testing "sort numeric" "-n input" "1\n3\n010\n" "3\n1\n010\n" "" +testing "sort reverse" "-r input" "wook\nwalrus\npoint\npabst\naargh\n" \ + "point\nwook\npabst\naargh\nwalrus\n" "" + +optional FEATURE_MOUNT_LOOP +testing "umount -D" + +optional FEATURE_MTAB_SUPPORT +optional FEATURE_MOUNT_NFS +# No idea what to test here. + +optional UMOUNT +optional FEATURE_UMOUNT_ALL +testing "umount -a" +testing "umount -r" +testing "umount -l" +testing "umount -f" + +exit $FAILCOUNT diff --git a/testsuite/msh/msh-supports-underscores-in-variable-names b/testsuite/msh/msh-supports-underscores-in-variable-names new file mode 100644 index 000000000..9c7834b37 --- /dev/null +++ b/testsuite/msh/msh-supports-underscores-in-variable-names @@ -0,0 +1 @@ +test "`busybox msh -c 'FOO_BAR=foo; echo $FOO_BAR'`" = foo diff --git a/testsuite/mv/mv-files-to-dir b/testsuite/mv/mv-files-to-dir new file mode 100644 index 000000000..c8eaba88e --- /dev/null +++ b/testsuite/mv/mv-files-to-dir @@ -0,0 +1,16 @@ +echo file number one > file1 +echo file number two > file2 +ln -s file2 link1 +mkdir dir1 +touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3 +mkdir there +busybox mv file1 file2 link1 dir1 there +test -f there/file1 +test -f there/file2 +test -f there/dir1/file3 +test -L there/link1 +test xfile2 = x`readlink there/link1` +test ! -e file1 +test ! -e file2 +test ! -e link1 +test ! -e dir1/file3 diff --git a/testsuite/mv/mv-follows-links b/testsuite/mv/mv-follows-links new file mode 100644 index 000000000..1fb355b81 --- /dev/null +++ b/testsuite/mv/mv-follows-links @@ -0,0 +1,4 @@ +touch foo +ln -s foo bar +busybox mv bar baz +test -f baz diff --git a/testsuite/mv/mv-moves-empty-file b/testsuite/mv/mv-moves-empty-file new file mode 100644 index 000000000..48afca4d5 --- /dev/null +++ b/testsuite/mv/mv-moves-empty-file @@ -0,0 +1,4 @@ +touch foo +busybox mv foo bar +test ! -e foo +test -f bar diff --git a/testsuite/mv/mv-moves-file b/testsuite/mv/mv-moves-file new file mode 100644 index 000000000..edb4c3751 --- /dev/null +++ b/testsuite/mv/mv-moves-file @@ -0,0 +1,3 @@ +touch foo +busybox mv foo bar +test ! -f foo -a -f bar diff --git a/testsuite/mv/mv-moves-hardlinks b/testsuite/mv/mv-moves-hardlinks new file mode 100644 index 000000000..eaa8215a4 --- /dev/null +++ b/testsuite/mv/mv-moves-hardlinks @@ -0,0 +1,4 @@ +touch foo +ln foo bar +busybox mv bar baz +test ! -f bar -a -f baz diff --git a/testsuite/mv/mv-moves-large-file b/testsuite/mv/mv-moves-large-file new file mode 100644 index 000000000..77d088ff1 --- /dev/null +++ b/testsuite/mv/mv-moves-large-file @@ -0,0 +1,4 @@ +dd if=/dev/zero of=foo seek=10k count=1 2>/dev/null +busybox mv foo bar +test ! -e foo +test -f bar diff --git a/testsuite/mv/mv-moves-small-file b/testsuite/mv/mv-moves-small-file new file mode 100644 index 000000000..065c7f1e9 --- /dev/null +++ b/testsuite/mv/mv-moves-small-file @@ -0,0 +1,4 @@ +echo I WANT > foo +busybox mv foo bar +test ! -e foo +test -f bar diff --git a/testsuite/mv/mv-moves-symlinks b/testsuite/mv/mv-moves-symlinks new file mode 100644 index 000000000..c413af07c --- /dev/null +++ b/testsuite/mv/mv-moves-symlinks @@ -0,0 +1,6 @@ +touch foo +ln -s foo bar +busybox mv bar baz +test -f foo +test ! -e bar +test -L baz diff --git a/testsuite/mv/mv-moves-unreadable-files b/testsuite/mv/mv-moves-unreadable-files new file mode 100644 index 000000000..bc9c3133c --- /dev/null +++ b/testsuite/mv/mv-moves-unreadable-files @@ -0,0 +1,5 @@ +touch foo +chmod a-r foo +busybox mv foo bar +test ! -e foo +test -f bar diff --git a/testsuite/mv/mv-preserves-hard-links b/testsuite/mv/mv-preserves-hard-links new file mode 100644 index 000000000..b3ba3aa29 --- /dev/null +++ b/testsuite/mv/mv-preserves-hard-links @@ -0,0 +1,6 @@ +# FEATURE: CONFIG_FEATURE_PRESERVE_HARDLINKS +touch foo +ln foo bar +mkdir baz +busybox mv foo bar baz +test baz/foo -ef baz/bar diff --git a/testsuite/mv/mv-preserves-links b/testsuite/mv/mv-preserves-links new file mode 100644 index 000000000..ea565d2f1 --- /dev/null +++ b/testsuite/mv/mv-preserves-links @@ -0,0 +1,5 @@ +touch foo +ln -s foo bar +busybox mv bar baz +test -L baz +test xfoo = x`readlink baz` diff --git a/testsuite/mv/mv-refuses-mv-dir-to-subdir b/testsuite/mv/mv-refuses-mv-dir-to-subdir new file mode 100644 index 000000000..7c572c4f8 --- /dev/null +++ b/testsuite/mv/mv-refuses-mv-dir-to-subdir @@ -0,0 +1,23 @@ +echo file number one > file1 +echo file number two > file2 +ln -s file2 link1 +mkdir dir1 +touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3 +mkdir there +busybox mv file1 file2 link1 dir1 there +test -f there/file1 +test -f there/file2 +test -f there/dir1/file3 +test -L there/link1 +test xfile2 = x`readlink there/link1` +test ! -e file1 +test ! -e file2 +test ! -e link1 +test ! -e dir1/file3 +set +e +busybox mv there there/dir1 +if [ $? != 0 ] ; then + exit 0; +fi + +exit 1; diff --git a/testsuite/mv/mv-removes-source-file b/testsuite/mv/mv-removes-source-file new file mode 100644 index 000000000..48afca4d5 --- /dev/null +++ b/testsuite/mv/mv-removes-source-file @@ -0,0 +1,4 @@ +touch foo +busybox mv foo bar +test ! -e foo +test -f bar diff --git a/testsuite/pidof.tests b/testsuite/pidof.tests new file mode 100755 index 000000000..bfde26e2c --- /dev/null +++ b/testsuite/pidof.tests @@ -0,0 +1,29 @@ +#!/bin/sh + +# pidof tests. +# Copyright 2005 by Bernhard Fischer +# Licensed under GPL v2, see file LICENSE for details. + +# AUDIT: + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" + +testing "pidof (exit with error)" \ + "pidof veryunlikelyoccuringbinaryname ; echo \$?" "1\n" "" "" +testing "pidof (exit with success)" "pidof pidof > /dev/null; echo \$?" \ + "0\n" "" "" +# We can get away with this because it says #!/bin/sh up top. + +testing "pidof this" "pidof pidof.tests | grep -o -w $$" "$$\n" "" "" + +optional FEATURE_PIDOF_SINGLE +testing "pidof -s" "pidof -s init" "1\n" "" "" + +optional FEATURE_PIDOF_OMIT +testing "pidof -o %PPID" "pidof -o %PPID pidof.tests | grep -o -w $$" "" "" "" +testing "pidof -o %PPID NOP" "pidof -o %PPID -s init" "1\n" "" "" +testing "pidof -o init" "pidof -o 1 init | grep -o -w 1" "" "" "" + +exit $FAILCOUNT diff --git a/testsuite/pwd/pwd-prints-working-directory b/testsuite/pwd/pwd-prints-working-directory new file mode 100644 index 000000000..8575347d6 --- /dev/null +++ b/testsuite/pwd/pwd-prints-working-directory @@ -0,0 +1 @@ +test $(pwd) = $(busybox pwd) diff --git a/testsuite/readlink.tests b/testsuite/readlink.tests new file mode 100755 index 000000000..0faa6ed0c --- /dev/null +++ b/testsuite/readlink.tests @@ -0,0 +1,32 @@ +#!/bin/sh + +# Readlink tests. +# Copyright 2006 by Natanael Copa <n@tanael.org> +# Licensed under GPL v2, see file LICENSE for details. + +. testing.sh + +TESTDIR=readlink_testdir +TESTFILE="$TESTDIR/testfile" +TESTLINK="testlink" +FAILLINK="$TESTDIR/$TESTDIR/testlink" + +# create the dir and test files +mkdir -p "./$TESTDIR" +touch "./$TESTFILE" +ln -s "./$TESTFILE" "./$TESTLINK" + +testing "readlink on a file" "readlink ./$TESTFILE" "" "" "" +testing "readlink on a link" "readlink ./$TESTLINK" "./$TESTFILE\n" "" "" + +optional FEATURE_READLINK_FOLLOW + +testing "readlink -f on a file" "readlink -f ./$TESTFILE" "$PWD/$TESTFILE\n" "" "" +testing "readlink -f on a link" "readlink -f ./$TESTLINK" "$PWD/$TESTFILE\n" "" "" +testing "readlink -f on an invalid link" "readlink -f ./$FAILLINK" "" "" "" +testing "readlink -f on a wierd dir" "readlink -f $TESTDIR/../$TESTFILE" "$PWD/$TESTFILE\n" "" "" + + +# clean up +rm -r "$TESTLINK" "$TESTDIR" + diff --git a/testsuite/rm/rm-removes-file b/testsuite/rm/rm-removes-file new file mode 100644 index 000000000..46571a98a --- /dev/null +++ b/testsuite/rm/rm-removes-file @@ -0,0 +1,3 @@ +touch foo +busybox rm foo +test ! -f foo diff --git a/testsuite/rmdir/rmdir-removes-parent-directories b/testsuite/rmdir/rmdir-removes-parent-directories new file mode 100644 index 000000000..222f5dec7 --- /dev/null +++ b/testsuite/rmdir/rmdir-removes-parent-directories @@ -0,0 +1,3 @@ +mkdir -p foo/bar +busybox rmdir -p foo/bar +test ! -d foo diff --git a/testsuite/runtest b/testsuite/runtest new file mode 100755 index 000000000..84cd6a7f3 --- /dev/null +++ b/testsuite/runtest @@ -0,0 +1,140 @@ +#!/bin/sh + +[ -n "$srcdir" ] || srcdir=$(pwd) +[ -n "$bindir" ] || bindir=$(dirname $(pwd)) +PATH=$bindir:$PATH + +# Run old-style test. + +function run_applet_testcase +{ + local applet=$1 + local testcase=$2 + + local status=0 + local RES= + + local uc_applet=$(echo $applet | tr a-z A-Z) + local testname=$(basename $testcase) + + if grep -q "^# CONFIG_${uc_applet} is not set$" $bindir/.config; then + echo UNTESTED: $testname + return 0 + fi + + if grep -q "^# FEATURE: " $testcase; then + local feature=`sed -ne 's/^# FEATURE: //p' $testcase` + + if grep -q "^# ${feature} is not set$" $bindir/.config; then + echo UNTESTED: $testname + return 0 + fi + fi + + rm -rf tmp + mkdir -p tmp + pushd tmp > /dev/null + + d=$srcdir sh -x -e $testcase >.logfile.txt 2>&1 || status=$? + + if [ $status -ne 0 ] ; then + echo FAIL: $testname + if [ $verbose -gt 0 ]; then + cat .logfile.txt + fi + status=$? + else + echo PASS: $testname + rm -f .logfile.txt + status=$? + fi + + popd > /dev/null + rm -rf tmp + + return $status +} + +run_applet_tests () +{ + local applet=$1 + + local status=0 + + for testcase in $srcdir/$applet/*; do + if [ "$testcase" = "$srcdir/$applet/CVS" ]; then + continue + fi + + if run_applet_testcase $applet $testcase; then + : + else + status=1 + fi + done + + return $status +} + + +status=0 +verbose=0 + +if [ x"$1" = x"-v" ]; then + verbose=1 + export VERBOSE=$verbose + shift +fi + +if [ $# -ne 0 ]; then + applets=$(cd $srcdir ; for i in $@; do ls ${i}* ; done) +else + applets=$(ls $srcdir) +fi + +# Populate a directory with links to all busybox applets + +LINKSDIR="${bindir}/runtest-tempdir-links" +rm -rf "$LINKSDIR" 2>/dev/null +mkdir "$LINKSDIR" +for i in $(sed 's@/[a-z0-9/\[]*/@@' $bindir/busybox.links 2>/dev/null) +do + ln -s $bindir/busybox "$LINKSDIR"/$i +done + +# Set up option flags so tests can be selective. + +configfile=${bindir:-../../}/.config +export OPTIONFLAGS=:$(echo $(sed -nr 's/^CONFIG_(.*)=.*/\1/p' $configfile) | sed 's/ /:/g') + +for applet in $applets; do + if [ "$applet" = "links" ]; then continue; fi + if [ "$applet" != "CVS" -a -d "$srcdir/$applet" ]; then + if run_applet_tests $applet; then + : + else + status=1 + fi + fi + + # Is this a new-style test? + applet=$(echo "$applet" | sed -n 's/\.tests$//p') + if [ ${#applet} -ne 0 ] + then + if [ ! -h "$LINKSDIR/$applet" ] && [ "${applet:0:4}" != "all_" ] + then + echo "SKIPPED: $applet (not built)" + continue + fi + if PATH="$LINKSDIR":$srcdir:$bindir:$PATH \ + "${srcdir:-.}/$applet".tests + then + : + else + status=1 + fi + fi + +done +rm -rf "$LINKSDIR" +exit $status diff --git a/testsuite/sed.tests b/testsuite/sed.tests new file mode 100755 index 000000000..2a0d4eacf --- /dev/null +++ b/testsuite/sed.tests @@ -0,0 +1,187 @@ +#!/bin/sh + +# SUSv3 compliant sed tests. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +. testing.sh + +# testing "description" "arguments" "result" "infile" "stdin" + +# Corner cases +testing "sed no files (stdin)" 'sed ""' "hello\n" "" "hello\n" +testing "sed explicit stdin" 'sed "" -' "hello\n" "" "hello\n" +testing "sed handles empty lines" "sed -e 's/\$/@/'" "@\n" "" "\n" +testing "sed stdin twice" 'sed "" - -' "hello" "" "hello" + +# Trailing EOF. +# Match $, at end of each file or all files? + +# -e corner cases +# without -e +# multiple -e +# interact with a +# -eee arg1 arg2 arg3 +# -f corner cases +# -e -f -e +# -n corner cases +# no newline at EOF? +# -r corner cases +# Just make sure it works. +# -i corner cases: +# sed -i - +# permissions +# -i on a symlink +# on a directory +# With $ last-line test +# Continue with \ +# End of script with trailing \ + +# command list +testing "sed accepts blanks before command" "sed -e '1 d'" "" "" "" +testing "sed accepts newlines in -e" "sed -e 'i\ +1 +a\ +3'" "1\n2\n3\n" "" "2\n" +testing "sed accepts multiple -e" "sed -e 'i\' -e '1' -e 'a\' -e '3'" \ + "1\n2\n3\n" "" "2\n" + +# substitutions +testing "sed -n" "sed -n -e s/foo/bar/ -e s/bar/baz/" "" "" "foo\n" +testing "sed s//p" "sed -e s/foo/bar/p -e s/bar/baz/p" "bar\nbaz\nbaz\n" \ + "" "foo\n" +testing "sed -n s//p" "sed -ne s/abc/def/p" "def\n" "" "abc\n" +testing "sed s//g (exhaustive)" "sed -e 's/[[:space:]]*/,/g'" ",1,2,3,4,5,\n" \ + "" "12345\n" +testing "sed s arbitrary delimiter" "sed -e 's woo boing '" "boing\n" "" "woo\n" +testing "sed s chains" "sed -e s/foo/bar/ -e s/bar/baz/" "baz\n" "" "foo\n" +testing "sed s chains2" "sed -e s/foo/bar/ -e s/baz/nee/" "bar\n" "" "foo\n" +testing "sed s [delimiter]" "sed -e 's@[@]@@'" "onetwo" "" "one@two" + +# branch +testing "sed b (branch)" "sed -e 'b one;p;: one'" "foo\n" "" "foo\n" +testing "sed b (branch with no label jumps to end)" "sed -e 'b;p'" \ + "foo\n" "" "foo\n" + +# test and branch +testing "sed t (test/branch)" "sed -e 's/a/1/;t one;p;: one;p'" \ + "1\n1\nb\nb\nb\nc\nc\nc\n" "" "a\nb\nc\n" +testing "sed t (test/branch clears test bit)" "sed -e 's/a/b/;:loop;t loop'" \ + "b\nb\nc\n" "" "a\nb\nc\n" +testing "sed T (!test/branch)" "sed -e 's/a/1/;T notone;p;: notone;p'" \ + "1\n1\n1\nb\nb\nc\nc\n" "" "a\nb\nc\n" + +# Normal sed end-of-script doesn't print "c" because n flushed the pattern +# space. If n hits EOF, pattern space is empty when script ends. +# Query: how does this interact with no newline at EOF? +testing "sed n (flushes pattern space, terminates early)" "sed -e 'n;p'" \ + "a\nb\nb\nc\n" "" "a\nb\nc\n" +# N does _not_ flush pattern space, therefore c is still in there @ script end. +testing "sed N (doesn't flush pattern space when terminating)" "sed -e 'N;p'" \ + "a\nb\na\nb\nc\n" "" "a\nb\nc\n" +testing "sed address match newline" 'sed "/b/N;/b\\nc/i woo"' \ + "a\nwoo\nb\nc\nd\n" "" "a\nb\nc\nd\n" + +# Multiple lines in pattern space +testing "sed N (stops at end of input) and P (prints to first newline only)" \ + "sed -n 'N;P;p'" "a\na\nb\n" "" "a\nb\nc\n" + +# Hold space +testing "sed G (append hold space to pattern space)" 'sed G' "a\n\nb\n\nc\n\n" \ + "" "a\nb\nc\n" +#testing "sed g/G (swap/append hold and patter space)" +#testing "sed g (swap hold/pattern space)" + +testing "sed d ends script iteration" \ + "sed -e '/ook/d;s/ook/ping/p;i woot'" "" "" "ook\n" +testing "sed d ends script iteration (2)" \ + "sed -e '/ook/d;a\' -e 'bang'" "woot\nbang\n" "" "ook\nwoot\n" + +# Multiple files, with varying newlines and NUL bytes +testing "sed embedded NUL" "sed -e 's/woo/bang/'" "\0bang\0woo\0" "" \ + "\0woo\0woo\0" +testing "sed embedded NUL g" "sed -e 's/woo/bang/g'" "bang\0bang\0" "" \ + "woo\0woo\0" +echo -e "/woo/a he\0llo" > sed.commands +testing "sed NUL in command" "sed -f sed.commands" "woo\nhe\0llo\n" "" "woo" +rm sed.commands + +# sed has funky behavior with newlines at the end of file. Test lots of +# corner cases with the optional newline appending behavior. + +testing "sed normal newlines" "sed -e 's/woo/bang/' input -" "bang\nbang\n" \ + "woo\n" "woo\n" +testing "sed leave off trailing newline" "sed -e 's/woo/bang/' input -" \ + "bang\nbang" "woo\n" "woo" +testing "sed autoinsert newline" "sed -e 's/woo/bang/' input -" "bang\nbang" \ + "woo" "woo" +testing "sed empty file plus cat" "sed -e 's/nohit//' input -" "one\ntwo" \ + "" "one\ntwo" +testing "sed cat plus empty file" "sed -e 's/nohit//' input -" "one\ntwo" \ + "one\ntwo" "" +testing "sed append autoinserts newline" "sed -e '/woot/a woo' -" \ + "woot\nwoo\n" "" "woot" +testing "sed insert doesn't autoinsert newline" "sed -e '/woot/i woo' -" \ + "woo\nwoot" "" "woot" +testing "sed print autoinsert newlines" "sed -e 'p' -" "one\none" "" "one" +testing "sed print autoinsert newlines two files" "sed -e 'p' input -" \ + "one\none\ntwo\ntwo" "one" "two" +testing "sed noprint, no match, no newline" "sed -ne 's/woo/bang/' input" \ + "" "no\n" "" +testing "sed selective matches with one nl" "sed -ne 's/woo/bang/p' input -" \ + "a bang\nc bang\n" "a woo\nb no" "c woo\nd no" +testing "sed selective matches insert newline" \ + "sed -ne 's/woo/bang/p' input -" "a bang\nb bang\nd bang" \ + "a woo\nb woo" "c no\nd woo" +testing "sed selective matches noinsert newline" \ + "sed -ne 's/woo/bang/p' input -" "a bang\nb bang" "a woo\nb woo" \ + "c no\nd no" +testing "sed clusternewline" \ + "sed -e '/one/a 111' -e '/two/i 222' -e p input -" \ + "one\none\n111\n222\ntwo\ntwo" "one" "two" + +# Test end-of-file matching behavior + +testing "sed match EOF" "sed -e '"'$p'"'" "hello\nthere\nthere" "" \ + "hello\nthere" +testing "sed match EOF two files" "sed -e '"'$p'"' input -" \ + "one\ntwo\nthree\nfour\nfour" "one\ntwo" "three\nfour" +echo -ne "three\nfour" > input2 +testing "sed match EOF inline" \ + "sed -e '"'$i ook'"' -i input input2 && cat input input2" \ + "one\nook\ntwothree\nook\nfour" "one\ntwo" "" +rm input2 + +# Test lie-to-autoconf + +testing "sed lie-to-autoconf" "sed --version | grep -o 'GNU sed version '" \ + "GNU sed version \n" "" "" + +# Jump to nonexistent label +testing "sed nonexistent label" "sed -e 'b walrus' 2> /dev/null || echo yes" \ + "yes\n" "" "" + +testing "sed backref from empty s uses range regex" \ + "sed -e '/woot/s//eep \0 eep/'" "eep woot eep" "" "woot" + +testing "sed backref from empty s uses range regex with newline" \ + "sed -e '/woot/s//eep \0 eep/'" "eep woot eep\n" "" "woot\n" + +# -i with no filename + +touch ./- # Detect gnu failure mode here. +testing "sed -i with no arg [GNUFAIL]" "sed -e '' -i 2> /dev/null || echo yes" \ + "yes\n" "" "" +rm ./- # Clean up + +testing "sed s/xxx/[/" "sed -e 's/xxx/[/'" "[\n" "" "xxx\n" + +# Ponder this a bit more, why "woo not found" from gnu version? +#testing "sed doesn't substitute in deleted line" \ +# "sed -e '/ook/d;s/ook//;t woo;a bang;'" "bang" "" "ook\n" + +# This makes both seds very unhappy. Why? +#testing "sed -g (exhaustive)" "sed -e 's/[[:space:]]*/,/g'" ",1,2,3,4,5," \ +# "" "12345" + +exit $FAILCOUNT diff --git a/testsuite/seq.tests b/testsuite/seq.tests new file mode 100755 index 000000000..ebb44e7a6 --- /dev/null +++ b/testsuite/seq.tests @@ -0,0 +1,36 @@ +#!/bin/sh + +# SUSv3 compliant seq tests. +# Copyright 2006 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +# AUDIT: Full SUSv3 coverage (except internationalization). + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" +# file input will be file called "input" +# test can create a file "actual" instead of writing to stdout + +# Test exit status + +testing "seq (exit with error)" "seq 2> /dev/null || echo yes" "yes\n" "" "" +testing "seq (exit with error)" "seq 1 2 3 4 2> /dev/null || echo yes" \ + "yes\n" "" "" +testing "seq one argument" "seq 3" "1\n2\n3\n" "" "" +testing "seq two arguments" "seq 5 7" "5\n6\n7\n" "" "" +testing "seq two arguments reversed" "seq 7 5" "" "" "" +testing "seq two arguments equal" "seq 3 3" "3\n" "" "" +testing "seq two arguments equal, arbitrary negative step" "seq 1 -15 1" \ + "1\n" "" "" +testing "seq two arguments equal, arbitrary positive step" "seq 1 +15 1" \ + "1\n" "" "" +testing "seq count up by 2" "seq 4 2 8" "4\n6\n8\n" "" "" +testing "seq count down by 2" "seq 8 -2 4" "8\n6\n4\n" "" "" +testing "seq count wrong way #1" "seq 4 -2 8" "" "" "" +testing "seq count wrong way #2" "seq 8 2 4" "" "" "" +testing "seq count by .3" "seq 3 .3 4" "3\n3.3\n3.6\n3.9\n" "" "" +testing "seq count by -.9" "seq .7 -.9 -2.2" "0.7\n-0.2\n-1.1\n-2\n" "" "" +testing "seq count by zero" "seq 4 0 8 | head -n 10" "" "" "" + +exit $FAILCOUNT diff --git a/testsuite/sort.tests b/testsuite/sort.tests new file mode 100755 index 000000000..5a4937b58 --- /dev/null +++ b/testsuite/sort.tests @@ -0,0 +1,83 @@ +#!/bin/bash + +# SUSv3 compliant sort tests. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +. testing.sh + +# The basic tests. These should work even with the small busybox. + +testing "sort" "sort input" "a\nb\nc\n" "c\na\nb\n" "" +testing "sort #2" "sort input" "010\n1\n3\n" "3\n1\n010\n" "" +testing "sort stdin" "sort" "a\nb\nc\n" "" "b\na\nc\n" +testing "sort numeric" "sort -n input" "1\n3\n010\n" "3\n1\n010\n" "" +testing "sort reverse" "sort -r input" "wook\nwalrus\npoint\npabst\naargh\n" \ + "point\nwook\npabst\naargh\nwalrus\n" "" + +# These tests require the full option set. + +optional FEATURE_SORT_BIG +# Longish chunk of data re-used by the next few tests + +data="42 1 3 woot +42 1 010 zoology +egg 1 2 papyrus +7 3 42 soup +999 3 0 algebra +" + +# Sorting with keys + +testing "sort one key" "sort -k4,4 input" \ +"999 3 0 algebra +egg 1 2 papyrus +7 3 42 soup +42 1 3 woot +42 1 010 zoology +" "$data" "" + +testing "sort key range with numeric option" "sort -k2,3n input" \ +"42 1 010 zoology +42 1 3 woot +egg 1 2 papyrus +7 3 42 soup +999 3 0 algebra +" "$data" "" + +# Busybox is definitely doing this one wrong just now. FIXME + +testing "sort key range with numeric option and global reverse" \ +"sort -k2,3n -r input" \ +"egg 1 2 papyrus +42 1 3 woot +42 1 010 zoology +999 3 0 algebra +7 3 42 soup +" "$data" "" + +# + +testing "sort key range with multiple options" "sort -k2,3rn input" \ +"7 3 42 soup +999 3 0 algebra +42 1 010 zoology +42 1 3 woot +egg 1 2 papyrus +" "$data" "" + +testing "sort key doesn't strip leading blanks, disables fallback global sort" \ +"sort -n -k2 -t ' '" " a \n 1 \n 2 \n" "" " 2 \n 1 \n a \n" + +testing "sort key edge case with -t" "sort -n -k4 -t/" \ +"/usr/lib/finish-install.d/1 +/usr/lib/finish-install.d/4 +/usr/lib/prebaseconfig.d/2 +/usr/lib/prebaseconfig.d/6 +" "" "/usr/lib/finish-install.d/1 +/usr/lib/prebaseconfig.d/2 +/usr/lib/finish-install.d/4 +/usr/lib/prebaseconfig.d/6 +" + +exit $FAILCOUNT diff --git a/testsuite/strings/strings-works-like-GNU b/testsuite/strings/strings-works-like-GNU new file mode 100644 index 000000000..2d6471033 --- /dev/null +++ b/testsuite/strings/strings-works-like-GNU @@ -0,0 +1,9 @@ +rm -f foo bar +strings -af ../../busybox > foo +busybox strings -af ../../busybox > bar +set +e +test ! -f foo -a -f bar +if [ $? = 0 ] ; then + set -e + diff -q foo bar +fi diff --git a/testsuite/tail/tail-n-works b/testsuite/tail/tail-n-works new file mode 100644 index 000000000..27a905f88 --- /dev/null +++ b/testsuite/tail/tail-n-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +tail -n 2 "$d/README" > logfile.gnu +busybox tail -n 2 "$d/README" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/tail/tail-works b/testsuite/tail/tail-works new file mode 100644 index 000000000..27a905f88 --- /dev/null +++ b/testsuite/tail/tail-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +tail -n 2 "$d/README" > logfile.gnu +busybox tail -n 2 "$d/README" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/tar/tar-archives-multiple-files b/testsuite/tar/tar-archives-multiple-files new file mode 100644 index 000000000..245d9e989 --- /dev/null +++ b/testsuite/tar/tar-archives-multiple-files @@ -0,0 +1,6 @@ +# FEATURE: CONFIG_FEATURE_TAR_CREATE +touch foo bar +busybox tar cf foo.tar foo bar +rm foo bar +tar xf foo.tar +test -f foo -a -f bar diff --git a/testsuite/tar/tar-complains-about-missing-file b/testsuite/tar/tar-complains-about-missing-file new file mode 100644 index 000000000..26e8cbb36 --- /dev/null +++ b/testsuite/tar/tar-complains-about-missing-file @@ -0,0 +1,3 @@ +touch foo +tar cf foo.tar foo +! busybox tar xf foo.tar bar diff --git a/testsuite/tar/tar-demands-at-least-one-ctx b/testsuite/tar/tar-demands-at-least-one-ctx new file mode 100644 index 000000000..85e7f6059 --- /dev/null +++ b/testsuite/tar/tar-demands-at-least-one-ctx @@ -0,0 +1 @@ +! busybox tar v diff --git a/testsuite/tar/tar-demands-at-most-one-ctx b/testsuite/tar/tar-demands-at-most-one-ctx new file mode 100644 index 000000000..130d0e7f9 --- /dev/null +++ b/testsuite/tar/tar-demands-at-most-one-ctx @@ -0,0 +1 @@ +! busybox tar tx diff --git a/testsuite/tar/tar-extracts-all-subdirs b/testsuite/tar/tar-extracts-all-subdirs new file mode 100644 index 000000000..886c37ce9 --- /dev/null +++ b/testsuite/tar/tar-extracts-all-subdirs @@ -0,0 +1,12 @@ +# FEATURE: CONFIG_FEATURE_TAR_CREATE +mkdir -p foo/{1,2,3} +mkdir -p foo/1/{10,11} +mkdir -p foo/1/10/{100,101,102} +tar cf foo.tar -C foo . +rm -rf foo/* +busybox tar xf foo.tar -C foo ./1/10 +find foo | sort >logfile.bb +rm -rf foo/* +tar xf foo.tar -C foo ./1/10 +find foo | sort >logfile.gnu +cmp logfile.gnu logfile.bb diff --git a/testsuite/tar/tar-extracts-file b/testsuite/tar/tar-extracts-file new file mode 100644 index 000000000..ca72f2489 --- /dev/null +++ b/testsuite/tar/tar-extracts-file @@ -0,0 +1,5 @@ +touch foo +tar cf foo.tar foo +rm foo +busybox tar xf foo.tar +test -f foo diff --git a/testsuite/tar/tar-extracts-from-standard-input b/testsuite/tar/tar-extracts-from-standard-input new file mode 100644 index 000000000..a30e9f0b9 --- /dev/null +++ b/testsuite/tar/tar-extracts-from-standard-input @@ -0,0 +1,5 @@ +touch foo +tar cf foo.tar foo +rm foo +cat foo.tar | busybox tar x +test -f foo diff --git a/testsuite/tar/tar-extracts-multiple-files b/testsuite/tar/tar-extracts-multiple-files new file mode 100644 index 000000000..8ae8cdda5 --- /dev/null +++ b/testsuite/tar/tar-extracts-multiple-files @@ -0,0 +1,6 @@ +touch foo bar +busybox tar cf foo.tar foo bar +rm foo bar +busybox tar -xf foo.tar +test -f foo +test -f bar diff --git a/testsuite/tar/tar-extracts-to-standard-output b/testsuite/tar/tar-extracts-to-standard-output new file mode 100644 index 000000000..ca48e364e --- /dev/null +++ b/testsuite/tar/tar-extracts-to-standard-output @@ -0,0 +1,3 @@ +echo foo > foo +tar cf foo.tar foo +cat foo.tar | busybox tar Ox | cmp foo - diff --git a/testsuite/tar/tar-handles-cz-options b/testsuite/tar/tar-handles-cz-options new file mode 100644 index 000000000..5b55e46d3 --- /dev/null +++ b/testsuite/tar/tar-handles-cz-options @@ -0,0 +1,5 @@ +# FEATURE: CONFIG_FEATURE_TAR_CREATE +# FEATURE: CONFIG_FEATURE_TAR_GZIP +touch foo +busybox tar czf foo.tar.gz foo +gzip -d foo.tar.gz diff --git a/testsuite/tar/tar-handles-empty-include-and-non-empty-exclude-list b/testsuite/tar/tar-handles-empty-include-and-non-empty-exclude-list new file mode 100644 index 000000000..503364230 --- /dev/null +++ b/testsuite/tar/tar-handles-empty-include-and-non-empty-exclude-list @@ -0,0 +1,6 @@ +# FEATURE: CONFIG_FEATURE_TAR_FROM +# FEATURE: CONFIG_FEATURE_TAR_CREATE +touch foo +tar cf foo.tar foo +echo foo >foo.exclude +busybox tar xf foo.tar -X foo.exclude diff --git a/testsuite/tar/tar-handles-exclude-and-extract-lists b/testsuite/tar/tar-handles-exclude-and-extract-lists new file mode 100644 index 000000000..2de0f0e91 --- /dev/null +++ b/testsuite/tar/tar-handles-exclude-and-extract-lists @@ -0,0 +1,8 @@ +# FEATURE: CONFIG_FEATURE_TAR_FROM +# FEATURE: CONFIG_FEATURE_TAR_CREATE +touch foo bar baz +tar cf foo.tar foo bar baz +echo foo >foo.exclude +rm foo bar baz +busybox tar xf foo.tar foo bar -X foo.exclude +test ! -f foo -a -f bar -a ! -f baz diff --git a/testsuite/tar/tar-handles-multiple-X-options b/testsuite/tar/tar-handles-multiple-X-options new file mode 100644 index 000000000..155b27e68 --- /dev/null +++ b/testsuite/tar/tar-handles-multiple-X-options @@ -0,0 +1,10 @@ +# FEATURE: CONFIG_FEATURE_TAR_FROM +# FEATURE: CONFIG_FEATURE_TAR_CREATE +touch foo +touch bar +tar cf foo.tar foo bar +echo foo > foo.exclude +echo bar > bar.exclude +rm foo bar +busybox tar xf foo.tar -X foo.exclude -X bar.exclude +test ! -f foo -a ! -f bar diff --git a/testsuite/tar/tar-handles-nested-exclude b/testsuite/tar/tar-handles-nested-exclude new file mode 100644 index 000000000..39013a105 --- /dev/null +++ b/testsuite/tar/tar-handles-nested-exclude @@ -0,0 +1,9 @@ +# FEATURE: CONFIG_FEATURE_TAR_FROM +# FEATURE: CONFIG_FEATURE_TAR_CREATE +mkdir foo +touch foo/bar +tar cf foo.tar foo +rm -rf foo +echo foo/bar >foobar.exclude +busybox tar xf foo.tar foo -X foobar.exclude +test -d foo -a ! -f foo/bar diff --git a/testsuite/tee/tee-appends-input b/testsuite/tee/tee-appends-input new file mode 100644 index 000000000..cff20bf7f --- /dev/null +++ b/testsuite/tee/tee-appends-input @@ -0,0 +1,5 @@ +echo i\'m a little teapot >foo +cp foo bar +echo i\'m a little teapot >>foo +echo i\'m a little teapot | busybox tee -a bar >/dev/null +cmp foo bar diff --git a/testsuite/tee/tee-tees-input b/testsuite/tee/tee-tees-input new file mode 100644 index 000000000..26e217384 --- /dev/null +++ b/testsuite/tee/tee-tees-input @@ -0,0 +1,3 @@ +echo i\'m a little teapot >foo +echo i\'m a little teapot | busybox tee bar >baz +cmp foo bar && cmp foo baz diff --git a/testsuite/testing.sh b/testsuite/testing.sh new file mode 100755 index 000000000..e253e1aa6 --- /dev/null +++ b/testsuite/testing.sh @@ -0,0 +1,154 @@ +# Simple test harness infrastructurei for BusyBox +# +# Copyright 2005 by Rob Landley +# +# License is GPLv2, see LICENSE in the busybox tarball for full license text. + +# This file defines two functions, "testing" and "optionflag" + +# The following environment variables may be set to enable optional behavior +# in "testing": +# VERBOSE - Print the diff -u of each failed test case. +# DEBUG - Enable command tracing. +# SKIP - do not perform this test (this is set by "optionflag") +# +# The "testing" function takes five arguments: +# $1) Description to display when running command +# $2) Command line arguments to command +# $3) Expected result (on stdout) +# $4) Data written to file "input" +# $5) Data written to stdin +# +# The exit value of testing is the exit value of the command it ran. +# +# The environment variable "FAILCOUNT" contains a cumulative total of the +# number of failed tests. + +# The "optional" function is used to skip certain tests, ala: +# optionflag CONFIG_FEATURE_THINGY +# +# The "optional" function checks the environment variable "OPTIONFLAGS", +# which is either empty (in which case it always clears SKIP) or +# else contains a colon-separated list of features (in which case the function +# clears SKIP if the flag was found, or sets it to 1 if the flag was not found). + +export FAILCOUNT=0 +export SKIP= + +# Helper functions + +optional() +{ + option=`echo "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"` + # Not set? + if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ] + then + SKIP="" + return + fi + SKIP=1 +} + +# The testing function + +testing () +{ + NAME="$1" + [ -z "$1" ] && NAME=$2 + + if [ $# -ne 5 ] + then + echo "Test $NAME has the wrong number of arguments ($# $*)" >&2 + exit + fi + + [ -n "$DEBUG" ] && set -x + + if [ -n "$SKIP" ] + then + echo "SKIPPED: $NAME" + return 0 + fi + + echo -ne "$3" > expected + echo -ne "$4" > input + [ -z "$VERBOSE" ] || echo "echo '$5' | $2" + echo -ne "$5" | eval "$2" > actual + RETVAL=$? + + cmp expected actual > /dev/null + if [ $? -ne 0 ] + then + FAILCOUNT=$[$FAILCOUNT+1] + echo "FAIL: $NAME" + [ -n "$VERBOSE" ] && diff -u expected actual + else + echo "PASS: $NAME" + fi + rm -f input expected actual + + [ -n "$DEBUG" ] && set +x + + return $RETVAL +} + +# Recursively grab an executable and all the libraries needed to run it. +# Source paths beginning with / will be copied into destpath, otherwise +# the file is assumed to already be there and only its library dependencies +# are copied. + +function mkchroot +{ + [ $# -lt 2 ] && return + + echo -n . + + dest=$1 + shift + for i in "$@" + do + [ "${i:0:1}" == "/" ] || i=$(which $i) + [ -f "$dest/$i" ] && continue + if [ -e "$i" ] + then + d=`echo "$i" | grep -o '.*/'` && + mkdir -p "$dest/$d" && + cat "$i" > "$dest/$i" && + chmod +x "$dest/$i" + else + echo "Not found: $i" + fi + mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ') + done +} + +# Set up a chroot environment and run commands within it. +# Needed commands listed on command line +# Script fed to stdin. + +function dochroot +{ + mkdir tmpdir4chroot + mount -t ramfs tmpdir4chroot tmpdir4chroot + mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev} + cp -L testing.sh tmpdir4chroot + + # Copy utilities from command line arguments + + echo -n "Setup chroot" + mkchroot tmpdir4chroot $* + echo + + mknod tmpdir4chroot/dev/tty c 5 0 + mknod tmpdir4chroot/dev/null c 1 3 + mknod tmpdir4chroot/dev/zero c 1 5 + + # Copy script from stdin + + cat > tmpdir4chroot/test.sh + chmod +x tmpdir4chroot/test.sh + chroot tmpdir4chroot /test.sh + umount -l tmpdir4chroot + rmdir tmpdir4chroot +} + diff --git a/testsuite/touch/touch-creates-file b/testsuite/touch/touch-creates-file new file mode 100644 index 000000000..4b4935421 --- /dev/null +++ b/testsuite/touch/touch-creates-file @@ -0,0 +1,2 @@ +busybox touch foo +test -f foo diff --git a/testsuite/touch/touch-does-not-create-file b/testsuite/touch/touch-does-not-create-file new file mode 100644 index 000000000..885259286 --- /dev/null +++ b/testsuite/touch/touch-does-not-create-file @@ -0,0 +1,2 @@ +busybox touch -c foo +test ! -f foo diff --git a/testsuite/touch/touch-touches-files-after-non-existent-file b/testsuite/touch/touch-touches-files-after-non-existent-file new file mode 100644 index 000000000..a869ec267 --- /dev/null +++ b/testsuite/touch/touch-touches-files-after-non-existent-file @@ -0,0 +1,3 @@ +touch -t 198001010000 bar +busybox touch -c foo bar +test x"`find bar -mtime -1`" = xbar diff --git a/testsuite/tr/tr-d-works b/testsuite/tr/tr-d-works new file mode 100644 index 000000000..d939e8b0f --- /dev/null +++ b/testsuite/tr/tr-d-works @@ -0,0 +1,4 @@ +echo testing | tr -d aeiou > logfile.gnu +echo testing | busybox tr -d aeiou > logfile.bb + +cmp logfile.gnu logfile.bb diff --git a/testsuite/tr/tr-non-gnu b/testsuite/tr/tr-non-gnu new file mode 100644 index 000000000..ffa6951ae --- /dev/null +++ b/testsuite/tr/tr-non-gnu @@ -0,0 +1 @@ +echo fdhrnzvfu bffvsentr | busybox tr '[a-z]' '[n-z][a-m]' diff --git a/testsuite/tr/tr-works b/testsuite/tr/tr-works new file mode 100644 index 000000000..9b2e90e42 --- /dev/null +++ b/testsuite/tr/tr-works @@ -0,0 +1,24 @@ +run_tr () +{ + echo -n "echo '$1' | tr '$2' '$3': " + echo "$1" | $bb tr "$2" "$3" + echo +} +tr_test () +{ + run_tr "cbaab" abc zyx + run_tr "TESTING A B C" '[A-Z]' '[a-z]' + run_tr "abc[]" "a[b" AXB + run_tr abc '[:alpha:]' A-ZA-Z + run_tr abc56 '[:alnum:]' A-ZA-Zxxxxxxxxxx + run_tr 012 '[:digit:]' abcdefghi + run_tr abc56 '[:lower:]' '[:upper:]' + run_tr " " '[:space:]' 12345 + run_tr " " '[:blank:]' 12 + run_tr 'a b' '[= =]' X + run_tr "[:" '[:' ab +} + +bb= tr_test > logfile.gnu +bb=busybox tr_test > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/true/true-is-silent b/testsuite/true/true-is-silent new file mode 100644 index 000000000..1d1bdb22f --- /dev/null +++ b/testsuite/true/true-is-silent @@ -0,0 +1 @@ +busybox true 2>&1 | cmp - /dev/null diff --git a/testsuite/true/true-returns-success b/testsuite/true/true-returns-success new file mode 100644 index 000000000..cdf2d55e5 --- /dev/null +++ b/testsuite/true/true-returns-success @@ -0,0 +1 @@ +busybox true diff --git a/testsuite/umlwrapper.sh b/testsuite/umlwrapper.sh new file mode 100755 index 000000000..e55e4dbd3 --- /dev/null +++ b/testsuite/umlwrapper.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# Wrapper for User Mode Linux emulation environment + +RUNFILE="$(pwd)/${1}.testroot" +if [ -z "$RUNFILE" ] || [ ! -x "$RUNFILE" ] +then + echo "Can't run '$RUNFILE'" + exit 1 +fi + +shift + +if [ -z $(which linux) ] +then + echo "No User Mode Linux." + exit 1; +fi + +linux rootfstype=hostfs rw init="$RUNFILE" TESTDIR=`pwd` PATH="$PATH" $* quiet diff --git a/testsuite/uniq.tests b/testsuite/uniq.tests new file mode 100755 index 000000000..49d4bed9c --- /dev/null +++ b/testsuite/uniq.tests @@ -0,0 +1,70 @@ +#!/bin/sh + +# SUSv3 compliant uniq tests. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +# AUDIT: Full SUSv3 coverage (except internationalization). + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" +# file input will be file called "input" +# test can create a file "actual" instead of writing to stdout + +# Test exit status + +testing "uniq (exit with error)" "uniq nonexistent 2> /dev/null || echo yes" \ + "yes\n" "" "" +testing "uniq (exit success)" "uniq /dev/null && echo yes" "yes\n" "" "" + +# Test various data sources and destinations + +testing "uniq (default to stdin)" "uniq" "one\ntwo\nthree\n" "" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" +testing "uniq - (specify stdin)" "uniq -" "one\ntwo\nthree\n" "" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" +testing "uniq input (specify file)" "uniq input" "one\ntwo\nthree\n" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" "" + +testing "uniq input outfile (two files)" "uniq input actual > /dev/null" \ + "one\ntwo\nthree\n" "one\ntwo\ntwo\nthree\nthree\nthree\n" "" +testing "uniq (stdin) outfile" "uniq - actual" \ + "one\ntwo\nthree\n" "" "one\ntwo\ntwo\nthree\nthree\nthree\n" +# Note: SUSv3 doesn't seem to require support for "-" output, but we do anyway. +testing "uniq input - (specify stdout)" "uniq input -" \ + "one\ntwo\nthree\n" "one\ntwo\ntwo\nthree\nthree\nthree\n" "" + + +#-f skip fields +#-s skip chars +#-c occurrences +#-d dups only +#-u + +# Test various command line options + +# Leading whitespace is a minor technical violation of the spec, +# but since gnu does it... +testing "uniq -c (occurrence count)" "uniq -c | sed 's/^[ \t]*//'" \ + "1 one\n2 two\n3 three\n" "" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" +testing "uniq -d (dups only) " "uniq -d" "two\nthree\n" "" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" + +testing "uniq -f -s (skip fields and chars)" "uniq -f2 -s 3" \ +"cc dd ee8 +aa bb cc9 +" "" \ +"cc dd ee8 +bb cc dd8 +aa bb cc9 +" + +# -d is "Suppress the writing fo lines that are not repeated in the input." +# -u is "Suppress the writing of lines that are repeated in the input." +# Therefore, together this means they should produce no output. +testing "uniq -u and -d produce no output" "uniq -d -u" "" "" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" + +exit $FAILCOUNT diff --git a/testsuite/unzip.tests b/testsuite/unzip.tests new file mode 100755 index 000000000..77814886f --- /dev/null +++ b/testsuite/unzip.tests @@ -0,0 +1,38 @@ +#!/bin/sh + +# Tests for unzip. +# Copyright 2006 Rob Landley <rob@landley.net> +# Copyright 2006 Glenn McGrath <bug1@ihug.co.nz> +# Licensed under GPL v2, see file LICENSE for details. + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" +# file input will be file called "input" +# test can create a file "actual" instead of writing to stdout + +# Create a scratch directory + +mkdir temp +cd temp + +# Create test file to work with. + +mkdir foo +touch foo/bar +zip foo.zip foo foo/bar > /dev/null +rm -f foo/bar +rmdir foo + +# Test that unzipping just foo doesn't create bar. +testing "unzip (subdir only)" "unzip -q foo.zip foo/ && test -d foo && test ! -f foo/bar && echo yes" "yes\n" "" "" + +rmdir foo +rm foo.zip + +# Clean up scratch directory. + +cd .. +rm -rf temp + +exit $FAILCOUNT diff --git a/testsuite/uptime/uptime-works b/testsuite/uptime/uptime-works new file mode 100644 index 000000000..80e578778 --- /dev/null +++ b/testsuite/uptime/uptime-works @@ -0,0 +1,2 @@ +busybox uptime + diff --git a/testsuite/uuencode.tests b/testsuite/uuencode.tests new file mode 100755 index 000000000..cb658db3b --- /dev/null +++ b/testsuite/uuencode.tests @@ -0,0 +1,28 @@ +#!/bin/sh + +# unit test for uuencode to test functionality. +# Copyright 2006 by Erik Hovland <erik@hovland.org> +# Licensed under GPL v2, see file LICENSE for details. + +# AUDIT: Unit tests for uuencode + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" +# file input will be file called "input" +# test can create a file "actual" instead of writing to stdout + +# Test setup of standard input +saved_umask=$(umask) +umask 0 +testing "uuencode sets standard input mode correctly" \ + "uuencode foo </dev/null | head -n 1 | grep -q 666 && echo yes" "yes\n" "" "" +umask $saved_umask + +testing "uuencode correct encoding" "uuencode bb_uuenc_test.out" \ +"begin 644 bb_uuenc_test.out\nM5&AE(&9A<W0@9W)E>2!F;W@@:G5M<&5D(&]V97(@=&AE(&QA>GD@8G)O=VX@\n%9&]G+@H\`\n\`\nend\n" \ + "" "The fast grey fox jumped over the lazy brown dog.\n" +testing "uuencode correct base64 encoding" "uuencode -m bb_uuenc_test.out" \ +"begin-base64 644 bb_uuenc_test.out\nVGhlIGZhc3QgZ3JleSBmb3gganVtcGVkIG92ZXIgdGhlIGxhenkgYnJvd24g\nZG9nLgo=\n====\n" \ + "" "The fast grey fox jumped over the lazy brown dog.\n" +exit $FAILCOUNT diff --git a/testsuite/wc/wc-counts-all b/testsuite/wc/wc-counts-all new file mode 100644 index 000000000..5e2cb6e4a --- /dev/null +++ b/testsuite/wc/wc-counts-all @@ -0,0 +1 @@ +test "`echo i\'m a little teapot | busybox wc`" = ' 1 4 20' diff --git a/testsuite/wc/wc-counts-characters b/testsuite/wc/wc-counts-characters new file mode 100644 index 000000000..755864684 --- /dev/null +++ b/testsuite/wc/wc-counts-characters @@ -0,0 +1 @@ +test `echo i\'m a little teapot | busybox wc -c` -eq 20 diff --git a/testsuite/wc/wc-counts-lines b/testsuite/wc/wc-counts-lines new file mode 100644 index 000000000..5be6ed089 --- /dev/null +++ b/testsuite/wc/wc-counts-lines @@ -0,0 +1 @@ +test `echo i\'m a little teapot | busybox wc -l` -eq 1 diff --git a/testsuite/wc/wc-counts-words b/testsuite/wc/wc-counts-words new file mode 100644 index 000000000..331650e95 --- /dev/null +++ b/testsuite/wc/wc-counts-words @@ -0,0 +1 @@ +test `echo i\'m a little teapot | busybox wc -w` -eq 4 diff --git a/testsuite/wc/wc-prints-longest-line-length b/testsuite/wc/wc-prints-longest-line-length new file mode 100644 index 000000000..78831fc13 --- /dev/null +++ b/testsuite/wc/wc-prints-longest-line-length @@ -0,0 +1 @@ +test `echo i\'m a little teapot | busybox wc -L` -eq 19 diff --git a/testsuite/wget/wget--O-overrides--P b/testsuite/wget/wget--O-overrides--P new file mode 100644 index 000000000..fdb5d47c0 --- /dev/null +++ b/testsuite/wget/wget--O-overrides--P @@ -0,0 +1,3 @@ +mkdir foo +busybox wget -q -O index.html -P foo http://www.google.com/ +test -s index.html diff --git a/testsuite/wget/wget-handles-empty-path b/testsuite/wget/wget-handles-empty-path new file mode 100644 index 000000000..5b591837a --- /dev/null +++ b/testsuite/wget/wget-handles-empty-path @@ -0,0 +1 @@ +busybox wget http://www.google.com diff --git a/testsuite/wget/wget-retrieves-google-index b/testsuite/wget/wget-retrieves-google-index new file mode 100644 index 000000000..7be9a8087 --- /dev/null +++ b/testsuite/wget/wget-retrieves-google-index @@ -0,0 +1,2 @@ +busybox wget -q -O foo http://www.google.com/ +test -s foo diff --git a/testsuite/wget/wget-supports--P b/testsuite/wget/wget-supports--P new file mode 100644 index 000000000..9b4d095e6 --- /dev/null +++ b/testsuite/wget/wget-supports--P @@ -0,0 +1,3 @@ +mkdir foo +busybox wget -q -P foo http://www.google.com/ +test -s foo/index.html diff --git a/testsuite/which/which-uses-default-path b/testsuite/which/which-uses-default-path new file mode 100644 index 000000000..63ceb9f8f --- /dev/null +++ b/testsuite/which/which-uses-default-path @@ -0,0 +1,4 @@ +BUSYBOX=$(type -p busybox) +SAVED_PATH=$PATH +unset PATH +$BUSYBOX which ls diff --git a/testsuite/xargs/xargs-works b/testsuite/xargs/xargs-works new file mode 100644 index 000000000..c95869e89 --- /dev/null +++ b/testsuite/xargs/xargs-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +find "$d" -name \*works -type f | xargs md5sum > logfile.gnu +find "$d" -name \*works -type f | busybox xargs md5sum > logfile.bb +diff -u logfile.gnu logfile.bb diff --git a/util-linux/Config.in b/util-linux/Config.in new file mode 100644 index 000000000..848914d6f --- /dev/null +++ b/util-linux/Config.in @@ -0,0 +1,526 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Linux System Utilities" + +config DMESG + bool "dmesg" + default n + help + dmesg is used to examine or control the kernel ring buffer. When the + Linux kernel prints messages to the system log, they are stored in + the kernel ring buffer. You can use dmesg to print the kernel's ring + buffer, clear the kernel ring buffer, change the size of the kernel + ring buffer, and change the priority level at which kernel messages + are also logged to the system console. Enable this option if you + wish to enable the 'dmesg' utility. + +config FEATURE_DMESG_PRETTY + bool "pretty dmesg output" + default y + depends on DMESG + help + If you wish to scrub the syslog level from the output, say 'Y' here. + The syslog level is a string prefixed to every line with the form "<#>". + + With this option you will see: + # dmesg + Linux version 2.6.17.4 ..... + BIOS-provided physical RAM map: + BIOS-e820: 0000000000000000 - 000000000009f000 (usable) + + Without this option you will see: + # dmesg + <5>Linux version 2.6.17.4 ..... + <6>BIOS-provided physical RAM map: + <6> BIOS-e820: 0000000000000000 - 000000000009f000 (usable) + +config FBSET + bool "fbset" + default n + help + fbset is used to show or change the settings of a Linux frame buffer + device. The frame buffer device provides a simple and unique + interface to access a graphics display. Enable this option + if you wish to enable the 'fbset' utility. + +config FEATURE_FBSET_FANCY + bool "Turn on extra fbset options" + default n + depends on FBSET + help + This option enables extended fbset options, allowing one to set the + framebuffer size, color depth, etc. interface to access a graphics + display. Enable this option if you wish to enable extended fbset + options. + +config FEATURE_FBSET_READMODE + bool "Turn on fbset readmode support" + default n + depends on FBSET + help + This option allows fbset to read the video mode database stored by + default as /etc/fb.modes, which can be used to set frame buffer + device to pre-defined video modes. + +config FDFLUSH + bool "fdflush" + default n + help + fdflush is only needed when changing media on slightly-broken + removable media drives. It is used to make Linux believe that a + hardware disk-change switch has been actuated, which causes Linux to + forget anything it has cached from the previous media. If you have + such a slightly-broken drive, you will need to run fdflush every time + you change a disk. Most people have working hardware and can safely + leave this disabled. + +config FDFORMAT + bool "fdformat" + default n + help + fdformat is used to low-level format a floppy disk. + +config FDISK + bool "fdisk" + default n + help + The fdisk utility is used to divide hard disks into one or more + logical disks, which are generally called partitions. This utility + can be used to list and edit the set of partitions or BSD style + 'disk slices' that are defined on a hard drive. + +config FDISK_SUPPORT_LARGE_DISKS + bool "support over 4GB disks" + default y + depends on FDISK + help + Enable this option to support large disks > 4GB. + +config FEATURE_FDISK_WRITABLE + bool "Write support" + default y + depends on FDISK + help + Enabling this option allows you to create or change a partition table + and write those changes out to disk. If you leave this option + disabled, you will only be able to view the partition table. + +config FEATURE_AIX_LABEL + bool "Support AIX disklabels" + default n + depends on FDISK && FEATURE_FDISK_WRITABLE + help + Enabling this option allows you to create or change AIX disklabels. + Most people can safely leave this option disabled. + +config FEATURE_SGI_LABEL + bool "Support SGI disklabels" + default n + depends on FDISK && FEATURE_FDISK_WRITABLE + help + Enabling this option allows you to create or change SGI disklabels. + Most people can safely leave this option disabled. + +config FEATURE_SUN_LABEL + bool "Support SUN disklabels" + default n + depends on FDISK && FEATURE_FDISK_WRITABLE + help + Enabling this option allows you to create or change SUN disklabels. + Most people can safely leave this option disabled. + +config FEATURE_OSF_LABEL + bool "Support BSD disklabels" + default n + depends on FDISK && FEATURE_FDISK_WRITABLE + help + Enabling this option allows you to create or change BSD disklabels + and define and edit BSD disk slices. + +config FEATURE_FDISK_ADVANCED + bool "Support expert mode" + default n + depends on FDISK && FEATURE_FDISK_WRITABLE + help + Enabling this option allows you to do terribly unsafe things like + define arbitrary drive geometry, move the beginning of data in a + partition, and similarly evil things. Unless you have a very good + reason you would be wise to leave this disabled. + +config FREERAMDISK + bool "freeramdisk" + default n + help + Linux allows you to create ramdisks. This utility allows you to + delete them and completely free all memory that was used for the + ramdisk. For example, if you boot Linux into a ramdisk and later + pivot_root, you may want to free the memory that is allocated to the + ramdisk. If you have no use for freeing memory from a ramdisk, leave + this disabled. + +config FSCK_MINIX + bool "fsck_minix" + default n + help + The minix filesystem is a nice, small, compact, read-write filesystem + with little overhead. It is not a journaling filesystem however and + can experience corruption if it is not properly unmounted or if the + power goes off in the middle of a write. This utility allows you to + check for and attempt to repair any corruption that occurs to a minix + filesystem. + +config MKFS_MINIX + bool "mkfs_minix" + default n + help + The minix filesystem is a nice, small, compact, read-write filesystem + with little overhead. If you wish to be able to create minix filesystems + this utility will do the job for you. + +comment "Minix filesystem support" + depends on FSCK_MINIX || MKFS_MINIX + +config FEATURE_MINIX2 + bool "Support Minix fs v2 (fsck_minix/mkfs_minix)" + default y + depends on FSCK_MINIX || MKFS_MINIX + help + If you wish to be able to create version 2 minix filesystems, enable this. + If you enabled 'mkfs_minix' then you almost certainly want to be using the + version 2 filesystem support. + +config GETOPT + bool "getopt" + default n + help + The getopt utility is used to break up (parse) options in command + lines to make it easy to write complex shell scripts that also check + for legal (and illegal) options. If you want to write horribly + complex shell scripts, or use some horribly complex shell script + written by others, this utility may be for you. Most people will + wisely leave this disabled. + +config HEXDUMP + bool "hexdump" + default n + help + The hexdump utility is used to display binary data in a readable + way that is comparable to the output from most hex editors. + +config HWCLOCK + bool "hwclock" + default n + help + The hwclock utility is used to read and set the hardware clock + on a system. This is primarily used to set the current time on + shutdown in the hardware clock, so the hardware will keep the + correct time when Linux is _not_ running. + +config FEATURE_HWCLOCK_LONG_OPTIONS + bool "Support long options (--hctosys,...)" + default n + depends on HWCLOCK && GETOPT_LONG + help + By default, the hwclock utility only uses short options. If you + are overly fond of its long options, such as --hctosys, --utc, etc) + then enable this option. + +config FEATURE_HWCLOCK_ADJTIME_FHS + bool "Use FHS /var/lib/hwclock/adjtime" + default y + depends on HWCLOCK + help + Starting with FHS 2.3, the adjtime state file is supposed to exist + at /var/lib/hwclock/adjtime instead of /etc/adjtime. If you wish + to use the FHS behavior, answer Y here, otherwise answer N for the + classic /etc/adjtime path. + + http://www.pathname.com/fhs/pub/fhs-2.3.html#VARLIBHWCLOCKSTATEDIRECTORYFORHWCLO + +config IPCRM + bool "ipcrm" + default n + select FEATURE_SUID + help + The ipcrm utility allows the removal of System V interprocess + communication (IPC) objects and the associated data structures + from the system. + +config IPCS + bool "ipcs" + default n + select FEATURE_SUID + help + The ipcs utility is used to provide information on the currently + allocated System V interprocess (IPC) objects in the system. + +config LOSETUP + bool "losetup" + default n + help + losetup is used to associate or detach a loop device with a regular + file or block device, and to query the status of a loop device. This + version does not currently support enabling data encryption. + +config MDEV + bool "mdev" + default n + help + mdev is a mini-udev implementation: call it with -s to populate + /dev from /sys, then "echo /sbin/mdev > /proc/sys/kernel/hotplug" to + have it handle hotplug events afterwards. Device names are taken + from sysfs. + +config FEATURE_MDEV_CONF + bool "Support /etc/mdev.conf" + default n + depends on MDEV + help + The mdev config file contains lines that look like: + + hd[a-z][0-9]* 0:3 660 + + That's device name (with regex match), uid:gid, and permissions. + + Config file parsing stops on the first matching line. If no config + entry is matched, devices are created with default 0:0 660. (Make + the last line match .* to override this.) + +config FEATURE_MDEV_EXEC + bool "Support command execution at device addition/removal" + default n + depends on FEATURE_MDEV_CONF + help + This adds support for an optional field to /etc/mdev.conf, consisting + of a special character and a command line to run after creating the + corresponding device(s) and before removing, ala: + + hdc root:cdrom 660 *ln -s $MDEV cdrom + + The $MDEV environment variable is set to the name of the device. + + The special characters and their meanings are: + @ Run after creating the device. + $ Run before removing the device. + * Run both after creating and before removing the device. + + Commands are executed via system() so you need /bin/sh, meaning you + probably want to select a default shell in the Shells menu. + +config MKSWAP + bool "mkswap" + default n + help + The mkswap utility is used to configure a file or disk partition as + Linux swap space. This allows Linux to use the entire file or + partition as if it were additional RAM, which can greatly increase + the capability of low-memory machines. This additional memory is + much slower than real RAM, but can be very helpful at preventing your + applications being killed by the Linux out of memory (OOM) killer. + Once you have created swap space using 'mkswap' you need to enable + the swap space using the 'swapon' utility. + +config FEATURE_MKSWAP_V0 + bool "version 0 support" + default n + depends on MKSWAP +# depends on MKSWAP && DEPRECATED + help + Enable support for the old v0 style. + If your kernel is older than 2.1.117, then v0 support is the + only option. + +config MORE + bool "more" + default n + help + more is a simple utility which allows you to read text one screen + sized page at a time. If you want to read text that is larger than + the screen, and you are using anything faster than a 300 baud modem, + you will probably find this utility very helpful. If you don't have + any need to reading text files, you can leave this disabled. + +config FEATURE_USE_TERMIOS + bool "Use termios to manipulate the screen" + default y + depends on MORE + help + This option allows utilities such as 'more' and 'top' to determine + the size of the screen. If you leave this disabled, your utilities + that display things on the screen will be especially primitive and + will be unable to determine the current screen size, and will be + unable to move the cursor. + +config MOUNT + bool "mount" + default n + help + All files and filesystems in Unix are arranged into one big directory + tree. The 'mount' utility is used to graft a filesystem onto a + particular part of the tree. A filesystem can either live on a block + device, or it can be accessible over the network, as is the case with + NFS filesystems. Most people using BusyBox will also want to enable + the 'mount' utility. + +config FEATURE_MOUNT_NFS + bool "Support mounting NFS file systems" + default n + depends on MOUNT + depends on FEATURE_HAVE_RPC + select FEATURE_SYSLOG + help + Enable mounting of NFS file systems. + +config FEATURE_MOUNT_CIFS + bool "Support mounting CIFS/SMB file systems" + default n + depends on MOUNT + help + Enable support for samba mounts. + +config FEATURE_MOUNT_FLAGS + depends on MOUNT + bool "Support lots of -o flags in mount" + default y + help + Without this, mount only supports ro/rw/remount. With this, it + supports nosuid, suid, dev, nodev, exec, noexec, sync, async, atime, + noatime, diratime, nodiratime, loud, bind, move, shared, slave, + private, unbindable, rshared, rslave, rprivate, and runbindable. + +config FEATURE_MOUNT_FSTAB + depends on MOUNT + bool "Support /etc/fstab and -a" + default y + help + Support mount all and looking for files in /etc/fstab. + +config PIVOT_ROOT + bool "pivot_root" + default n + help + The pivot_root utility swaps the mount points for the root filesystem + with some other mounted filesystem. This allows you to do all sorts + of wild and crazy things with your Linux system and is far more + powerful than 'chroot'. + + Note: This is for initrd in linux 2.4. Under initramfs (introduced + in linux 2.6) use switch_root instead. + +config RDATE + bool "rdate" + default n + help + The rdate utility allows you to synchronize the date and time of your + system clock with the date and time of a remote networked system using + the RFC868 protocol, which is built into the inetd daemon on most + systems. + +config READPROFILE + bool "readprofile" + default n + help + This allows you to parse /proc/profile for basic profiling. + +config SETARCH + bool "setarch" + default n + help + The linux32 utility is used to create a 32bit environment for the + specified program (usually a shell). It only makes sense to have + this util on a system that supports both 64bit and 32bit userland + (like amd64/x86, ppc64/ppc, sparc64/sparc, etc...). + +config SWAPONOFF + bool "swaponoff" + default n + help + This option enables both the 'swapon' and the 'swapoff' utilities. + Once you have created some swap space using 'mkswap', you also need + to enable your swap space with the 'swapon' utility. The 'swapoff' + utility is used, typically at system shutdown, to disable any swap + space. If you are not using any swap space, you can leave this + option disabled. + +config SWITCH_ROOT + bool "switch_root" + default n + help + The switch_root utility is used from initramfs to select a new + root device. Under initramfs, you have to use this instead of + pivot_root. (Stop reading here if you don't care why.) + + Booting with initramfs extracts a gzipped cpio archive into rootfs + (which is a variant of ramfs/tmpfs). Because rootfs can't be moved + or unmounted*, pivot_root will not work from initramfs. Instead, + switch_root deletes everything out of rootfs (including itself), + does a mount --move that overmounts rootfs with the new root, and + then execs the specified init program. + + * Because the Linux kernel uses rootfs internally as the starting + and ending point for searching through the kernel's doubly linked + list of active mount points. That's why. + +config UMOUNT + bool "umount" + default n + help + When you want to remove a mounted filesystem from its current mount point, + for example when you are shutting down the system, the 'umount' utility is + the tool to use. If you enabled the 'mount' utility, you almost certainly + also want to enable 'umount'. + +config FEATURE_UMOUNT_ALL + bool "umount -a option" + default n + depends on UMOUNT + help + Support -a option to unmount all currently mounted filesystems. + +comment "Common options for mount/umount" + depends on MOUNT || UMOUNT + +config FEATURE_MOUNT_LOOP + bool "Support loopback mounts" + default n + depends on MOUNT || UMOUNT + help + Enabling this feature allows automatic mounting of files (containing + filesystem images) via the linux kernel's loopback devices. The mount + command will detect you are trying to mount a file instead of a block + device, and transparently associate the file with a loopback device. + The umount command will also free that loopback device. + + You can still use the 'losetup' utility (to manually associate files + with loop devices) if you need to do something advanced, such as + specify an offset or cryptographic options to the loopback device. + (If you don't want umount to free the loop device, use "umount -D".) + +config FEATURE_MTAB_SUPPORT + bool "Support for the old /etc/mtab file" + default n + depends on MOUNT || UMOUNT + help + Historically, Unix systems kept track of the currently mounted + partitions in the file "/etc/mtab". These days, the kernel exports + the list of currently mounted partitions in "/proc/mounts", rendering + the old mtab file obsolete. (In modern systems, /etc/mtab should be + a symlink to /proc/mounts.) + + The only reason to have mount maintain an /etc/mtab file itself is if + your stripped-down embedded system does not have a /proc directory. + If you must use this, keep in mind it's inherently brittle (for + example a mount under chroot won't update it), can't handle modern + features like separate per-process filesystem namespaces, requires + that your /etc directory be writeable, tends to get easily confused + by --bind or --move mounts, won't update if you rename a directory + that contains a mount point, and so on. (In brief: avoid.) + + About the only reason to use this is if you've removed /proc from + your kernel. + +endmenu + diff --git a/util-linux/Kbuild b/util-linux/Kbuild new file mode 100644 index 000000000..cc1d0e05d --- /dev/null +++ b/util-linux/Kbuild @@ -0,0 +1,32 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org> +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_DMESG) +=dmesg.o +lib-$(CONFIG_FBSET) +=fbset.o +lib-$(CONFIG_FDFLUSH) +=freeramdisk.o +lib-$(CONFIG_FDFORMAT) +=fdformat.o +lib-$(CONFIG_FDISK) +=fdisk.o +lib-$(CONFIG_FREERAMDISK) +=freeramdisk.o +lib-$(CONFIG_FSCK_MINIX) +=fsck_minix.o +lib-$(CONFIG_GETOPT) +=getopt.o +lib-$(CONFIG_HEXDUMP) +=hexdump.o +lib-$(CONFIG_HWCLOCK) +=hwclock.o +lib-$(CONFIG_IPCRM) +=ipcrm.o +lib-$(CONFIG_IPCS) +=ipcs.o +lib-$(CONFIG_LOSETUP) +=losetup.o +lib-$(CONFIG_MDEV) +=mdev.o +lib-$(CONFIG_MKFS_MINIX) +=mkfs_minix.o +lib-$(CONFIG_MKSWAP) +=mkswap.o +lib-$(CONFIG_MORE) +=more.o +lib-$(CONFIG_MOUNT) +=mount.o +lib-$(CONFIG_PIVOT_ROOT) +=pivot_root.o +lib-$(CONFIG_RDATE) +=rdate.o +lib-$(CONFIG_READPROFILE) +=readprofile.o +lib-$(CONFIG_SETARCH) +=setarch.o +lib-$(CONFIG_SWAPONOFF) +=swaponoff.o +lib-$(CONFIG_SWITCH_ROOT) +=switch_root.o +lib-$(CONFIG_UMOUNT) +=umount.o diff --git a/util-linux/dmesg.c b/util-linux/dmesg.c new file mode 100644 index 000000000..658cddc38 --- /dev/null +++ b/util-linux/dmesg.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * + * dmesg - display/control kernel ring buffer. + * + * Copyright 2006 Rob Landley <rob@landley.net> + * Copyright 2006 Bernhard Fischer <rep.nop@aon.at> + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <unistd.h> +#include <sys/klog.h> + +int dmesg_main(int argc, char *argv[]) +{ + char *size, *level; + int flags = getopt32(argc, argv, "cs:n:", &size, &level); + + if (flags & 4) { + if (klogctl(8, NULL, xatoul_range(level, 0, 10))) + bb_perror_msg_and_die("klogctl"); + } else { + int len; + char *buf; + + len = (flags & 2) ? xatoul_range(size, 2, INT_MAX) : 16384; + buf = xmalloc(len); + if (0 > (len = klogctl(3 + (flags & 1), buf, len))) + bb_perror_msg_and_die("klogctl"); + + // Skip <#> at the start of lines, and make sure we end with a newline. + + if (ENABLE_FEATURE_DMESG_PRETTY) { + int last = '\n'; + int in; + + for (in = 0; in<len;) { + if (last == '\n' && buf[in] == '<') in += 3; + else putchar(last = buf[in++]); + } + if (last != '\n') putchar('\n'); + } else { + write(1,buf,len); + if (len && buf[len-1]!='\n') putchar('\n'); + } + + if (ENABLE_FEATURE_CLEAN_UP) free(buf); + } + + return 0; +} diff --git a/util-linux/fbset.c b/util-linux/fbset.c new file mode 100644 index 000000000..1aa0a0ac1 --- /dev/null +++ b/util-linux/fbset.c @@ -0,0 +1,407 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini fbset implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung <tausq@debian.org> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * This is a from-scratch implementation of fbset; but the de facto fbset + * implementation was a good reference. fbset (original) is released under + * the GPL, and is (c) 1995-1999 by: + * Geert Uytterhoeven (Geert.Uytterhoeven@cs.kuleuven.ac.be) + */ + +#include "busybox.h" + +#define DEFAULTFBDEV FB_0 +#define DEFAULTFBMODE "/etc/fb.modes" + +enum { + OPT_CHANGE = (1 << 0), + OPT_INFO = (1 << 1), + OPT_READMODE = (1 << 2), + OPT_ALL = (1 << 9), + + CMD_FB = 1, + CMD_DB = 2, + CMD_GEOMETRY = 3, + CMD_TIMING = 4, + CMD_ACCEL = 5, + CMD_HSYNC = 6, + CMD_VSYNC = 7, + CMD_LACED = 8, + CMD_DOUBLE = 9, +/* CMD_XCOMPAT = 10, */ + CMD_ALL = 11, + CMD_INFO = 12, + CMD_CHANGE = 13, + +#ifdef CONFIG_FEATURE_FBSET_FANCY + CMD_XRES = 100, + CMD_YRES = 101, + CMD_VXRES = 102, + CMD_VYRES = 103, + CMD_DEPTH = 104, + CMD_MATCH = 105, + CMD_PIXCLOCK = 106, + CMD_LEFT = 107, + CMD_RIGHT = 108, + CMD_UPPER = 109, + CMD_LOWER = 110, + CMD_HSLEN = 111, + CMD_VSLEN = 112, + CMD_CSYNC = 113, + CMD_GSYNC = 114, + CMD_EXTSYNC = 115, + CMD_BCAST = 116, + CMD_RGBA = 117, + CMD_STEP = 118, + CMD_MOVE = 119, +#endif +}; + +static unsigned int g_options = 0; + +/* Stuff stolen from the kernel's fb.h */ +#define FB_ACTIVATE_ALL 64 +enum { + FBIOGET_VSCREENINFO = 0x4600, + FBIOPUT_VSCREENINFO = 0x4601 +}; +struct fb_bitfield { + uint32_t offset; /* beginning of bitfield */ + uint32_t length; /* length of bitfield */ + uint32_t msb_right; /* !=0: Most significant bit is right */ +}; +struct fb_var_screeninfo { + uint32_t xres; /* visible resolution */ + uint32_t yres; + uint32_t xres_virtual; /* virtual resolution */ + uint32_t yres_virtual; + uint32_t xoffset; /* offset from virtual to visible */ + uint32_t yoffset; /* resolution */ + + uint32_t bits_per_pixel; + uint32_t grayscale; /* !=0 Graylevels instead of colors */ + + struct fb_bitfield red; /* bitfield in fb mem if true color, */ + struct fb_bitfield green; /* else only length is significant */ + struct fb_bitfield blue; + struct fb_bitfield transp; /* transparency */ + + uint32_t nonstd; /* !=0 Non standard pixel format */ + + uint32_t activate; /* see FB_ACTIVATE_x */ + + uint32_t height; /* height of picture in mm */ + uint32_t width; /* width of picture in mm */ + + uint32_t accel_flags; /* acceleration flags (hints) */ + + /* Timing: All values in pixclocks, except pixclock (of course) */ + uint32_t pixclock; /* pixel clock in ps (pico seconds) */ + uint32_t left_margin; /* time from sync to picture */ + uint32_t right_margin; /* time from picture to sync */ + uint32_t upper_margin; /* time from sync to picture */ + uint32_t lower_margin; + uint32_t hsync_len; /* length of horizontal sync */ + uint32_t vsync_len; /* length of vertical sync */ + uint32_t sync; /* see FB_SYNC_x */ + uint32_t vmode; /* see FB_VMODE_x */ + uint32_t reserved[6]; /* Reserved for future compatibility */ +}; + + +static const struct cmdoptions_t { + const char name[10]; + const unsigned char param_count; + const unsigned char code; +} g_cmdoptions[] = { + { "-fb", 1, CMD_FB }, + { "-db", 1, CMD_DB }, + { "-a", 0, CMD_ALL }, + { "-i", 0, CMD_INFO }, + { "-g", 5, CMD_GEOMETRY }, + { "-t", 7, CMD_TIMING }, + { "-accel", 1, CMD_ACCEL }, + { "-hsync", 1, CMD_HSYNC }, + { "-vsync", 1, CMD_VSYNC }, + { "-laced", 1, CMD_LACED }, + { "-double", 1, CMD_DOUBLE }, + { "-n", 0, CMD_CHANGE }, +#ifdef CONFIG_FEATURE_FBSET_FANCY + { "-all", 0, CMD_ALL }, + { "-xres", 1, CMD_XRES }, + { "-yres", 1, CMD_YRES }, + { "-vxres", 1, CMD_VXRES }, + { "-vyres", 1, CMD_VYRES }, + { "-depth", 1, CMD_DEPTH }, + { "-match", 0, CMD_MATCH }, + { "-geometry", 5, CMD_GEOMETRY }, + { "-pixclock", 1, CMD_PIXCLOCK }, + { "-left", 1, CMD_LEFT }, + { "-right", 1, CMD_RIGHT }, + { "-upper", 1, CMD_UPPER }, + { "-lower", 1, CMD_LOWER }, + { "-hslen", 1, CMD_HSLEN }, + { "-vslen", 1, CMD_VSLEN }, + { "-timings", 7, CMD_TIMING }, + { "-csync", 1, CMD_CSYNC }, + { "-gsync", 1, CMD_GSYNC }, + { "-extsync", 1, CMD_EXTSYNC }, + { "-bcast", 1, CMD_BCAST }, + { "-rgba", 1, CMD_RGBA }, + { "-step", 1, CMD_STEP }, + { "-move", 1, CMD_MOVE }, +#endif + { "", 0, 0 } +}; + +#ifdef CONFIG_FEATURE_FBSET_READMODE +/* taken from linux/fb.h */ +enum { + FB_VMODE_INTERLACED = 1, /* interlaced */ + FB_VMODE_DOUBLE = 2, /* double scan */ + FB_SYNC_HOR_HIGH_ACT = 1, /* horizontal sync high active */ + FB_SYNC_VERT_HIGH_ACT = 2, /* vertical sync high active */ + FB_SYNC_EXT = 4, /* external sync */ + FB_SYNC_COMP_HIGH_ACT = 8 /* composite sync high active */ +}; +#endif + +static int readmode(struct fb_var_screeninfo *base, const char *fn, + const char *mode) +{ +#ifdef CONFIG_FEATURE_FBSET_READMODE + FILE *f; + char buf[256]; + char *p = buf; + + f = xfopen(fn, "r"); + while (!feof(f)) { + fgets(buf, sizeof(buf), f); + if (!(p = strstr(buf, "mode ")) && !(p = strstr(buf, "mode\t"))) + continue; + p += 5; + if (!(p = strstr(buf, mode))) + continue; + p += strlen(mode); + if (!isspace(*p) && (*p != 0) && (*p != '"') + && (*p != '\r') && (*p != '\n')) + continue; /* almost, but not quite */ + + while (!feof(f)) { + fgets(buf, sizeof(buf), f); + if ((p = strstr(buf, "geometry "))) { + p += 9; + /* FIXME: catastrophic on arches with 64bit ints */ + sscanf(p, "%d %d %d %d %d", + &(base->xres), &(base->yres), + &(base->xres_virtual), &(base->yres_virtual), + &(base->bits_per_pixel)); + } else if ((p = strstr(buf, "timings "))) { + p += 8; + sscanf(p, "%d %d %d %d %d %d %d", + &(base->pixclock), + &(base->left_margin), &(base->right_margin), + &(base->upper_margin), &(base->lower_margin), + &(base->hsync_len), &(base->vsync_len)); + } else if ((p = strstr(buf, "laced "))) { + //p += 6; + if (strstr(buf, "false")) { + base->vmode &= ~FB_VMODE_INTERLACED; + } else { + base->vmode |= FB_VMODE_INTERLACED; + } + } else if ((p = strstr(buf, "double "))) { + //p += 7; + if (strstr(buf, "false")) { + base->vmode &= ~FB_VMODE_DOUBLE; + } else { + base->vmode |= FB_VMODE_DOUBLE; + } + } else if ((p = strstr(buf, "vsync "))) { + //p += 6; + if (strstr(buf, "low")) { + base->sync &= ~FB_SYNC_VERT_HIGH_ACT; + } else { + base->sync |= FB_SYNC_VERT_HIGH_ACT; + } + } else if ((p = strstr(buf, "hsync "))) { + //p += 6; + if (strstr(buf, "low")) { + base->sync &= ~FB_SYNC_HOR_HIGH_ACT; + } else { + base->sync |= FB_SYNC_HOR_HIGH_ACT; + } + } else if ((p = strstr(buf, "csync "))) { + //p += 6; + if (strstr(buf, "low")) { + base->sync &= ~FB_SYNC_COMP_HIGH_ACT; + } else { + base->sync |= FB_SYNC_COMP_HIGH_ACT; + } + } else if ((p = strstr(buf, "extsync "))) { + //p += 8; + if (strstr(buf, "false")) { + base->sync &= ~FB_SYNC_EXT; + } else { + base->sync |= FB_SYNC_EXT; + } + } + + if (strstr(buf, "endmode")) + return 1; + } + } +#else + bb_error_msg("mode reading not compiled in"); +#endif + return 0; +} + +static inline void setmode(struct fb_var_screeninfo *base, + struct fb_var_screeninfo *set) +{ + if ((int) set->xres > 0) + base->xres = set->xres; + if ((int) set->yres > 0) + base->yres = set->yres; + if ((int) set->xres_virtual > 0) + base->xres_virtual = set->xres_virtual; + if ((int) set->yres_virtual > 0) + base->yres_virtual = set->yres_virtual; + if ((int) set->bits_per_pixel > 0) + base->bits_per_pixel = set->bits_per_pixel; +} + +static inline void showmode(struct fb_var_screeninfo *v) +{ + double drate = 0, hrate = 0, vrate = 0; + + if (v->pixclock) { + drate = 1e12 / v->pixclock; + hrate = drate / (v->left_margin + v->xres + v->right_margin + v->hsync_len); + vrate = hrate / (v->upper_margin + v->yres + v->lower_margin + v->vsync_len); + } + printf("\nmode \"%ux%u-%u\"\n" +#ifdef CONFIG_FEATURE_FBSET_FANCY + "\t# D: %.3f MHz, H: %.3f kHz, V: %.3f Hz\n" +#endif + "\tgeometry %u %u %u %u %u\n" + "\ttimings %u %u %u %u %u %u %u\n" + "\taccel %s\n" + "\trgba %u/%u,%u/%u,%u/%u,%u/%u\n" + "endmode\n\n", + v->xres, v->yres, (int) (vrate + 0.5), +#ifdef CONFIG_FEATURE_FBSET_FANCY + drate / 1e6, hrate / 1e3, vrate, +#endif + v->xres, v->yres, v->xres_virtual, v->yres_virtual, v->bits_per_pixel, + v->pixclock, v->left_margin, v->right_margin, v->upper_margin, v->lower_margin, + v->hsync_len, v->vsync_len, + (v->accel_flags > 0 ? "true" : "false"), + v->red.length, v->red.offset, v->green.length, v->green.offset, + v->blue.length, v->blue.offset, v->transp.length, v->transp.offset); +} + +#ifdef STANDALONE +int main(int argc, char **argv) +#else +int fbset_main(int argc, char **argv) +#endif +{ + struct fb_var_screeninfo var, varset; + int fh, i; + char *fbdev = DEFAULTFBDEV; + char *modefile = DEFAULTFBMODE; + char *thisarg, *mode = NULL; + + memset(&varset, 0xFF, sizeof(varset)); + + /* parse cmd args.... why do they have to make things so difficult? */ + argv++; + argc--; + for (; argc > 0 && (thisarg = *argv); argc--, argv++) { + for (i = 0; g_cmdoptions[i].name[0]; i++) { + if (strcmp(thisarg, g_cmdoptions[i].name)) + continue; + if (argc-1 < g_cmdoptions[i].param_count) + bb_show_usage(); + + switch (g_cmdoptions[i].code) { + case CMD_FB: + fbdev = argv[1]; + break; + case CMD_DB: + modefile = argv[1]; + break; + case CMD_GEOMETRY: + varset.xres = xatou32(argv[1]); + varset.yres = xatou32(argv[2]); + varset.xres_virtual = xatou32(argv[3]); + varset.yres_virtual = xatou32(argv[4]); + varset.bits_per_pixel = xatou32(argv[5]); + break; + case CMD_TIMING: + varset.pixclock = xatou32(argv[1]); + varset.left_margin = xatou32(argv[2]); + varset.right_margin = xatou32(argv[3]); + varset.upper_margin = xatou32(argv[4]); + varset.lower_margin = xatou32(argv[5]); + varset.hsync_len = xatou32(argv[6]); + varset.vsync_len = xatou32(argv[7]); + break; + case CMD_ALL: + g_options |= OPT_ALL; + break; + case CMD_CHANGE: + g_options |= OPT_CHANGE; + break; +#ifdef CONFIG_FEATURE_FBSET_FANCY + case CMD_XRES: + varset.xres = xatou32(argv[1]); + break; + case CMD_YRES: + varset.yres = xatou32(argv[1]); + break; + case CMD_DEPTH: + varset.bits_per_pixel = xatou32(argv[1]); + break; +#endif + } + argc -= g_cmdoptions[i].param_count; + argv += g_cmdoptions[i].param_count; + break; + } + if (!g_cmdoptions[i].name[0]) { + if (argc != 1) + bb_show_usage(); + mode = *argv; + g_options |= OPT_READMODE; + } + } + + fh = xopen(fbdev, O_RDONLY); + if (ioctl(fh, FBIOGET_VSCREENINFO, &var)) + bb_perror_msg_and_die("ioctl(%sT_VSCREENINFO)", "GE"); + if (g_options & OPT_READMODE) { + if (!readmode(&var, modefile, mode)) { + bb_error_msg_and_die("unknown video mode '%s'", mode); + } + } + + setmode(&var, &varset); + if (g_options & OPT_CHANGE) { + if (g_options & OPT_ALL) + var.activate = FB_ACTIVATE_ALL; + if (ioctl(fh, FBIOPUT_VSCREENINFO, &var)) + bb_perror_msg_and_die("ioctl(%sT_VSCREENINFO)", "PU"); + } + showmode(&var); + /* Don't close the file, as exiting will take care of that */ + /* close(fh); */ + + return EXIT_SUCCESS; +} diff --git a/util-linux/fdformat.c b/util-linux/fdformat.c new file mode 100644 index 000000000..0242d8d3a --- /dev/null +++ b/util-linux/fdformat.c @@ -0,0 +1,143 @@ +/* vi: set sw=4 ts=4: */ +/* fdformat.c - Low-level formats a floppy disk - Werner Almesberger */ + +/* 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * 1999-03-20 Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * - more i18n/nls translatable strings marked + * + * 5 July 2003 -- modified for Busybox by Erik Andersen + */ + +#include "busybox.h" + + +/* Stuff extracted from linux/fd.h */ +struct floppy_struct { + unsigned int size, /* nr of sectors total */ + sect, /* sectors per track */ + head, /* nr of heads */ + track, /* nr of tracks */ + stretch; /* !=0 means double track steps */ +#define FD_STRETCH 1 +#define FD_SWAPSIDES 2 + + unsigned char gap, /* gap1 size */ + + rate, /* data rate. |= 0x40 for perpendicular */ +#define FD_2M 0x4 +#define FD_SIZECODEMASK 0x38 +#define FD_SIZECODE(floppy) (((((floppy)->rate&FD_SIZECODEMASK)>> 3)+ 2) %8) +#define FD_SECTSIZE(floppy) ( (floppy)->rate & FD_2M ? \ + 512 : 128 << FD_SIZECODE(floppy) ) +#define FD_PERP 0x40 + + spec1, /* stepping rate, head unload time */ + fmt_gap; /* gap2 size */ + const char * name; /* used only for predefined formats */ +}; +struct format_descr { + unsigned int device,head,track; +}; +#define FDFMTBEG _IO(2,0x47) +#define FDFMTTRK _IOW(2,0x48, struct format_descr) +#define FDFMTEND _IO(2,0x49) +#define FDGETPRM _IOR(2, 0x04, struct floppy_struct) +#define FD_FILL_BYTE 0xF6 /* format fill byte. */ + +static void xioctl(int fd, int request, void *argp, const char *string) +{ + if (ioctl(fd, request, argp) < 0) { + bb_perror_msg_and_die(string); + } +} + +int fdformat_main(int argc,char **argv) +{ + int fd, n, cyl, read_bytes, verify; + unsigned char *data; + struct stat st; + struct floppy_struct param; + struct format_descr descr; + + if (argc < 2) { + bb_show_usage(); + } + verify = !getopt32(argc, argv, "n"); + argv += optind; + + xstat(*argv, &st); + if (!S_ISBLK(st.st_mode)) { + bb_error_msg_and_die("%s: not a block device", *argv); + /* do not test major - perhaps this was an USB floppy */ + } + + /* O_RDWR for formatting and verifying */ + fd = xopen(*argv, O_RDWR); + + /* original message was: "Could not determine current format type" */ + xioctl(fd, FDGETPRM, ¶m, "FDGETPRM"); + + printf("%s-sided, %d tracks, %d sec/track. Total capacity %d kB\n", + (param.head == 2) ? "Double" : "Single", + param.track, param.sect, param.size >> 1); + + /* FORMAT */ + printf("Formatting... "); + xioctl(fd, FDFMTBEG, NULL, "FDFMTBEG"); + + /* n == track */ + for (n = 0; n < param.track; n++) { + descr.head = 0; + descr.track = n; + xioctl(fd, FDFMTTRK, &descr, "FDFMTTRK"); + printf("%3d\b\b\b", n); + if (param.head == 2) { + descr.head = 1; + xioctl(fd, FDFMTTRK, &descr, "FDFMTTRK"); + } + } + + xioctl(fd, FDFMTEND, NULL, "FDFMTEND"); + printf("done\n"); + + /* VERIFY */ + if (verify) { + /* n == cyl_size */ + n = param.sect*param.head*512; + + data = xmalloc(n); + printf("Verifying... "); + for (cyl = 0; cyl < param.track; cyl++) { + printf("%3d\b\b\b", cyl); + read_bytes = safe_read(fd, data, n); + if (read_bytes != n) { + if (read_bytes < 0) { + bb_perror_msg(bb_msg_read_error); + } + bb_error_msg_and_die("problem reading cylinder %d, " + "expected %d, read %d", cyl, n, read_bytes); + // FIXME: maybe better seek & continue?? + } + /* Check backwards so we don't need a counter */ + while (--read_bytes >= 0) { + if (data[read_bytes] != FD_FILL_BYTE) { + printf("bad data in cyl %d\nContinuing... ",cyl); + } + } + } + /* There is no point in freeing blocks at the end of a program, because + all of the program's space is given back to the system when the process + terminates.*/ + + if (ENABLE_FEATURE_CLEAN_UP) free(data); + + printf("done\n"); + } + + if (ENABLE_FEATURE_CLEAN_UP) close(fd); + + /* Don't bother closing. Exit does + * that, so we can save a few bytes */ + return EXIT_SUCCESS; +} diff --git a/util-linux/fdisk.c b/util-linux/fdisk.c new file mode 100644 index 000000000..2f87f1c60 --- /dev/null +++ b/util-linux/fdisk.c @@ -0,0 +1,3043 @@ +/* vi: set sw=4 ts=4: */ +/* fdisk.c -- Partition table manipulator for Linux. + * + * Copyright (C) 1992 A. V. Le Blanc (LeBlanc@mcc.ac.uk) + * Copyright (C) 2001,2002 Vladimir Oleynik <dzo@simtreas.ru> (initial bb port) + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include <assert.h> /* assert */ +#include "busybox.h" +#define _(x) x + +/* Looks like someone forgot to add this to config system */ +#ifndef ENABLE_FEATURE_FDISK_BLKSIZE +# define ENABLE_FEATURE_FDISK_BLKSIZE 0 +# define USE_FEATURE_FDISK_BLKSIZE(a) +#endif + +#define DEFAULT_SECTOR_SIZE 512 +#define MAX_SECTOR_SIZE 2048 +#define SECTOR_SIZE 512 /* still used in osf/sgi/sun code */ +#define MAXIMUM_PARTS 60 + +#define ACTIVE_FLAG 0x80 + +#define EXTENDED 0x05 +#define WIN98_EXTENDED 0x0f +#define LINUX_PARTITION 0x81 +#define LINUX_SWAP 0x82 +#define LINUX_NATIVE 0x83 +#define LINUX_EXTENDED 0x85 +#define LINUX_LVM 0x8e +#define LINUX_RAID 0xfd + +#define IS_EXTENDED(i) \ + ((i) == EXTENDED || (i) == WIN98_EXTENDED || (i) == LINUX_EXTENDED) + +#define SIZE(a) (sizeof(a)/sizeof((a)[0])) + +#define cround(n) (display_in_cyl_units ? ((n)/units_per_sector)+1 : (n)) +#define scround(x) (((x)+units_per_sector-1)/units_per_sector) + +struct hd_geometry { + unsigned char heads; + unsigned char sectors; + unsigned short cylinders; + unsigned long start; +}; + +#define HDIO_GETGEO 0x0301 /* get device geometry */ + +struct systypes { + const char *name; +}; + +static unsigned sector_size = DEFAULT_SECTOR_SIZE; +static unsigned user_set_sector_size; +static unsigned sector_offset = 1; + +/* + * Raw disk label. For DOS-type partition tables the MBR, + * with descriptions of the primary partitions. + */ +#if (MAX_SECTOR_SIZE) > (BUFSIZ+1) +static char MBRbuffer[MAX_SECTOR_SIZE]; +#else +# define MBRbuffer bb_common_bufsiz1 +#endif + +#if ENABLE_FEATURE_OSF_LABEL +static int possibly_osf_label; +#endif + +static unsigned heads, sectors, cylinders; +static void update_units(void); + + +/* + * return partition name - uses static storage unless buf is supplied + */ +static const char * +partname(const char *dev, int pno, int lth) +{ + static char buffer[80]; + const char *p; + int w, wp; + int bufsiz; + char *bufp; + + bufp = buffer; + bufsiz = sizeof(buffer); + + w = strlen(dev); + p = ""; + + if (isdigit(dev[w-1])) + p = "p"; + + /* devfs kludge - note: fdisk partition names are not supposed + to equal kernel names, so there is no reason to do this */ + if (strcmp(dev + w - 4, "disc") == 0) { + w -= 4; + p = "part"; + } + + wp = strlen(p); + + if (lth) { + snprintf(bufp, bufsiz, "%*.*s%s%-2u", + lth-wp-2, w, dev, p, pno); + } else { + snprintf(bufp, bufsiz, "%.*s%s%-2u", w, dev, p, pno); + } + return bufp; +} + +struct partition { + unsigned char boot_ind; /* 0x80 - active */ + unsigned char head; /* starting head */ + unsigned char sector; /* starting sector */ + unsigned char cyl; /* starting cylinder */ + unsigned char sys_ind; /* What partition type */ + unsigned char end_head; /* end head */ + unsigned char end_sector; /* end sector */ + unsigned char end_cyl; /* end cylinder */ + unsigned char start4[4]; /* starting sector counting from 0 */ + unsigned char size4[4]; /* nr of sectors in partition */ +} ATTRIBUTE_PACKED; + +enum failure { + ioctl_error, unable_to_open, unable_to_read, unable_to_seek, + unable_to_write +}; + +enum label_type { + label_dos, label_sun, label_sgi, label_aix, label_osf +}; +#define LABEL_IS_DOS (label_dos == current_label_type) + +#if ENABLE_FEATURE_SUN_LABEL +#define LABEL_IS_SUN (label_sun == current_label_type) +#define STATIC_SUN static +#else +#define LABEL_IS_SUN 0 +#define STATIC_SUN extern +#endif + +#if ENABLE_FEATURE_SGI_LABEL +#define LABEL_IS_SGI (label_sgi == current_label_type) +#define STATIC_SGI static +#else +#define LABEL_IS_SGI 0 +#define STATIC_SGI extern +#endif + +#if ENABLE_FEATURE_AIX_LABEL +#define LABEL_IS_AIX (label_aix == current_label_type) +#define STATIC_AIX static +#else +#define LABEL_IS_AIX 0 +#define STATIC_AIX extern +#endif + +#if ENABLE_FEATURE_OSF_LABEL +#define LABEL_IS_OSF (label_osf == current_label_type) +#define STATIC_OSF static +#else +#define LABEL_IS_OSF 0 +#define STATIC_OSF extern +#endif + +enum action { fdisk, require, try_only, create_empty_dos, create_empty_sun }; + +static enum label_type current_label_type; + +static const char *disk_device; +static int fd; /* the disk */ +static int partitions = 4; /* maximum partition + 1 */ +static int display_in_cyl_units = 1; +static unsigned units_per_sector = 1; +#if ENABLE_FEATURE_FDISK_WRITABLE +static void change_units(void); +static void reread_partition_table(int leave); +static void delete_partition(int i); +static int get_partition(int warn, int max); +static void list_types(const struct systypes *sys); +static unsigned read_int(unsigned low, unsigned dflt, unsigned high, unsigned base, char *mesg); +#endif +static const char *partition_type(unsigned char type); +static void fdisk_fatal(enum failure why) ATTRIBUTE_NORETURN; +static void get_geometry(void); +static int get_boot(enum action what); + +#define PLURAL 0 +#define SINGULAR 1 + +#define hex_val(c) ({ \ + char _c = (c); \ + isdigit(_c) ? _c - '0' : \ + tolower(_c) + 10 - 'a'; \ + }) + + +#define LINE_LENGTH 800 +#define pt_offset(b, n) ((struct partition *)((b) + 0x1be + \ + (n) * sizeof(struct partition))) +#define sector(s) ((s) & 0x3f) +#define cylinder(s, c) ((c) | (((s) & 0xc0) << 2)) + +#define hsc2sector(h,s,c) (sector(s) - 1 + sectors * \ + ((h) + heads * cylinder(s,c))) +#define set_hsc(h,s,c,sector) { \ + s = sector % sectors + 1; \ + sector /= sectors; \ + h = sector % heads; \ + sector /= heads; \ + c = sector & 0xff; \ + s |= (sector >> 2) & 0xc0; \ + } + + +static int32_t get_start_sect(const struct partition *p); +static int32_t get_nr_sects(const struct partition *p); + +/* + * per partition table entry data + * + * The four primary partitions have the same sectorbuffer (MBRbuffer) + * and have NULL ext_pointer. + * Each logical partition table entry has two pointers, one for the + * partition and one link to the next one. + */ +static struct pte { + struct partition *part_table; /* points into sectorbuffer */ + struct partition *ext_pointer; /* points into sectorbuffer */ +#if ENABLE_FEATURE_FDISK_WRITABLE + char changed; /* boolean */ +#endif + off_t offset; /* disk sector number */ + char *sectorbuffer; /* disk sector contents */ +} ptes[MAXIMUM_PARTS]; + + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +set_all_unchanged(void) +{ + int i; + + for (i = 0; i < MAXIMUM_PARTS; i++) + ptes[i].changed = 0; +} + +extern inline void +set_changed(int i) +{ + ptes[i].changed = 1; +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +extern inline struct partition * +get_part_table(int i) +{ + return ptes[i].part_table; +} + +static const char * +str_units(int n) +{ /* n==1: use singular */ + if (n == 1) + return display_in_cyl_units ? _("cylinder") : _("sector"); + else + return display_in_cyl_units ? _("cylinders") : _("sectors"); +} + +static int +valid_part_table_flag(const char *mbuffer) +{ + return (mbuffer[510] == 0x55 && (uint8_t)mbuffer[511] == 0xaa); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +extern inline void +write_part_table_flag(char *b) +{ + b[510] = 0x55; + b[511] = 0xaa; +} + +static char line_buffer[LINE_LENGTH]; +static char *line_ptr; + +/* read line; return 0 or first char */ +static int +read_line(void) +{ + fflush(stdout); /* requested by niles@scyld.com */ + line_ptr = line_buffer; + if (!fgets(line_buffer, LINE_LENGTH, stdin)) { + /* error or eof */ + bb_error_msg_and_die("\ngot EOF, exiting"); + } + while (*line_ptr && !isgraph(*line_ptr)) + line_ptr++; + return *line_ptr; +} + +static char +read_nonempty(const char *mesg) +{ + do { + fputs(mesg, stdout); + } while (!read_line()); + return *line_ptr; +} + +static char +read_maybe_empty(const char *mesg) +{ + fputs(mesg, stdout); + if (!read_line()) { + line_ptr = line_buffer; + *line_ptr = '\n'; + line_ptr[1] = 0; + } + return *line_ptr; +} + +static int +read_hex(const struct systypes *sys) +{ + unsigned long v; + while (1) { + read_nonempty(_("Hex code (type L to list codes): ")); + if (*line_ptr == 'l' || *line_ptr == 'L') { + list_types(sys); + continue; + } + v = bb_strtoul(line_ptr, NULL, 16); + if (errno || v > 0xff) continue; + return v; + } +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +#include "fdisk_aix.c" + +typedef struct { + unsigned char info[128]; /* Informative text string */ + unsigned char spare0[14]; + struct sun_info { + unsigned char spare1; + unsigned char id; + unsigned char spare2; + unsigned char flags; + } infos[8]; + unsigned char spare1[246]; /* Boot information etc. */ + unsigned short rspeed; /* Disk rotational speed */ + unsigned short pcylcount; /* Physical cylinder count */ + unsigned short sparecyl; /* extra sects per cylinder */ + unsigned char spare2[4]; /* More magic... */ + unsigned short ilfact; /* Interleave factor */ + unsigned short ncyl; /* Data cylinder count */ + unsigned short nacyl; /* Alt. cylinder count */ + unsigned short ntrks; /* Tracks per cylinder */ + unsigned short nsect; /* Sectors per track */ + unsigned char spare3[4]; /* Even more magic... */ + struct sun_partinfo { + uint32_t start_cylinder; + uint32_t num_sectors; + } partitions[8]; + unsigned short magic; /* Magic number */ + unsigned short csum; /* Label xor'd checksum */ +} sun_partition; +#define sunlabel ((sun_partition *)MBRbuffer) +#define SUNOS_SWAP 3 +#define SUN_WHOLE_DISK 5 +STATIC_OSF void bsd_select(void); +STATIC_OSF void xbsd_print_disklabel(int); +#include "fdisk_osf.c" + +#define SGI_VOLHDR 0x00 +/* 1 and 2 were used for drive types no longer supported by SGI */ +#define SGI_SWAP 0x03 +/* 4 and 5 were for filesystem types SGI haven't ever supported on MIPS CPUs */ +#define SGI_VOLUME 0x06 +#define SGI_EFS 0x07 +#define SGI_LVOL 0x08 +#define SGI_RLVOL 0x09 +#define SGI_XFS 0x0a +#define SGI_XFSLOG 0x0b +#define SGI_XLV 0x0c +#define SGI_XVM 0x0d +#define SGI_ENTIRE_DISK SGI_VOLUME +#if defined(CONFIG_FEATURE_SGI_LABEL) || defined(CONFIG_FEATURE_SUN_LABEL) +static uint16_t +__swap16(uint16_t x) +{ + return (x << 8) | (x >> 8); +} + +static uint32_t +__swap32(uint32_t x) +{ + return (x << 24) | + ((x & 0xFF00) << 8) | + ((x & 0xFF0000) >> 8) | + (x >> 24); +} +#endif + +STATIC_SGI const struct systypes sgi_sys_types[]; +STATIC_SGI unsigned sgi_get_num_sectors(int i); +STATIC_SGI int sgi_get_sysid(int i); +STATIC_SGI void sgi_delete_partition(int i); +STATIC_SGI void sgi_change_sysid(int i, int sys); +STATIC_SGI void sgi_list_table(int xtra); +STATIC_SGI void sgi_set_xcyl(void); +STATIC_SGI int verify_sgi(int verbose); +STATIC_SGI void sgi_add_partition(int n, int sys); +STATIC_SGI void sgi_set_swappartition(int i); +STATIC_SGI const char *sgi_get_bootfile(void); +STATIC_SGI void sgi_set_bootfile(const char* aFile); +STATIC_SGI void create_sgiinfo(void); +STATIC_SGI void sgi_write_table(void); +STATIC_SGI void sgi_set_bootpartition(int i); + +#include "fdisk_sgi.c" + +STATIC_SUN const struct systypes sun_sys_types[]; +STATIC_SUN void sun_delete_partition(int i); +STATIC_SUN void sun_change_sysid(int i, int sys); +STATIC_SUN void sun_list_table(int xtra); +STATIC_SUN void sun_set_xcyl(void); +STATIC_SUN void add_sun_partition(int n, int sys); +STATIC_SUN void sun_set_alt_cyl(void); +STATIC_SUN void sun_set_ncyl(int cyl); +STATIC_SUN void sun_set_xcyl(void); +STATIC_SUN void sun_set_ilfact(void); +STATIC_SUN void sun_set_rspeed(void); +STATIC_SUN void sun_set_pcylcount(void); +STATIC_SUN void toggle_sunflags(int i, unsigned char mask); +STATIC_SUN void verify_sun(void); +STATIC_SUN void sun_write_table(void); +#include "fdisk_sun.c" + +/* DOS partition types */ + +static const struct systypes i386_sys_types[] = { + { "\x00" "Empty" }, + { "\x01" "FAT12" }, + { "\x04" "FAT16 <32M" }, + { "\x05" "Extended" }, /* DOS 3.3+ extended partition */ + { "\x06" "FAT16" }, /* DOS 16-bit >=32M */ + { "\x07" "HPFS/NTFS" }, /* OS/2 IFS, eg, HPFS or NTFS or QNX */ + { "\x0a" "OS/2 Boot Manager" },/* OS/2 Boot Manager */ + { "\x0b" "Win95 FAT32" }, + { "\x0c" "Win95 FAT32 (LBA)" },/* LBA really is 'Extended Int 13h' */ + { "\x0e" "Win95 FAT16 (LBA)" }, + { "\x0f" "Win95 Ext'd (LBA)" }, + { "\x11" "Hidden FAT12" }, + { "\x12" "Compaq diagnostics" }, + { "\x14" "Hidden FAT16 <32M" }, + { "\x16" "Hidden FAT16" }, + { "\x17" "Hidden HPFS/NTFS" }, + { "\x1b" "Hidden Win95 FAT32" }, + { "\x1c" "Hidden Win95 FAT32 (LBA)" }, + { "\x1e" "Hidden Win95 FAT16 (LBA)" }, + { "\x3c" "PartitionMagic recovery" }, + { "\x41" "PPC PReP Boot" }, + { "\x42" "SFS" }, + { "\x63" "GNU HURD or SysV" }, /* GNU HURD or Mach or Sys V/386 (such as ISC UNIX) */ + { "\x80" "Old Minix" }, /* Minix 1.4a and earlier */ + { "\x81" "Minix / old Linux" },/* Minix 1.4b and later */ + { "\x82" "Linux swap" }, /* also Solaris */ + { "\x83" "Linux" }, + { "\x84" "OS/2 hidden C: drive" }, + { "\x85" "Linux extended" }, + { "\x86" "NTFS volume set" }, + { "\x87" "NTFS volume set" }, + { "\x8e" "Linux LVM" }, + { "\x9f" "BSD/OS" }, /* BSDI */ + { "\xa0" "IBM Thinkpad hibernation" }, + { "\xa5" "FreeBSD" }, /* various BSD flavours */ + { "\xa6" "OpenBSD" }, + { "\xa8" "Darwin UFS" }, + { "\xa9" "NetBSD" }, + { "\xab" "Darwin boot" }, + { "\xb7" "BSDI fs" }, + { "\xb8" "BSDI swap" }, + { "\xbe" "Solaris boot" }, + { "\xeb" "BeOS fs" }, + { "\xee" "EFI GPT" }, /* Intel EFI GUID Partition Table */ + { "\xef" "EFI (FAT-12/16/32)" },/* Intel EFI System Partition */ + { "\xf0" "Linux/PA-RISC boot" },/* Linux/PA-RISC boot loader */ + { "\xf2" "DOS secondary" }, /* DOS 3.3+ secondary */ + { "\xfd" "Linux raid autodetect" },/* New (2.2.x) raid partition with + autodetect using persistent + superblock */ +#if 0 /* ENABLE_WEIRD_PARTITION_TYPES */ + { "\x02" "XENIX root" }, + { "\x03" "XENIX usr" }, + { "\x08" "AIX" }, /* AIX boot (AIX -- PS/2 port) or SplitDrive */ + { "\x09" "AIX bootable" }, /* AIX data or Coherent */ + { "\x10" "OPUS" }, + { "\x18" "AST SmartSleep" }, + { "\x24" "NEC DOS" }, + { "\x39" "Plan 9" }, + { "\x40" "Venix 80286" }, + { "\x4d" "QNX4.x" }, + { "\x4e" "QNX4.x 2nd part" }, + { "\x4f" "QNX4.x 3rd part" }, + { "\x50" "OnTrack DM" }, + { "\x51" "OnTrack DM6 Aux1" }, /* (or Novell) */ + { "\x52" "CP/M" }, /* CP/M or Microport SysV/AT */ + { "\x53" "OnTrack DM6 Aux3" }, + { "\x54" "OnTrackDM6" }, + { "\x55" "EZ-Drive" }, + { "\x56" "Golden Bow" }, + { "\x5c" "Priam Edisk" }, + { "\x61" "SpeedStor" }, + { "\x64" "Novell Netware 286" }, + { "\x65" "Novell Netware 386" }, + { "\x70" "DiskSecure Multi-Boot" }, + { "\x75" "PC/IX" }, + { "\x93" "Amoeba" }, + { "\x94" "Amoeba BBT" }, /* (bad block table) */ + { "\xa7" "NeXTSTEP" }, + { "\xbb" "Boot Wizard hidden" }, + { "\xc1" "DRDOS/sec (FAT-12)" }, + { "\xc4" "DRDOS/sec (FAT-16 < 32M)" }, + { "\xc6" "DRDOS/sec (FAT-16)" }, + { "\xc7" "Syrinx" }, + { "\xda" "Non-FS data" }, + { "\xdb" "CP/M / CTOS / ..." },/* CP/M or Concurrent CP/M or + Concurrent DOS or CTOS */ + { "\xde" "Dell Utility" }, /* Dell PowerEdge Server utilities */ + { "\xdf" "BootIt" }, /* BootIt EMBRM */ + { "\xe1" "DOS access" }, /* DOS access or SpeedStor 12-bit FAT + extended partition */ + { "\xe3" "DOS R/O" }, /* DOS R/O or SpeedStor */ + { "\xe4" "SpeedStor" }, /* SpeedStor 16-bit FAT extended + partition < 1024 cyl. */ + { "\xf1" "SpeedStor" }, + { "\xf4" "SpeedStor" }, /* SpeedStor large partition */ + { "\xfe" "LANstep" }, /* SpeedStor >1024 cyl. or LANstep */ + { "\xff" "BBT" }, /* Xenix Bad Block Table */ +#endif + { 0 } +}; + + +#if ENABLE_FEATURE_FDISK_WRITABLE +/* start_sect and nr_sects are stored little endian on all machines */ +/* moreover, they are not aligned correctly */ +static void +store4_little_endian(unsigned char *cp, unsigned val) +{ + cp[0] = val; + cp[1] = val >> 8; + cp[2] = val >> 16; + cp[3] = val >> 24; +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +static unsigned +read4_little_endian(const unsigned char *cp) +{ + return cp[0] + (cp[1] << 8) + (cp[2] << 16) + (cp[3] << 24); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +set_start_sect(struct partition *p, unsigned start_sect) +{ + store4_little_endian(p->start4, start_sect); +} +#endif + +static int32_t +get_start_sect(const struct partition *p) +{ + return read4_little_endian(p->start4); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +set_nr_sects(struct partition *p, int32_t nr_sects) +{ + store4_little_endian(p->size4, nr_sects); +} +#endif + +static int32_t +get_nr_sects(const struct partition *p) +{ + return read4_little_endian(p->size4); +} + +/* normally O_RDWR, -l option gives O_RDONLY */ +static int type_open = O_RDWR; + + +static int ext_index; /* the prime extended partition */ +static int listing; /* no aborts for fdisk -l */ +static int dos_compatible_flag = ~0; +#if ENABLE_FEATURE_FDISK_WRITABLE +static int dos_changed; +static int nowarn; /* no warnings for fdisk -l/-s */ +#endif + + + +static unsigned user_cylinders, user_heads, user_sectors; +static unsigned pt_heads, pt_sectors; +static unsigned kern_heads, kern_sectors; + +static off_t extended_offset; /* offset of link pointers */ + +static unsigned long long total_number_of_sectors; + + +static jmp_buf listingbuf; + +static void fdisk_fatal(enum failure why) +{ + const char *message; + + if (listing) { + close(fd); + longjmp(listingbuf, 1); + } + + switch (why) { + case unable_to_open: + message = "\nUnable to open %s"; + break; + case unable_to_read: + message = "\nUnable to read %s"; + break; + case unable_to_seek: + message = "\nUnable to seek on %s"; + break; + case unable_to_write: + message = "\nUnable to write %s"; + break; + case ioctl_error: + message = "\nBLKGETSIZE ioctl failed on %s"; + break; + default: + message = "\nFatal error"; + } + + bb_error_msg_and_die(message, disk_device); +} + +static void +seek_sector(off_t secno) +{ + off_t offset = secno * sector_size; + if (lseek(fd, offset, SEEK_SET) == (off_t) -1) + fdisk_fatal(unable_to_seek); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +write_sector(off_t secno, char *buf) +{ + seek_sector(secno); + if (write(fd, buf, sector_size) != sector_size) + fdisk_fatal(unable_to_write); +} +#endif + +/* Allocate a buffer and read a partition table sector */ +static void +read_pte(struct pte *pe, off_t offset) +{ + pe->offset = offset; + pe->sectorbuffer = (char *) xmalloc(sector_size); + seek_sector(offset); + if (read(fd, pe->sectorbuffer, sector_size) != sector_size) + fdisk_fatal(unable_to_read); +#if ENABLE_FEATURE_FDISK_WRITABLE + pe->changed = 0; +#endif + pe->part_table = pe->ext_pointer = NULL; +} + +static unsigned +get_partition_start(const struct pte *pe) +{ + return pe->offset + get_start_sect(pe->part_table); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +/* + * Avoid warning about DOS partitions when no DOS partition was changed. + * Here a heuristic "is probably dos partition". + * We might also do the opposite and warn in all cases except + * for "is probably nondos partition". + */ +static int +is_dos_partition(int t) +{ + return (t == 1 || t == 4 || t == 6 || + t == 0x0b || t == 0x0c || t == 0x0e || + t == 0x11 || t == 0x12 || t == 0x14 || t == 0x16 || + t == 0x1b || t == 0x1c || t == 0x1e || t == 0x24 || + t == 0xc1 || t == 0xc4 || t == 0xc6); +} + +static void +menu(void) +{ + if (LABEL_IS_SUN) { + puts(_("Command action")); + puts(_("\ta\ttoggle a read only flag")); /* sun */ + puts(_("\tb\tedit bsd disklabel")); + puts(_("\tc\ttoggle the mountable flag")); /* sun */ + puts(_("\td\tdelete a partition")); + puts(_("\tl\tlist known partition types")); + puts(_("\tm\tprint this menu")); + puts(_("\tn\tadd a new partition")); + puts(_("\to\tcreate a new empty DOS partition table")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\ts\tcreate a new empty Sun disklabel")); /* sun */ + puts(_("\tt\tchange a partition's system id")); + puts(_("\tu\tchange display/entry units")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); +#if ENABLE_FEATURE_FDISK_ADVANCED + puts(_("\tx\textra functionality (experts only)")); +#endif + } else + if (LABEL_IS_SGI) { + puts(_("Command action")); + puts(_("\ta\tselect bootable partition")); /* sgi flavour */ + puts(_("\tb\tedit bootfile entry")); /* sgi */ + puts(_("\tc\tselect sgi swap partition")); /* sgi flavour */ + puts(_("\td\tdelete a partition")); + puts(_("\tl\tlist known partition types")); + puts(_("\tm\tprint this menu")); + puts(_("\tn\tadd a new partition")); + puts(_("\to\tcreate a new empty DOS partition table")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\ts\tcreate a new empty Sun disklabel")); /* sun */ + puts(_("\tt\tchange a partition's system id")); + puts(_("\tu\tchange display/entry units")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); + } else + if (LABEL_IS_AIX) { + puts(_("Command action")); + puts(_("\tm\tprint this menu")); + puts(_("\to\tcreate a new empty DOS partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\ts\tcreate a new empty Sun disklabel")); /* sun */ + } else + { + puts(_("Command action")); + puts(_("\ta\ttoggle a bootable flag")); + puts(_("\tb\tedit bsd disklabel")); + puts(_("\tc\ttoggle the dos compatibility flag")); + puts(_("\td\tdelete a partition")); + puts(_("\tl\tlist known partition types")); + puts(_("\tm\tprint this menu")); + puts(_("\tn\tadd a new partition")); + puts(_("\to\tcreate a new empty DOS partition table")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\ts\tcreate a new empty Sun disklabel")); /* sun */ + puts(_("\tt\tchange a partition's system id")); + puts(_("\tu\tchange display/entry units")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); +#if ENABLE_FEATURE_FDISK_ADVANCED + puts(_("\tx\textra functionality (experts only)")); +#endif + } +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + + +#if ENABLE_FEATURE_FDISK_ADVANCED +static void +xmenu(void) +{ + if (LABEL_IS_SUN) { + puts(_("Command action")); + puts(_("\ta\tchange number of alternate cylinders")); /*sun*/ + puts(_("\tc\tchange number of cylinders")); + puts(_("\td\tprint the raw data in the partition table")); + puts(_("\te\tchange number of extra sectors per cylinder"));/*sun*/ + puts(_("\th\tchange number of heads")); + puts(_("\ti\tchange interleave factor")); /*sun*/ + puts(_("\to\tchange rotation speed (rpm)")); /*sun*/ + puts(_("\tm\tprint this menu")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\tr\treturn to main menu")); + puts(_("\ts\tchange number of sectors/track")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); + puts(_("\ty\tchange number of physical cylinders")); /*sun*/ + } else + if (LABEL_IS_SGI) { + puts(_("Command action")); + puts(_("\tb\tmove beginning of data in a partition")); /* !sun */ + puts(_("\tc\tchange number of cylinders")); + puts(_("\td\tprint the raw data in the partition table")); + puts(_("\te\tlist extended partitions")); /* !sun */ + puts(_("\tg\tcreate an IRIX (SGI) partition table"));/* sgi */ + puts(_("\th\tchange number of heads")); + puts(_("\tm\tprint this menu")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\tr\treturn to main menu")); + puts(_("\ts\tchange number of sectors/track")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); + } else + if (LABEL_IS_AIX) { + puts(_("Command action")); + puts(_("\tb\tmove beginning of data in a partition")); /* !sun */ + puts(_("\tc\tchange number of cylinders")); + puts(_("\td\tprint the raw data in the partition table")); + puts(_("\te\tlist extended partitions")); /* !sun */ + puts(_("\tg\tcreate an IRIX (SGI) partition table"));/* sgi */ + puts(_("\th\tchange number of heads")); + puts(_("\tm\tprint this menu")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\tr\treturn to main menu")); + puts(_("\ts\tchange number of sectors/track")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); + } else { + puts(_("Command action")); + puts(_("\tb\tmove beginning of data in a partition")); /* !sun */ + puts(_("\tc\tchange number of cylinders")); + puts(_("\td\tprint the raw data in the partition table")); + puts(_("\te\tlist extended partitions")); /* !sun */ + puts(_("\tf\tfix partition order")); /* !sun, !aix, !sgi */ +#if ENABLE_FEATURE_SGI_LABEL + puts(_("\tg\tcreate an IRIX (SGI) partition table"));/* sgi */ +#endif + puts(_("\th\tchange number of heads")); + puts(_("\tm\tprint this menu")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\tr\treturn to main menu")); + puts(_("\ts\tchange number of sectors/track")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); + } +} +#endif /* ADVANCED mode */ + +#if ENABLE_FEATURE_FDISK_WRITABLE +static const struct systypes * +get_sys_types(void) +{ + return ( + LABEL_IS_SUN ? sun_sys_types : + LABEL_IS_SGI ? sgi_sys_types : + i386_sys_types); +} +#else +#define get_sys_types() i386_sys_types +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +static const char *partition_type(unsigned char type) +{ + int i; + const struct systypes *types = get_sys_types(); + + for (i = 0; types[i].name; i++) + if ((unsigned char )types[i].name[0] == type) + return types[i].name + 1; + + return _("Unknown"); +} + + +#if ENABLE_FEATURE_FDISK_WRITABLE +static int +get_sysid(int i) +{ + return LABEL_IS_SUN ? sunlabel->infos[i].id : + (LABEL_IS_SGI ? sgi_get_sysid(i) : + ptes[i].part_table->sys_ind); +} + +void list_types(const struct systypes *sys) +{ + unsigned last[4], done = 0, next = 0, size; + int i; + + for (i = 0; sys[i].name; i++); + size = i; + + for (i = 3; i >= 0; i--) + last[3 - i] = done += (size + i - done) / (i + 1); + i = done = 0; + + do { + printf("%c%2x %-15.15s", i ? ' ' : '\n', + (unsigned char)sys[next].name[0], + partition_type((unsigned char)sys[next].name[0])); + next = last[i++] + done; + if (i > 3 || next >= last[i]) { + i = 0; + next = ++done; + } + } while (done < last[0]); + putchar('\n'); +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +static int +is_cleared_partition(const struct partition *p) +{ + return !(!p || p->boot_ind || p->head || p->sector || p->cyl || + p->sys_ind || p->end_head || p->end_sector || p->end_cyl || + get_start_sect(p) || get_nr_sects(p)); +} + +static void +clear_partition(struct partition *p) +{ + if (!p) + return; + memset(p, 0, sizeof(struct partition)); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +set_partition(int i, int doext, off_t start, off_t stop, int sysid) +{ + struct partition *p; + off_t offset; + + if (doext) { + p = ptes[i].ext_pointer; + offset = extended_offset; + } else { + p = ptes[i].part_table; + offset = ptes[i].offset; + } + p->boot_ind = 0; + p->sys_ind = sysid; + set_start_sect(p, start - offset); + set_nr_sects(p, stop - start + 1); + if (dos_compatible_flag && (start/(sectors*heads) > 1023)) + start = heads*sectors*1024 - 1; + set_hsc(p->head, p->sector, p->cyl, start); + if (dos_compatible_flag && (stop/(sectors*heads) > 1023)) + stop = heads*sectors*1024 - 1; + set_hsc(p->end_head, p->end_sector, p->end_cyl, stop); + ptes[i].changed = 1; +} +#endif + +static int +test_c(const char **m, const char *mesg) +{ + int val = 0; + if (!*m) + printf(_("You must set")); + else { + printf(" %s", *m); + val = 1; + } + *m = mesg; + return val; +} + +static int +warn_geometry(void) +{ + const char *m = NULL; + int prev = 0; + + if (!heads) + prev = test_c(&m, _("heads")); + if (!sectors) + prev = test_c(&m, _("sectors")); + if (!cylinders) + prev = test_c(&m, _("cylinders")); + if (!m) + return 0; + + printf("%s%s.\n" +#if ENABLE_FEATURE_FDISK_WRITABLE + "You can do this from the extra functions menu.\n" +#endif + , prev ? _(" and ") : " ", m); + + return 1; +} + +static void update_units(void) +{ + int cyl_units = heads * sectors; + + if (display_in_cyl_units && cyl_units) + units_per_sector = cyl_units; + else + units_per_sector = 1; /* in sectors */ +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +warn_cylinders(void) +{ + if (LABEL_IS_DOS && cylinders > 1024 && !nowarn) + printf(_("\n" +"The number of cylinders for this disk is set to %d.\n" +"There is nothing wrong with that, but this is larger than 1024,\n" +"and could in certain setups cause problems with:\n" +"1) software that runs at boot time (e.g., old versions of LILO)\n" +"2) booting and partitioning software from other OSs\n" +" (e.g., DOS FDISK, OS/2 FDISK)\n"), + cylinders); +} +#endif + +static void +read_extended(int ext) +{ + int i; + struct pte *pex; + struct partition *p, *q; + + ext_index = ext; + pex = &ptes[ext]; + pex->ext_pointer = pex->part_table; + + p = pex->part_table; + if (!get_start_sect(p)) { + printf(_("Bad offset in primary extended partition\n")); + return; + } + + while (IS_EXTENDED(p->sys_ind)) { + struct pte *pe = &ptes[partitions]; + + if (partitions >= MAXIMUM_PARTS) { + /* This is not a Linux restriction, but + this program uses arrays of size MAXIMUM_PARTS. + Do not try to 'improve' this test. */ + struct pte *pre = &ptes[partitions-1]; +#if ENABLE_FEATURE_FDISK_WRITABLE + printf(_("Warning: deleting partitions after %d\n"), + partitions); + pre->changed = 1; +#endif + clear_partition(pre->ext_pointer); + return; + } + + read_pte(pe, extended_offset + get_start_sect(p)); + + if (!extended_offset) + extended_offset = get_start_sect(p); + + q = p = pt_offset(pe->sectorbuffer, 0); + for (i = 0; i < 4; i++, p++) if (get_nr_sects(p)) { + if (IS_EXTENDED(p->sys_ind)) { + if (pe->ext_pointer) + printf(_("Warning: extra link " + "pointer in partition table" + " %d\n"), partitions + 1); + else + pe->ext_pointer = p; + } else if (p->sys_ind) { + if (pe->part_table) + printf(_("Warning: ignoring extra " + "data in partition table" + " %d\n"), partitions + 1); + else + pe->part_table = p; + } + } + + /* very strange code here... */ + if (!pe->part_table) { + if (q != pe->ext_pointer) + pe->part_table = q; + else + pe->part_table = q + 1; + } + if (!pe->ext_pointer) { + if (q != pe->part_table) + pe->ext_pointer = q; + else + pe->ext_pointer = q + 1; + } + + p = pe->ext_pointer; + partitions++; + } + +#if ENABLE_FEATURE_FDISK_WRITABLE + /* remove empty links */ + remove: + for (i = 4; i < partitions; i++) { + struct pte *pe = &ptes[i]; + + if (!get_nr_sects(pe->part_table) && + (partitions > 5 || ptes[4].part_table->sys_ind)) { + printf("omitting empty partition (%d)\n", i+1); + delete_partition(i); + goto remove; /* numbering changed */ + } + } +#endif +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +create_doslabel(void) +{ + int i; + + printf( + _("Building a new DOS disklabel. Changes will remain in memory only,\n" + "until you decide to write them. After that, of course, the previous\n" + "content won't be recoverable.\n\n")); + + current_label_type = label_dos; + +#if ENABLE_FEATURE_OSF_LABEL + possibly_osf_label = 0; +#endif + partitions = 4; + + for (i = 510-64; i < 510; i++) + MBRbuffer[i] = 0; + write_part_table_flag(MBRbuffer); + extended_offset = 0; + set_all_unchanged(); + set_changed(0); + get_boot(create_empty_dos); +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +static void +get_sectorsize(void) +{ + if (!user_set_sector_size) { + int arg; + if (ioctl(fd, BLKSSZGET, &arg) == 0) + sector_size = arg; + if (sector_size != DEFAULT_SECTOR_SIZE) + printf(_("Note: sector size is %d (not %d)\n"), + sector_size, DEFAULT_SECTOR_SIZE); + } +} + +static void +get_kernel_geometry(void) +{ + struct hd_geometry geometry; + + if (!ioctl(fd, HDIO_GETGEO, &geometry)) { + kern_heads = geometry.heads; + kern_sectors = geometry.sectors; + /* never use geometry.cylinders - it is truncated */ + } +} + +static void +get_partition_table_geometry(void) +{ + const unsigned char *bufp = (const unsigned char *)MBRbuffer; + struct partition *p; + int i, h, s, hh, ss; + int first = 1; + int bad = 0; + + if (!(valid_part_table_flag((char*)bufp))) + return; + + hh = ss = 0; + for (i = 0; i < 4; i++) { + p = pt_offset(bufp, i); + if (p->sys_ind != 0) { + h = p->end_head + 1; + s = (p->end_sector & 077); + if (first) { + hh = h; + ss = s; + first = 0; + } else if (hh != h || ss != s) + bad = 1; + } + } + + if (!first && !bad) { + pt_heads = hh; + pt_sectors = ss; + } +} + +static void +get_geometry(void) +{ + int sec_fac; + unsigned long long bytes; /* really u64 */ + + get_sectorsize(); + sec_fac = sector_size / 512; +#if ENABLE_FEATURE_SUN_LABEL + guess_device_type(); +#endif + heads = cylinders = sectors = 0; + kern_heads = kern_sectors = 0; + pt_heads = pt_sectors = 0; + + get_kernel_geometry(); + get_partition_table_geometry(); + + heads = user_heads ? user_heads : + pt_heads ? pt_heads : + kern_heads ? kern_heads : 255; + sectors = user_sectors ? user_sectors : + pt_sectors ? pt_sectors : + kern_sectors ? kern_sectors : 63; + if (ioctl(fd, BLKGETSIZE64, &bytes) == 0) { + /* got bytes */ + } else { + unsigned long longsectors; + + if (ioctl(fd, BLKGETSIZE, &longsectors)) + longsectors = 0; + bytes = ((unsigned long long) longsectors) << 9; + } + + total_number_of_sectors = (bytes >> 9); + + sector_offset = 1; + if (dos_compatible_flag) + sector_offset = sectors; + + cylinders = total_number_of_sectors / (heads * sectors * sec_fac); + if (!cylinders) + cylinders = user_cylinders; +} + +/* + * Read MBR. Returns: + * -1: no 0xaa55 flag present (possibly entire disk BSD) + * 0: found or created label + * 1: I/O error + */ +static int +get_boot(enum action what) +{ + int i; + + partitions = 4; + + for (i = 0; i < 4; i++) { + struct pte *pe = &ptes[i]; + + pe->part_table = pt_offset(MBRbuffer, i); + pe->ext_pointer = NULL; + pe->offset = 0; + pe->sectorbuffer = MBRbuffer; +#if ENABLE_FEATURE_FDISK_WRITABLE + pe->changed = (what == create_empty_dos); +#endif + } + +#if ENABLE_FEATURE_SUN_LABEL + if (what == create_empty_sun && check_sun_label()) + return 0; +#endif + + memset(MBRbuffer, 0, 512); + +#if ENABLE_FEATURE_FDISK_WRITABLE + if (what == create_empty_dos) + goto got_dos_table; /* skip reading disk */ + + if ((fd = open(disk_device, type_open)) < 0) { + if ((fd = open(disk_device, O_RDONLY)) < 0) { + if (what == try_only) + return 1; + fdisk_fatal(unable_to_open); + } else + printf(_("You will not be able to write " + "the partition table.\n")); + } + + if (512 != read(fd, MBRbuffer, 512)) { + if (what == try_only) + return 1; + fdisk_fatal(unable_to_read); + } +#else + if ((fd = open(disk_device, O_RDONLY)) < 0) + return 1; + if (512 != read(fd, MBRbuffer, 512)) + return 1; +#endif + + get_geometry(); + + update_units(); + +#if ENABLE_FEATURE_SUN_LABEL + if (check_sun_label()) + return 0; +#endif + +#if ENABLE_FEATURE_SGI_LABEL + if (check_sgi_label()) + return 0; +#endif + +#if ENABLE_FEATURE_AIX_LABEL + if (check_aix_label()) + return 0; +#endif + +#if ENABLE_FEATURE_OSF_LABEL + if (check_osf_label()) { + possibly_osf_label = 1; + if (!valid_part_table_flag(MBRbuffer)) { + current_label_type = label_osf; + return 0; + } + printf(_("This disk has both DOS and BSD magic.\n" + "Give the 'b' command to go to BSD mode.\n")); + } +#endif + +#if ENABLE_FEATURE_FDISK_WRITABLE + got_dos_table: +#endif + + if (!valid_part_table_flag(MBRbuffer)) { +#ifndef CONFIG_FEATURE_FDISK_WRITABLE + return -1; +#else + switch (what) { + case fdisk: + printf(_("Device contains neither a valid DOS " + "partition table, nor Sun, SGI or OSF " + "disklabel\n")); +#ifdef __sparc__ +#if ENABLE_FEATURE_SUN_LABEL + create_sunlabel(); +#endif +#else + create_doslabel(); +#endif + return 0; + case try_only: + return -1; + case create_empty_dos: +#if ENABLE_FEATURE_SUN_LABEL + case create_empty_sun: +#endif + break; + default: + bb_error_msg_and_die(_("internal error")); + } +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + } + +#if ENABLE_FEATURE_FDISK_WRITABLE + warn_cylinders(); +#endif + warn_geometry(); + + for (i = 0; i < 4; i++) { + struct pte *pe = &ptes[i]; + + if (IS_EXTENDED(pe->part_table->sys_ind)) { + if (partitions != 4) + printf(_("Ignoring extra extended " + "partition %d\n"), i + 1); + else + read_extended(i); + } + } + + for (i = 3; i < partitions; i++) { + struct pte *pe = &ptes[i]; + + if (!valid_part_table_flag(pe->sectorbuffer)) { + printf(_("Warning: invalid flag 0x%02x,0x%02x of partition " + "table %d will be corrected by w(rite)\n"), + pe->sectorbuffer[510], + pe->sectorbuffer[511], + i + 1); +#if ENABLE_FEATURE_FDISK_WRITABLE + pe->changed = 1; +#endif + } + } + + return 0; +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +/* + * Print the message MESG, then read an integer between LOW and HIGH (inclusive). + * If the user hits Enter, DFLT is returned. + * Answers like +10 are interpreted as offsets from BASE. + * + * There is no default if DFLT is not between LOW and HIGH. + */ +static unsigned +read_int(unsigned low, unsigned dflt, unsigned high, unsigned base, char *mesg) +{ + unsigned i; + int default_ok = 1; + const char *fmt = "%s (%u-%u, default %u): "; + + if (dflt < low || dflt > high) { + fmt = "%s (%u-%u): "; + default_ok = 0; + } + + while (1) { + int use_default = default_ok; + + /* ask question and read answer */ + do { + printf(fmt, mesg, low, high, dflt); + read_maybe_empty(""); + } while (*line_ptr != '\n' && !isdigit(*line_ptr) + && *line_ptr != '-' && *line_ptr != '+'); + + if (*line_ptr == '+' || *line_ptr == '-') { + int minus = (*line_ptr == '-'); + int absolute = 0; + + i = atoi(line_ptr + 1); + + while (isdigit(*++line_ptr)) + use_default = 0; + + switch (*line_ptr) { + case 'c': + case 'C': + if (!display_in_cyl_units) + i *= heads * sectors; + break; + case 'K': + absolute = 1024; + break; + case 'k': + absolute = 1000; + break; + case 'm': + case 'M': + absolute = 1000000; + break; + case 'g': + case 'G': + absolute = 1000000000; + break; + default: + break; + } + if (absolute) { + unsigned long long bytes; + unsigned long unit; + + bytes = (unsigned long long) i * absolute; + unit = sector_size * units_per_sector; + bytes += unit/2; /* round */ + bytes /= unit; + i = bytes; + } + if (minus) + i = -i; + i += base; + } else { + i = atoi(line_ptr); + while (isdigit(*line_ptr)) { + line_ptr++; + use_default = 0; + } + } + if (use_default) + printf(_("Using default value %u\n"), i = dflt); + if (i >= low && i <= high) + break; + else + printf(_("Value is out of range\n")); + } + return i; +} + +static int +get_partition(int warn, int max) +{ + struct pte *pe; + int i; + + i = read_int(1, 0, max, 0, _("Partition number")) - 1; + pe = &ptes[i]; + + if (warn) { + if ((!LABEL_IS_SUN && !LABEL_IS_SGI && !pe->part_table->sys_ind) + || (LABEL_IS_SUN && (!sunlabel->partitions[i].num_sectors || !sunlabel->infos[i].id)) + || (LABEL_IS_SGI && !sgi_get_num_sectors(i)) + ) { + printf(_("Warning: partition %d has empty type\n"), i+1); + } + } + return i; +} + +static int +get_existing_partition(int warn, int max) +{ + int pno = -1; + int i; + + for (i = 0; i < max; i++) { + struct pte *pe = &ptes[i]; + struct partition *p = pe->part_table; + + if (p && !is_cleared_partition(p)) { + if (pno >= 0) + goto not_unique; + pno = i; + } + } + if (pno >= 0) { + printf(_("Selected partition %d\n"), pno+1); + return pno; + } + printf(_("No partition is defined yet!\n")); + return -1; + + not_unique: + return get_partition(warn, max); +} + +static int +get_nonexisting_partition(int warn, int max) +{ + int pno = -1; + int i; + + for (i = 0; i < max; i++) { + struct pte *pe = &ptes[i]; + struct partition *p = pe->part_table; + + if (p && is_cleared_partition(p)) { + if (pno >= 0) + goto not_unique; + pno = i; + } + } + if (pno >= 0) { + printf(_("Selected partition %d\n"), pno+1); + return pno; + } + printf(_("All primary partitions have been defined already!\n")); + return -1; + + not_unique: + return get_partition(warn, max); +} + + +static void +change_units(void) +{ + display_in_cyl_units = !display_in_cyl_units; + update_units(); + printf(_("Changing display/entry units to %s\n"), + str_units(PLURAL)); +} + +static void +toggle_active(int i) +{ + struct pte *pe = &ptes[i]; + struct partition *p = pe->part_table; + + if (IS_EXTENDED(p->sys_ind) && !p->boot_ind) + printf(_("WARNING: Partition %d is an extended partition\n"), i + 1); + p->boot_ind = (p->boot_ind ? 0 : ACTIVE_FLAG); + pe->changed = 1; +} + +static void +toggle_dos_compatibility_flag(void) +{ + dos_compatible_flag = ~dos_compatible_flag; + if (dos_compatible_flag) { + sector_offset = sectors; + printf(_("DOS Compatibility flag is set\n")); + } + else { + sector_offset = 1; + printf(_("DOS Compatibility flag is not set\n")); + } +} + +static void +delete_partition(int i) +{ + struct pte *pe = &ptes[i]; + struct partition *p = pe->part_table; + struct partition *q = pe->ext_pointer; + +/* Note that for the fifth partition (i == 4) we don't actually + * decrement partitions. + */ + + if (warn_geometry()) + return; /* C/H/S not set */ + pe->changed = 1; + + if (LABEL_IS_SUN) { + sun_delete_partition(i); + return; + } + if (LABEL_IS_SGI) { + sgi_delete_partition(i); + return; + } + + if (i < 4) { + if (IS_EXTENDED(p->sys_ind) && i == ext_index) { + partitions = 4; + ptes[ext_index].ext_pointer = NULL; + extended_offset = 0; + } + clear_partition(p); + return; + } + + if (!q->sys_ind && i > 4) { + /* the last one in the chain - just delete */ + --partitions; + --i; + clear_partition(ptes[i].ext_pointer); + ptes[i].changed = 1; + } else { + /* not the last one - further ones will be moved down */ + if (i > 4) { + /* delete this link in the chain */ + p = ptes[i-1].ext_pointer; + *p = *q; + set_start_sect(p, get_start_sect(q)); + set_nr_sects(p, get_nr_sects(q)); + ptes[i-1].changed = 1; + } else if (partitions > 5) { /* 5 will be moved to 4 */ + /* the first logical in a longer chain */ + pe = &ptes[5]; + + if (pe->part_table) /* prevent SEGFAULT */ + set_start_sect(pe->part_table, + get_partition_start(pe) - + extended_offset); + pe->offset = extended_offset; + pe->changed = 1; + } + + if (partitions > 5) { + partitions--; + while (i < partitions) { + ptes[i] = ptes[i+1]; + i++; + } + } else + /* the only logical: clear only */ + clear_partition(ptes[i].part_table); + } +} + +static void +change_sysid(void) +{ + int i, sys, origsys; + struct partition *p; + + /* If sgi_label then don't use get_existing_partition, + let the user select a partition, since get_existing_partition() + only works for Linux like partition tables. */ + if (!LABEL_IS_SGI) { + i = get_existing_partition(0, partitions); + } else { + i = get_partition(0, partitions); + } + if (i == -1) + return; + p = ptes[i].part_table; + origsys = sys = get_sysid(i); + + /* if changing types T to 0 is allowed, then + the reverse change must be allowed, too */ + if (!sys && !LABEL_IS_SGI && !LABEL_IS_SUN && !get_nr_sects(p)) { + printf(_("Partition %d does not exist yet!\n"), i + 1); + return; + } + while (1) { + sys = read_hex (get_sys_types()); + + if (!sys && !LABEL_IS_SGI && !LABEL_IS_SUN) { + printf(_("Type 0 means free space to many systems\n" + "(but not to Linux). Having partitions of\n" + "type 0 is probably unwise. You can delete\n" + "a partition using the 'd' command.\n")); + /* break; */ + } + + if (!LABEL_IS_SUN && !LABEL_IS_SGI) { + if (IS_EXTENDED(sys) != IS_EXTENDED(p->sys_ind)) { + printf(_("You cannot change a partition into" + " an extended one or vice versa\n" + "Delete it first.\n")); + break; + } + } + + if (sys < 256) { + if (LABEL_IS_SUN && i == 2 && sys != SUN_WHOLE_DISK) + printf(_("Consider leaving partition 3 " + "as Whole disk (5),\n" + "as SunOS/Solaris expects it and " + "even Linux likes it.\n\n")); + if (LABEL_IS_SGI && + ( + (i == 10 && sys != SGI_ENTIRE_DISK) || + (i == 8 && sys != 0) + ) + ){ + printf(_("Consider leaving partition 9 " + "as volume header (0),\nand " + "partition 11 as entire volume (6)" + "as IRIX expects it.\n\n")); + } + if (sys == origsys) + break; + if (LABEL_IS_SUN) { + sun_change_sysid(i, sys); + } else if (LABEL_IS_SGI) { + sgi_change_sysid(i, sys); + } else + p->sys_ind = sys; + + printf(_("Changed system type of partition %d " + "to %x (%s)\n"), i + 1, sys, + partition_type(sys)); + ptes[i].changed = 1; + if (is_dos_partition(origsys) || + is_dos_partition(sys)) + dos_changed = 1; + break; + } + } +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + + +/* check_consistency() and long2chs() added Sat Mar 6 12:28:16 1993, + * faith@cs.unc.edu, based on code fragments from pfdisk by Gordon W. Ross, + * Jan. 1990 (version 1.2.1 by Gordon W. Ross Aug. 1990; Modified by S. + * Lubkin Oct. 1991). */ + +static void +long2chs(ulong ls, unsigned *c, unsigned *h, unsigned *s) +{ + int spc = heads * sectors; + + *c = ls / spc; + ls = ls % spc; + *h = ls / sectors; + *s = ls % sectors + 1; /* sectors count from 1 */ +} + +static void +check_consistency(const struct partition *p, int partition) +{ + unsigned pbc, pbh, pbs; /* physical beginning c, h, s */ + unsigned pec, peh, pes; /* physical ending c, h, s */ + unsigned lbc, lbh, lbs; /* logical beginning c, h, s */ + unsigned lec, leh, les; /* logical ending c, h, s */ + + if (!heads || !sectors || (partition >= 4)) + return; /* do not check extended partitions */ + +/* physical beginning c, h, s */ + pbc = (p->cyl & 0xff) | ((p->sector << 2) & 0x300); + pbh = p->head; + pbs = p->sector & 0x3f; + +/* physical ending c, h, s */ + pec = (p->end_cyl & 0xff) | ((p->end_sector << 2) & 0x300); + peh = p->end_head; + pes = p->end_sector & 0x3f; + +/* compute logical beginning (c, h, s) */ + long2chs(get_start_sect(p), &lbc, &lbh, &lbs); + +/* compute logical ending (c, h, s) */ + long2chs(get_start_sect(p) + get_nr_sects(p) - 1, &lec, &leh, &les); + +/* Same physical / logical beginning? */ + if (cylinders <= 1024 && (pbc != lbc || pbh != lbh || pbs != lbs)) { + printf(_("Partition %d has different physical/logical " + "beginnings (non-Linux?):\n"), partition + 1); + printf(_(" phys=(%d, %d, %d) "), pbc, pbh, pbs); + printf(_("logical=(%d, %d, %d)\n"),lbc, lbh, lbs); + } + +/* Same physical / logical ending? */ + if (cylinders <= 1024 && (pec != lec || peh != leh || pes != les)) { + printf(_("Partition %d has different physical/logical " + "endings:\n"), partition + 1); + printf(_(" phys=(%d, %d, %d) "), pec, peh, pes); + printf(_("logical=(%d, %d, %d)\n"),lec, leh, les); + } + +/* Ending on cylinder boundary? */ + if (peh != (heads - 1) || pes != sectors) { + printf(_("Partition %i does not end on cylinder boundary.\n"), + partition + 1); + } +} + +static void +list_disk_geometry(void) +{ + long long bytes = (total_number_of_sectors << 9); + long megabytes = bytes/1000000; + + if (megabytes < 10000) + printf(_("\nDisk %s: %ld MB, %lld bytes\n"), + disk_device, megabytes, bytes); + else + printf(_("\nDisk %s: %ld.%ld GB, %lld bytes\n"), + disk_device, megabytes/1000, (megabytes/100)%10, bytes); + printf(_("%d heads, %d sectors/track, %d cylinders"), + heads, sectors, cylinders); + if (units_per_sector == 1) + printf(_(", total %llu sectors"), + total_number_of_sectors / (sector_size/512)); + printf(_("\nUnits = %s of %d * %d = %d bytes\n\n"), + str_units(PLURAL), + units_per_sector, sector_size, units_per_sector * sector_size); +} + +/* + * Check whether partition entries are ordered by their starting positions. + * Return 0 if OK. Return i if partition i should have been earlier. + * Two separate checks: primary and logical partitions. + */ +static int +wrong_p_order(int *prev) +{ + const struct pte *pe; + const struct partition *p; + off_t last_p_start_pos = 0, p_start_pos; + int i, last_i = 0; + + for (i = 0 ; i < partitions; i++) { + if (i == 4) { + last_i = 4; + last_p_start_pos = 0; + } + pe = &ptes[i]; + if ((p = pe->part_table)->sys_ind) { + p_start_pos = get_partition_start(pe); + + if (last_p_start_pos > p_start_pos) { + if (prev) + *prev = last_i; + return i; + } + + last_p_start_pos = p_start_pos; + last_i = i; + } + } + return 0; +} + +#if ENABLE_FEATURE_FDISK_ADVANCED +/* + * Fix the chain of logicals. + * extended_offset is unchanged, the set of sectors used is unchanged + * The chain is sorted so that sectors increase, and so that + * starting sectors increase. + * + * After this it may still be that cfdisk doesnt like the table. + * (This is because cfdisk considers expanded parts, from link to + * end of partition, and these may still overlap.) + * Now + * sfdisk /dev/hda > ohda; sfdisk /dev/hda < ohda + * may help. + */ +static void +fix_chain_of_logicals(void) +{ + int j, oj, ojj, sj, sjj; + struct partition *pj,*pjj,tmp; + + /* Stage 1: sort sectors but leave sector of part 4 */ + /* (Its sector is the global extended_offset.) */ + stage1: + for (j = 5; j < partitions-1; j++) { + oj = ptes[j].offset; + ojj = ptes[j+1].offset; + if (oj > ojj) { + ptes[j].offset = ojj; + ptes[j+1].offset = oj; + pj = ptes[j].part_table; + set_start_sect(pj, get_start_sect(pj)+oj-ojj); + pjj = ptes[j+1].part_table; + set_start_sect(pjj, get_start_sect(pjj)+ojj-oj); + set_start_sect(ptes[j-1].ext_pointer, + ojj-extended_offset); + set_start_sect(ptes[j].ext_pointer, + oj-extended_offset); + goto stage1; + } + } + + /* Stage 2: sort starting sectors */ + stage2: + for (j = 4; j < partitions-1; j++) { + pj = ptes[j].part_table; + pjj = ptes[j+1].part_table; + sj = get_start_sect(pj); + sjj = get_start_sect(pjj); + oj = ptes[j].offset; + ojj = ptes[j+1].offset; + if (oj+sj > ojj+sjj) { + tmp = *pj; + *pj = *pjj; + *pjj = tmp; + set_start_sect(pj, ojj+sjj-oj); + set_start_sect(pjj, oj+sj-ojj); + goto stage2; + } + } + + /* Probably something was changed */ + for (j = 4; j < partitions; j++) + ptes[j].changed = 1; +} + + +static void +fix_partition_table_order(void) +{ + struct pte *pei, *pek; + int i,k; + + if (!wrong_p_order(NULL)) { + printf(_("Nothing to do. Ordering is correct already.\n\n")); + return; + } + + while ((i = wrong_p_order(&k)) != 0 && i < 4) { + /* partition i should have come earlier, move it */ + /* We have to move data in the MBR */ + struct partition *pi, *pk, *pe, pbuf; + pei = &ptes[i]; + pek = &ptes[k]; + + pe = pei->ext_pointer; + pei->ext_pointer = pek->ext_pointer; + pek->ext_pointer = pe; + + pi = pei->part_table; + pk = pek->part_table; + + memmove(&pbuf, pi, sizeof(struct partition)); + memmove(pi, pk, sizeof(struct partition)); + memmove(pk, &pbuf, sizeof(struct partition)); + + pei->changed = pek->changed = 1; + } + + if (i) + fix_chain_of_logicals(); + + printf("Done.\n"); + +} +#endif + +static void +list_table(int xtra) +{ + const struct partition *p; + int i, w; + + if (LABEL_IS_SUN) { + sun_list_table(xtra); + return; + } + if (LABEL_IS_SUN) { + sgi_list_table(xtra); + return; + } + + list_disk_geometry(); + + if (LABEL_IS_OSF) { + xbsd_print_disklabel(xtra); + return; + } + + /* Heuristic: we list partition 3 of /dev/foo as /dev/foo3, + but if the device name ends in a digit, say /dev/foo1, + then the partition is called /dev/foo1p3. */ + w = strlen(disk_device); + if (w && isdigit(disk_device[w-1])) + w++; + if (w < 5) + w = 5; + + // 1 12345678901 12345678901 12345678901 12 + printf(_("%*s Boot Start End Blocks Id System\n"), + w+1, _("Device")); + + for (i = 0; i < partitions; i++) { + const struct pte *pe = &ptes[i]; + off_t psects; + off_t pblocks; + unsigned podd; + + p = pe->part_table; + if (!p || is_cleared_partition(p)) + continue; + + psects = get_nr_sects(p); + pblocks = psects; + podd = 0; + + if (sector_size < 1024) { + pblocks /= (1024 / sector_size); + podd = psects % (1024 / sector_size); + } + if (sector_size > 1024) + pblocks *= (sector_size / 1024); + + printf("%s %c %11llu %11llu %11llu%c %2x %s\n", + partname(disk_device, i+1, w+2), + !p->boot_ind ? ' ' : p->boot_ind == ACTIVE_FLAG /* boot flag */ + ? '*' : '?', + (unsigned long long) cround(get_partition_start(pe)), /* start */ + (unsigned long long) cround(get_partition_start(pe) + psects /* end */ + - (psects ? 1 : 0)), + (unsigned long long) pblocks, podd ? '+' : ' ', /* odd flag on end */ + p->sys_ind, /* type id */ + partition_type(p->sys_ind)); /* type name */ + + check_consistency(p, i); + } + + /* Is partition table in disk order? It need not be, but... */ + /* partition table entries are not checked for correct order if this + is a sgi, sun or aix labeled disk... */ + if (LABEL_IS_DOS && wrong_p_order(NULL)) { + /* FIXME */ + printf(_("\nPartition table entries are not in disk order\n")); + } +} + +#if ENABLE_FEATURE_FDISK_ADVANCED +static void +x_list_table(int extend) +{ + const struct pte *pe; + const struct partition *p; + int i; + + printf(_("\nDisk %s: %d heads, %d sectors, %d cylinders\n\n"), + disk_device, heads, sectors, cylinders); + printf(_("Nr AF Hd Sec Cyl Hd Sec Cyl Start Size ID\n")); + for (i = 0 ; i < partitions; i++) { + pe = &ptes[i]; + p = (extend ? pe->ext_pointer : pe->part_table); + if (p != NULL) { + printf("%2d %02x%4d%4d%5d%4d%4d%5d%11u%11u %02x\n", + i + 1, p->boot_ind, p->head, + sector(p->sector), + cylinder(p->sector, p->cyl), p->end_head, + sector(p->end_sector), + cylinder(p->end_sector, p->end_cyl), + get_start_sect(p), get_nr_sects(p), p->sys_ind); + if (p->sys_ind) + check_consistency(p, i); + } + } +} +#endif + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +fill_bounds(off_t *first, off_t *last) +{ + int i; + const struct pte *pe = &ptes[0]; + const struct partition *p; + + for (i = 0; i < partitions; pe++,i++) { + p = pe->part_table; + if (!p->sys_ind || IS_EXTENDED(p->sys_ind)) { + first[i] = 0xffffffff; + last[i] = 0; + } else { + first[i] = get_partition_start(pe); + last[i] = first[i] + get_nr_sects(p) - 1; + } + } +} + +static void +check(int n, unsigned h, unsigned s, unsigned c, off_t start) +{ + off_t total, real_s, real_c; + + real_s = sector(s) - 1; + real_c = cylinder(s, c); + total = (real_c * sectors + real_s) * heads + h; + if (!total) + printf(_("Warning: partition %d contains sector 0\n"), n); + if (h >= heads) + printf(_("Partition %d: head %d greater than maximum %d\n"), + n, h + 1, heads); + if (real_s >= sectors) + printf(_("Partition %d: sector %d greater than " + "maximum %d\n"), n, s, sectors); + if (real_c >= cylinders) + printf(_("Partitions %d: cylinder %llu greater than " + "maximum %d\n"), n, (unsigned long long)real_c + 1, cylinders); + if (cylinders <= 1024 && start != total) + printf(_("Partition %d: previous sectors %llu disagrees with " + "total %llu\n"), n, (unsigned long long)start, (unsigned long long)total); +} + +static void +verify(void) +{ + int i, j; + unsigned total = 1; + off_t first[partitions], last[partitions]; + struct partition *p; + + if (warn_geometry()) + return; + + if (LABEL_IS_SUN) { + verify_sun(); + return; + } + if (LABEL_IS_SGI) { + verify_sgi(1); + return; + } + + fill_bounds(first, last); + for (i = 0; i < partitions; i++) { + struct pte *pe = &ptes[i]; + + p = pe->part_table; + if (p->sys_ind && !IS_EXTENDED(p->sys_ind)) { + check_consistency(p, i); + if (get_partition_start(pe) < first[i]) + printf(_("Warning: bad start-of-data in " + "partition %d\n"), i + 1); + check(i + 1, p->end_head, p->end_sector, p->end_cyl, + last[i]); + total += last[i] + 1 - first[i]; + for (j = 0; j < i; j++) + if ((first[i] >= first[j] && first[i] <= last[j]) + || ((last[i] <= last[j] && last[i] >= first[j]))) { + printf(_("Warning: partition %d overlaps " + "partition %d.\n"), j + 1, i + 1); + total += first[i] >= first[j] ? + first[i] : first[j]; + total -= last[i] <= last[j] ? + last[i] : last[j]; + } + } + } + + if (extended_offset) { + struct pte *pex = &ptes[ext_index]; + off_t e_last = get_start_sect(pex->part_table) + + get_nr_sects(pex->part_table) - 1; + + for (i = 4; i < partitions; i++) { + total++; + p = ptes[i].part_table; + if (!p->sys_ind) { + if (i != 4 || i + 1 < partitions) + printf(_("Warning: partition %d " + "is empty\n"), i + 1); + } + else if (first[i] < extended_offset || + last[i] > e_last) + printf(_("Logical partition %d not entirely in " + "partition %d\n"), i + 1, ext_index + 1); + } + } + + if (total > heads * sectors * cylinders) + printf(_("Total allocated sectors %d greater than the maximum " + "%d\n"), total, heads * sectors * cylinders); + else if ((total = heads * sectors * cylinders - total) != 0) + printf(_("%d unallocated sectors\n"), total); +} + +static void +add_partition(int n, int sys) +{ + char mesg[256]; /* 48 does not suffice in Japanese */ + int i, num_read = 0; + struct partition *p = ptes[n].part_table; + struct partition *q = ptes[ext_index].part_table; + long long llimit; + off_t start, stop = 0, limit, temp, + first[partitions], last[partitions]; + + if (p && p->sys_ind) { + printf(_("Partition %d is already defined. Delete " + "it before re-adding it.\n"), n + 1); + return; + } + fill_bounds(first, last); + if (n < 4) { + start = sector_offset; + if (display_in_cyl_units || !total_number_of_sectors) + llimit = heads * sectors * cylinders - 1; + else + llimit = total_number_of_sectors - 1; + limit = llimit; + if (limit != llimit) + limit = 0x7fffffff; + if (extended_offset) { + first[ext_index] = extended_offset; + last[ext_index] = get_start_sect(q) + + get_nr_sects(q) - 1; + } + } else { + start = extended_offset + sector_offset; + limit = get_start_sect(q) + get_nr_sects(q) - 1; + } + if (display_in_cyl_units) + for (i = 0; i < partitions; i++) + first[i] = (cround(first[i]) - 1) * units_per_sector; + + snprintf(mesg, sizeof(mesg), _("First %s"), str_units(SINGULAR)); + do { + temp = start; + for (i = 0; i < partitions; i++) { + int lastplusoff; + + if (start == ptes[i].offset) + start += sector_offset; + lastplusoff = last[i] + ((n < 4) ? 0 : sector_offset); + if (start >= first[i] && start <= lastplusoff) + start = lastplusoff + 1; + } + if (start > limit) + break; + if (start >= temp+units_per_sector && num_read) { + printf(_("Sector %"OFF_FMT"d is already allocated\n"), temp); + temp = start; + num_read = 0; + } + if (!num_read && start == temp) { + off_t saved_start; + + saved_start = start; + start = read_int(cround(saved_start), cround(saved_start), cround(limit), + 0, mesg); + if (display_in_cyl_units) { + start = (start - 1) * units_per_sector; + if (start < saved_start) start = saved_start; + } + num_read = 1; + } + } while (start != temp || !num_read); + if (n > 4) { /* NOT for fifth partition */ + struct pte *pe = &ptes[n]; + + pe->offset = start - sector_offset; + if (pe->offset == extended_offset) { /* must be corrected */ + pe->offset++; + if (sector_offset == 1) + start++; + } + } + + for (i = 0; i < partitions; i++) { + struct pte *pe = &ptes[i]; + + if (start < pe->offset && limit >= pe->offset) + limit = pe->offset - 1; + if (start < first[i] && limit >= first[i]) + limit = first[i] - 1; + } + if (start > limit) { + printf(_("No free sectors available\n")); + if (n > 4) + partitions--; + return; + } + if (cround(start) == cround(limit)) { + stop = limit; + } else { + snprintf(mesg, sizeof(mesg), + _("Last %s or +size or +sizeM or +sizeK"), + str_units(SINGULAR)); + stop = read_int(cround(start), cround(limit), cround(limit), + cround(start), mesg); + if (display_in_cyl_units) { + stop = stop * units_per_sector - 1; + if (stop >limit) + stop = limit; + } + } + + set_partition(n, 0, start, stop, sys); + if (n > 4) + set_partition(n - 1, 1, ptes[n].offset, stop, EXTENDED); + + if (IS_EXTENDED(sys)) { + struct pte *pe4 = &ptes[4]; + struct pte *pen = &ptes[n]; + + ext_index = n; + pen->ext_pointer = p; + pe4->offset = extended_offset = start; + pe4->sectorbuffer = xzalloc(sector_size); + pe4->part_table = pt_offset(pe4->sectorbuffer, 0); + pe4->ext_pointer = pe4->part_table + 1; + pe4->changed = 1; + partitions = 5; + } +} + +static void +add_logical(void) +{ + if (partitions > 5 || ptes[4].part_table->sys_ind) { + struct pte *pe = &ptes[partitions]; + + pe->sectorbuffer = xzalloc(sector_size); + pe->part_table = pt_offset(pe->sectorbuffer, 0); + pe->ext_pointer = pe->part_table + 1; + pe->offset = 0; + pe->changed = 1; + partitions++; + } + add_partition(partitions - 1, LINUX_NATIVE); +} + +static void +new_partition(void) +{ + int i, free_primary = 0; + + if (warn_geometry()) + return; + + if (LABEL_IS_SUN) { + add_sun_partition(get_partition(0, partitions), LINUX_NATIVE); + return; + } + if (LABEL_IS_SGI) { + sgi_add_partition(get_partition(0, partitions), LINUX_NATIVE); + return; + } + if (LABEL_IS_AIX) { + printf(_("\tSorry - this fdisk cannot handle AIX disk labels." + "\n\tIf you want to add DOS-type partitions, create" + "\n\ta new empty DOS partition table first. (Use o.)" + "\n\tWARNING: " + "This will destroy the present disk contents.\n")); + return; + } + + for (i = 0; i < 4; i++) + free_primary += !ptes[i].part_table->sys_ind; + + if (!free_primary && partitions >= MAXIMUM_PARTS) { + printf(_("The maximum number of partitions has been created\n")); + return; + } + + if (!free_primary) { + if (extended_offset) + add_logical(); + else + printf(_("You must delete some partition and add " + "an extended partition first\n")); + } else { + char c, line[LINE_LENGTH]; + snprintf(line, sizeof(line), "%s\n %s\n p primary " + "partition (1-4)\n", + "Command action", (extended_offset ? + "l logical (5 or over)" : "e extended")); + while (1) { + c = read_nonempty(line); + if (c == 'p' || c == 'P') { + i = get_nonexisting_partition(0, 4); + if (i >= 0) + add_partition(i, LINUX_NATIVE); + return; + } + else if (c == 'l' && extended_offset) { + add_logical(); + return; + } + else if (c == 'e' && !extended_offset) { + i = get_nonexisting_partition(0, 4); + if (i >= 0) + add_partition(i, EXTENDED); + return; + } + else + printf(_("Invalid partition number " + "for type '%c'\n"), c); + } + } +} + +static void +write_table(void) +{ + int i; + + if (LABEL_IS_DOS) { + for (i = 0; i < 3; i++) + if (ptes[i].changed) + ptes[3].changed = 1; + for (i = 3; i < partitions; i++) { + struct pte *pe = &ptes[i]; + + if (pe->changed) { + write_part_table_flag(pe->sectorbuffer); + write_sector(pe->offset, pe->sectorbuffer); + } + } + } + else if (LABEL_IS_SGI) { + /* no test on change? the printf below might be mistaken */ + sgi_write_table(); + } + else if (LABEL_IS_SUN) { + int needw = 0; + + for (i = 0; i < 8; i++) + if (ptes[i].changed) + needw = 1; + if (needw) + sun_write_table(); + } + + printf(_("The partition table has been altered!\n\n")); + reread_partition_table(1); +} + +static void +reread_partition_table(int leave) +{ + int error = 0; + int i; + + printf(_("Calling ioctl() to re-read partition table.\n")); + sync(); + sleep(2); + if ((i = ioctl(fd, BLKRRPART)) != 0) { + error = errno; + } else { + /* some kernel versions (1.2.x) seem to have trouble + rereading the partition table, but if asked to do it + twice, the second time works. - biro@yggdrasil.com */ + sync(); + sleep(2); + if ((i = ioctl(fd, BLKRRPART)) != 0) + error = errno; + } + + if (i) { + printf(_("\nWARNING: Re-reading the partition table " + "failed with error %d: %s.\n" + "The kernel still uses the old table.\n" + "The new table will be used " + "at the next reboot.\n"), + error, strerror(error)); + } + + if (dos_changed) + printf( + _("\nWARNING: If you have created or modified any DOS 6.x\n" + "partitions, please see the fdisk manual page for additional\n" + "information.\n")); + + if (leave) { + close(fd); + + printf(_("Syncing disks.\n")); + sync(); + sleep(4); /* for sync() */ + exit(!!i); + } +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +#if ENABLE_FEATURE_FDISK_ADVANCED +#define MAX_PER_LINE 16 +static void +print_buffer(char *pbuffer) +{ + int i,l; + + for (i = 0, l = 0; i < sector_size; i++, l++) { + if (l == 0) + printf("0x%03X:", i); + printf(" %02X", (unsigned char) pbuffer[i]); + if (l == MAX_PER_LINE - 1) { + puts(""); + l = -1; + } + } + if (l > 0) + puts(""); + puts(""); +} + + +static void +print_raw(void) +{ + int i; + + printf(_("Device: %s\n"), disk_device); + if (LABEL_IS_SGI || LABEL_IS_SUN) + print_buffer(MBRbuffer); + else { + for (i = 3; i < partitions; i++) + print_buffer(ptes[i].sectorbuffer); + } +} + +static void +move_begin(int i) +{ + struct pte *pe = &ptes[i]; + struct partition *p = pe->part_table; + off_t new, first; + + if (warn_geometry()) + return; + if (!p->sys_ind || !get_nr_sects(p) || IS_EXTENDED(p->sys_ind)) { + printf(_("Partition %d has no data area\n"), i + 1); + return; + } + first = get_partition_start(pe); + new = read_int(first, first, first + get_nr_sects(p) - 1, first, + _("New beginning of data")) - pe->offset; + + if (new != get_nr_sects(p)) { + first = get_nr_sects(p) + get_start_sect(p) - new; + set_nr_sects(p, first); + set_start_sect(p, new); + pe->changed = 1; + } +} + +static void +xselect(void) +{ + char c; + + while (1) { + putchar('\n'); + c = tolower(read_nonempty(_("Expert command (m for help): "))); + switch (c) { + case 'a': + if (LABEL_IS_SUN) + sun_set_alt_cyl(); + break; + case 'b': + if (LABEL_IS_DOS) + move_begin(get_partition(0, partitions)); + break; + case 'c': + user_cylinders = cylinders = + read_int(1, cylinders, 1048576, 0, + _("Number of cylinders")); + if (LABEL_IS_SUN) + sun_set_ncyl(cylinders); + if (LABEL_IS_DOS) + warn_cylinders(); + break; + case 'd': + print_raw(); + break; + case 'e': + if (LABEL_IS_SGI) + sgi_set_xcyl(); + else if (LABEL_IS_SUN) + sun_set_xcyl(); + else if (LABEL_IS_DOS) + x_list_table(1); + break; + case 'f': + if (LABEL_IS_DOS) + fix_partition_table_order(); + break; + case 'g': +#if ENABLE_FEATURE_SGI_LABEL + create_sgilabel(); +#endif + break; + case 'h': + user_heads = heads = read_int(1, heads, 256, 0, + _("Number of heads")); + update_units(); + break; + case 'i': + if (LABEL_IS_SUN) + sun_set_ilfact(); + break; + case 'o': + if (LABEL_IS_SUN) + sun_set_rspeed(); + break; + case 'p': + if (LABEL_IS_SUN) + list_table(1); + else + x_list_table(0); + break; + case 'q': + close(fd); + puts(""); + exit(0); + case 'r': + return; + case 's': + user_sectors = sectors = read_int(1, sectors, 63, 0, + _("Number of sectors")); + if (dos_compatible_flag) { + sector_offset = sectors; + printf(_("Warning: setting sector offset for DOS " + "compatiblity\n")); + } + update_units(); + break; + case 'v': + verify(); + break; + case 'w': + write_table(); /* does not return */ + break; + case 'y': + if (LABEL_IS_SUN) + sun_set_pcylcount(); + break; + default: + xmenu(); + } + } +} +#endif /* ADVANCED mode */ + +static int +is_ide_cdrom_or_tape(const char *device) +{ + FILE *procf; + char buf[100]; + struct stat statbuf; + int is_ide = 0; + + /* No device was given explicitly, and we are trying some + likely things. But opening /dev/hdc may produce errors like + "hdc: tray open or drive not ready" + if it happens to be a CD-ROM drive. It even happens that + the process hangs on the attempt to read a music CD. + So try to be careful. This only works since 2.1.73. */ + + if (strncmp("/dev/hd", device, 7)) + return 0; + + snprintf(buf, sizeof(buf), "/proc/ide/%s/media", device+5); + procf = fopen(buf, "r"); + if (procf != NULL && fgets(buf, sizeof(buf), procf)) + is_ide = (!strncmp(buf, "cdrom", 5) || + !strncmp(buf, "tape", 4)); + else + /* Now when this proc file does not exist, skip the + device when it is read-only. */ + if (stat(device, &statbuf) == 0) + is_ide = ((statbuf.st_mode & 0222) == 0); + + if (procf) + fclose(procf); + return is_ide; +} + + +static void +try(const char *device, int user_specified) +{ + int gb; + + disk_device = device; + if (setjmp(listingbuf)) + return; + if (!user_specified) + if (is_ide_cdrom_or_tape(device)) + return; + if ((fd = open(disk_device, type_open)) >= 0) { + gb = get_boot(try_only); + if (gb > 0) { /* I/O error */ + close(fd); + } else if (gb < 0) { /* no DOS signature */ + list_disk_geometry(); + if (LABEL_IS_AIX) { + return; + } +#if ENABLE_FEATURE_OSF_LABEL + if (btrydev(device) < 0) +#endif + printf(_("Disk %s doesn't contain a valid " + "partition table\n"), device); + close(fd); + } else { + close(fd); + list_table(0); +#if ENABLE_FEATURE_FDISK_WRITABLE + if (!LABEL_IS_SUN && partitions > 4){ + delete_partition(ext_index); + } +#endif + } + } else { + /* Ignore other errors, since we try IDE + and SCSI hard disks which may not be + installed on the system. */ + if (errno == EACCES) { + printf(_("Cannot open %s\n"), device); + return; + } + } +} + +/* for fdisk -l: try all things in /proc/partitions + that look like a partition name (do not end in a digit) */ +static void +tryprocpt(void) +{ + FILE *procpt; + char line[100], ptname[100], devname[120], *s; + int ma, mi, sz; + + procpt = fopen_or_warn("/proc/partitions", "r"); + + while (fgets(line, sizeof(line), procpt)) { + if (sscanf(line, " %d %d %d %[^\n ]", + &ma, &mi, &sz, ptname) != 4) + continue; + for (s = ptname; *s; s++); + if (isdigit(s[-1])) + continue; + sprintf(devname, "/dev/%s", ptname); + try(devname, 0); + } +#if ENABLE_FEATURE_CLEAN_UP + fclose(procpt); +#endif +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +unknown_command(int c) +{ + printf(_("%c: unknown command\n"), c); +} +#endif + +int fdisk_main(int argc, char **argv) +{ + char *str_b, *str_C, *str_H, *str_S; + unsigned opt; + int c; + /* + * fdisk -v + * fdisk -l [-b sectorsize] [-u] device ... + * fdisk -s [partition] ... + * fdisk [-b sectorsize] [-u] device + * + * Options -C, -H, -S set the geometry. + */ + enum { + OPT_b = 1 << 0, + OPT_C = 1 << 1, + OPT_H = 1 << 2, + OPT_l = 1 << 3, + OPT_S = 1 << 4, + OPT_u = 1 << 5, + OPT_s = (1 << 6) * ENABLE_FEATURE_FDISK_BLKSIZE, + }; + opt = getopt32(argc, argv, "b:C:H:lS:u" USE_FEATURE_FDISK_BLKSIZE("s"), + &str_b, &str_C, &str_H, &str_S); + argc -= optind; + argv += optind; + if (opt & OPT_b) { // -b + /* Ugly: this sector size is really per device, + so cannot be combined with multiple disks, + and the same goes for the C/H/S options. + */ + sector_size = xatoi_u(str_b); + if (sector_size != 512 && sector_size != 1024 && + sector_size != 2048) + bb_show_usage(); + sector_offset = 2; + user_set_sector_size = 1; + } + if (opt & OPT_C) user_cylinders = xatoi_u(str_C); // -C + if (opt & OPT_H) { // -H + user_heads = xatoi_u(str_H); + if (user_heads <= 0 || user_heads >= 256) + user_heads = 0; + } + //if (opt & OPT_l) // -l + if (opt & OPT_S) { // -S + user_sectors = xatoi_u(str_S); + if (user_sectors <= 0 || user_sectors >= 64) + user_sectors = 0; + } + if (opt & OPT_u) display_in_cyl_units = 0; // -u + //if (opt & OPT_s) // -s + + if (user_set_sector_size && argc != 1) + printf(_("Warning: the -b (set sector size) option should" + " be used with one specified device\n")); + +#if ENABLE_FEATURE_FDISK_WRITABLE + if (opt & OPT_l) { + nowarn = 1; +#endif + type_open = O_RDONLY; + if (argc > 0) { + int k; +#if __GNUC__ + /* avoid gcc warning: + variable `k' might be clobbered by `longjmp' */ + (void)&k; +#endif + listing = 1; + for (k = 0; k < argc; k++) + try(argv[k], 1); + } else { + /* we no longer have default device names */ + /* but, we can use /proc/partitions instead */ + tryprocpt(); + } + return 0; +#if ENABLE_FEATURE_FDISK_WRITABLE + } +#endif + +#if ENABLE_FEATURE_FDISK_BLKSIZE + if (opt & OPT_s) { + long size; + int j; + + nowarn = 1; + type_open = O_RDONLY; + + if (argc <= 0) + bb_show_usage(); + + for (j = 0; j < argc; j++) { + disk_device = argv[j]; + fd = open(disk_device, type_open); + if (fd < 0) + fdisk_fatal(unable_to_open); + if (ioctl(fd, BLKGETSIZE, &size)) + fdisk_fatal(ioctl_error); + close(fd); + if (argc == 1) + printf("%ld\n", size/2); + else + printf("%s: %ld\n", argv[j], size/2); + } + return 0; + } +#endif + +#if ENABLE_FEATURE_FDISK_WRITABLE + if (argc != 1) + bb_show_usage(); + + disk_device = argv[0]; + get_boot(fdisk); + + if (LABEL_IS_OSF) { + /* OSF label, and no DOS label */ + printf(_("Detected an OSF/1 disklabel on %s, entering " + "disklabel mode.\n"), disk_device); + bsd_select(); + /*Why do we do this? It seems to be counter-intuitive*/ + current_label_type = label_dos; + /* If we return we may want to make an empty DOS label? */ + } + + while (1) { + putchar('\n'); + c = tolower(read_nonempty(_("Command (m for help): "))); + switch (c) { + case 'a': + if (LABEL_IS_DOS) + toggle_active(get_partition(1, partitions)); + else if (LABEL_IS_SUN) + toggle_sunflags(get_partition(1, partitions), + 0x01); + else if (LABEL_IS_SGI) + sgi_set_bootpartition( + get_partition(1, partitions)); + else + unknown_command(c); + break; + case 'b': + if (LABEL_IS_SGI) { + printf(_("\nThe current boot file is: %s\n"), + sgi_get_bootfile()); + if (read_maybe_empty(_("Please enter the name of the " + "new boot file: ")) == '\n') + printf(_("Boot file unchanged\n")); + else + sgi_set_bootfile(line_ptr); + } +#if ENABLE_FEATURE_OSF_LABEL + else + bsd_select(); +#endif + break; + case 'c': + if (LABEL_IS_DOS) + toggle_dos_compatibility_flag(); + else if (LABEL_IS_SUN) + toggle_sunflags(get_partition(1, partitions), + 0x10); + else if (LABEL_IS_SGI) + sgi_set_swappartition( + get_partition(1, partitions)); + else + unknown_command(c); + break; + case 'd': + { + int j; + /* If sgi_label then don't use get_existing_partition, + let the user select a partition, since + get_existing_partition() only works for Linux-like + partition tables */ + if (!LABEL_IS_SGI) { + j = get_existing_partition(1, partitions); + } else { + j = get_partition(1, partitions); + } + if (j >= 0) + delete_partition(j); + } + break; + case 'i': + if (LABEL_IS_SGI) + create_sgiinfo(); + else + unknown_command(c); + case 'l': + list_types(get_sys_types()); + break; + case 'm': + menu(); + break; + case 'n': + new_partition(); + break; + case 'o': + create_doslabel(); + break; + case 'p': + list_table(0); + break; + case 'q': + close(fd); + puts(""); + return 0; + case 's': +#if ENABLE_FEATURE_SUN_LABEL + create_sunlabel(); +#endif + break; + case 't': + change_sysid(); + break; + case 'u': + change_units(); + break; + case 'v': + verify(); + break; + case 'w': + write_table(); /* does not return */ + break; +#if ENABLE_FEATURE_FDISK_ADVANCED + case 'x': + if (LABEL_IS_SGI) { + printf(_("\n\tSorry, no experts menu for SGI " + "partition tables available.\n\n")); + } else + xselect(); + break; +#endif + default: + unknown_command(c); + menu(); + } + } + return 0; +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ +} diff --git a/util-linux/fdisk_aix.c b/util-linux/fdisk_aix.c new file mode 100644 index 000000000..a3d5fe15f --- /dev/null +++ b/util-linux/fdisk_aix.c @@ -0,0 +1,76 @@ +#ifdef CONFIG_FEATURE_AIX_LABEL +/* + * Copyright (C) Andreas Neuper, Sep 1998. + * This file may be redistributed under + * the terms of the GNU Public License. + */ + +typedef struct { + unsigned int magic; /* expect AIX_LABEL_MAGIC */ + unsigned int fillbytes1[124]; + unsigned int physical_volume_id; + unsigned int fillbytes2[124]; +} aix_partition; + +#define AIX_LABEL_MAGIC 0xc9c2d4c1 +#define AIX_LABEL_MAGIC_SWAPPED 0xc1d4c2c9 +#define AIX_INFO_MAGIC 0x00072959 +#define AIX_INFO_MAGIC_SWAPPED 0x59290700 + +#define aixlabel ((aix_partition *)MBRbuffer) + + +/* + Changes: + * 1999-03-20 Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * Internationalization + * + * 2003-03-20 Phillip Kesling <pkesling@sgi.com> + * Some fixes +*/ + +static int aix_other_endian; +static short aix_volumes = 1; + +/* + * only dealing with free blocks here + */ + +static void +aix_info(void) +{ + puts( + _("\n\tThere is a valid AIX label on this disk.\n" + "\tUnfortunately Linux cannot handle these\n" + "\tdisks at the moment. Nevertheless some\n" + "\tadvice:\n" + "\t1. fdisk will destroy its contents on write.\n" + "\t2. Be sure that this disk is NOT a still vital\n" + "\t part of a volume group. (Otherwise you may\n" + "\t erase the other disks as well, if unmirrored.)\n" + "\t3. Before deleting this physical volume be sure\n" + "\t to remove the disk logically from your AIX\n" + "\t machine. (Otherwise you become an AIXpert).") + ); +} + +static int +check_aix_label(void) +{ + if (aixlabel->magic != AIX_LABEL_MAGIC && + aixlabel->magic != AIX_LABEL_MAGIC_SWAPPED) { + current_label_type = 0; + aix_other_endian = 0; + return 0; + } + aix_other_endian = (aixlabel->magic == AIX_LABEL_MAGIC_SWAPPED); + update_units(); + current_label_type = label_aix; + partitions = 1016; + aix_volumes = 15; + aix_info(); + /*aix_nolabel();*/ /* %% */ + /*aix_label = 1;*/ /* %% */ + return 1; +} +#endif /* AIX_LABEL */ diff --git a/util-linux/fdisk_osf.c b/util-linux/fdisk_osf.c new file mode 100644 index 000000000..bff2371e4 --- /dev/null +++ b/util-linux/fdisk_osf.c @@ -0,0 +1,1046 @@ +#ifdef CONFIG_FEATURE_OSF_LABEL +/* + * Copyright (c) 1987, 1988 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgment: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +#ifndef BSD_DISKMAGIC +#define BSD_DISKMAGIC ((uint32_t) 0x82564557) +#endif + +#ifndef BSD_MAXPARTITIONS +#define BSD_MAXPARTITIONS 16 +#endif + +#define BSD_LINUX_BOOTDIR "/usr/ucb/mdec" + +#if defined (i386) || defined (__sparc__) || defined (__arm__) || defined (__m68k__) || defined (__mips__) || defined (__s390__) || defined (__sh__) || defined(__x86_64__) +#define BSD_LABELSECTOR 1 +#define BSD_LABELOFFSET 0 +#elif defined (__alpha__) || defined (__powerpc__) || defined (__ia64__) || defined (__hppa__) +#define BSD_LABELSECTOR 0 +#define BSD_LABELOFFSET 64 +#elif defined (__s390__) || defined (__s390x__) +#define BSD_LABELSECTOR 1 +#define BSD_LABELOFFSET 0 +#else +#error unknown architecture +#endif + +#define BSD_BBSIZE 8192 /* size of boot area, with label */ +#define BSD_SBSIZE 8192 /* max size of fs superblock */ + +struct xbsd_disklabel { + uint32_t d_magic; /* the magic number */ + int16_t d_type; /* drive type */ + int16_t d_subtype; /* controller/d_type specific */ + char d_typename[16]; /* type name, e.g. "eagle" */ + char d_packname[16]; /* pack identifier */ + /* disk geometry: */ + uint32_t d_secsize; /* # of bytes per sector */ + uint32_t d_nsectors; /* # of data sectors per track */ + uint32_t d_ntracks; /* # of tracks per cylinder */ + uint32_t d_ncylinders; /* # of data cylinders per unit */ + uint32_t d_secpercyl; /* # of data sectors per cylinder */ + uint32_t d_secperunit; /* # of data sectors per unit */ + /* + * Spares (bad sector replacements) below + * are not counted in d_nsectors or d_secpercyl. + * Spare sectors are assumed to be physical sectors + * which occupy space at the end of each track and/or cylinder. + */ + uint16_t d_sparespertrack; /* # of spare sectors per track */ + uint16_t d_sparespercyl; /* # of spare sectors per cylinder */ + /* + * Alternate cylinders include maintenance, replacement, + * configuration description areas, etc. + */ + uint32_t d_acylinders; /* # of alt. cylinders per unit */ + + /* hardware characteristics: */ + /* + * d_interleave, d_trackskew and d_cylskew describe perturbations + * in the media format used to compensate for a slow controller. + * Interleave is physical sector interleave, set up by the formatter + * or controller when formatting. When interleaving is in use, + * logically adjacent sectors are not physically contiguous, + * but instead are separated by some number of sectors. + * It is specified as the ratio of physical sectors traversed + * per logical sector. Thus an interleave of 1:1 implies contiguous + * layout, while 2:1 implies that logical sector 0 is separated + * by one sector from logical sector 1. + * d_trackskew is the offset of sector 0 on track N + * relative to sector 0 on track N-1 on the same cylinder. + * Finally, d_cylskew is the offset of sector 0 on cylinder N + * relative to sector 0 on cylinder N-1. + */ + uint16_t d_rpm; /* rotational speed */ + uint16_t d_interleave; /* hardware sector interleave */ + uint16_t d_trackskew; /* sector 0 skew, per track */ + uint16_t d_cylskew; /* sector 0 skew, per cylinder */ + uint32_t d_headswitch; /* head switch time, usec */ + uint32_t d_trkseek; /* track-to-track seek, usec */ + uint32_t d_flags; /* generic flags */ +#define NDDATA 5 + uint32_t d_drivedata[NDDATA]; /* drive-type specific information */ +#define NSPARE 5 + uint32_t d_spare[NSPARE]; /* reserved for future use */ + uint32_t d_magic2; /* the magic number (again) */ + uint16_t d_checksum; /* xor of data incl. partitions */ + /* filesystem and partition information: */ + uint16_t d_npartitions; /* number of partitions in following */ + uint32_t d_bbsize; /* size of boot area at sn0, bytes */ + uint32_t d_sbsize; /* max size of fs superblock, bytes */ + struct xbsd_partition { /* the partition table */ + uint32_t p_size; /* number of sectors in partition */ + uint32_t p_offset; /* starting sector */ + uint32_t p_fsize; /* filesystem basic fragment size */ + uint8_t p_fstype; /* filesystem type, see below */ + uint8_t p_frag; /* filesystem fragments per block */ + uint16_t p_cpg; /* filesystem cylinders per group */ + } d_partitions[BSD_MAXPARTITIONS]; /* actually may be more */ +}; + +/* d_type values: */ +#define BSD_DTYPE_SMD 1 /* SMD, XSMD; VAX hp/up */ +#define BSD_DTYPE_MSCP 2 /* MSCP */ +#define BSD_DTYPE_DEC 3 /* other DEC (rk, rl) */ +#define BSD_DTYPE_SCSI 4 /* SCSI */ +#define BSD_DTYPE_ESDI 5 /* ESDI interface */ +#define BSD_DTYPE_ST506 6 /* ST506 etc. */ +#define BSD_DTYPE_HPIB 7 /* CS/80 on HP-IB */ +#define BSD_DTYPE_HPFL 8 /* HP Fiber-link */ +#define BSD_DTYPE_FLOPPY 10 /* floppy */ + +/* d_subtype values: */ +#define BSD_DSTYPE_INDOSPART 0x8 /* is inside dos partition */ +#define BSD_DSTYPE_DOSPART(s) ((s) & 3) /* dos partition number */ +#define BSD_DSTYPE_GEOMETRY 0x10 /* drive params in label */ + +static const char * const xbsd_dktypenames[] = { + "unknown", + "SMD", + "MSCP", + "old DEC", + "SCSI", + "ESDI", + "ST506", + "HP-IB", + "HP-FL", + "type 9", + "floppy", + 0 +}; +#define BSD_DKMAXTYPES (sizeof(xbsd_dktypenames) / sizeof(xbsd_dktypenames[0]) - 1) + +/* + * Filesystem type and version. + * Used to interpret other filesystem-specific + * per-partition information. + */ +#define BSD_FS_UNUSED 0 /* unused */ +#define BSD_FS_SWAP 1 /* swap */ +#define BSD_FS_V6 2 /* Sixth Edition */ +#define BSD_FS_V7 3 /* Seventh Edition */ +#define BSD_FS_SYSV 4 /* System V */ +#define BSD_FS_V71K 5 /* V7 with 1K blocks (4.1, 2.9) */ +#define BSD_FS_V8 6 /* Eighth Edition, 4K blocks */ +#define BSD_FS_BSDFFS 7 /* 4.2BSD fast file system */ +#define BSD_FS_BSDLFS 9 /* 4.4BSD log-structured file system */ +#define BSD_FS_OTHER 10 /* in use, but unknown/unsupported */ +#define BSD_FS_HPFS 11 /* OS/2 high-performance file system */ +#define BSD_FS_ISO9660 12 /* ISO-9660 filesystem (cdrom) */ +#define BSD_FS_ISOFS BSD_FS_ISO9660 +#define BSD_FS_BOOT 13 /* partition contains bootstrap */ +#define BSD_FS_ADOS 14 /* AmigaDOS fast file system */ +#define BSD_FS_HFS 15 /* Macintosh HFS */ +#define BSD_FS_ADVFS 16 /* Digital Unix AdvFS */ + +/* this is annoying, but it's also the way it is :-( */ +#ifdef __alpha__ +#define BSD_FS_EXT2 8 /* ext2 file system */ +#else +#define BSD_FS_MSDOS 8 /* MS-DOS file system */ +#endif + +static const struct systypes xbsd_fstypes[] = { + { "\x00" "unused" }, /* BSD_FS_UNUSED */ + { "\x01" "swap" }, /* BSD_FS_SWAP */ + { "\x02" "Version 6" }, /* BSD_FS_V6 */ + { "\x03" "Version 7" }, /* BSD_FS_V7 */ + { "\x04" "System V" }, /* BSD_FS_SYSV */ + { "\x05" "4.1BSD" }, /* BSD_FS_V71K */ + { "\x06" "Eighth Edition" }, /* BSD_FS_V8 */ + { "\x07" "4.2BSD" }, /* BSD_FS_BSDFFS */ +#ifdef __alpha__ + { "\x08" "ext2" }, /* BSD_FS_EXT2 */ +#else + { "\x08" "MS-DOS" }, /* BSD_FS_MSDOS */ +#endif + { "\x09" "4.4LFS" }, /* BSD_FS_BSDLFS */ + { "\x0a" "unknown" }, /* BSD_FS_OTHER */ + { "\x0b" "HPFS" }, /* BSD_FS_HPFS */ + { "\x0c" "ISO-9660" }, /* BSD_FS_ISO9660 */ + { "\x0d" "boot" }, /* BSD_FS_BOOT */ + { "\x0e" "ADOS" }, /* BSD_FS_ADOS */ + { "\x0f" "HFS" }, /* BSD_FS_HFS */ + { "\x10" "AdvFS" }, /* BSD_FS_ADVFS */ + { NULL } +}; +#define BSD_FSMAXTYPES (SIZE(xbsd_fstypes)-1) + + +/* + * flags shared by various drives: + */ +#define BSD_D_REMOVABLE 0x01 /* removable media */ +#define BSD_D_ECC 0x02 /* supports ECC */ +#define BSD_D_BADSECT 0x04 /* supports bad sector forw. */ +#define BSD_D_RAMDISK 0x08 /* disk emulator */ +#define BSD_D_CHAIN 0x10 /* can do back-back transfers */ +#define BSD_D_DOSPART 0x20 /* within MSDOS partition */ + +/* + Changes: + 19990319 - Arnaldo Carvalho de Melo <acme@conectiva.com.br> - i18n/nls + + 20000101 - David Huggins-Daines <dhuggins@linuxcare.com> - Better + support for OSF/1 disklabels on Alpha. + Also fixed unaligned accesses in alpha_bootblock_checksum() +*/ + +static int possibly_osf_label; + +#define FREEBSD_PARTITION 0xa5 +#define NETBSD_PARTITION 0xa9 + +static void xbsd_delete_part(void); +static void xbsd_new_part(void); +static void xbsd_write_disklabel(void); +static int xbsd_create_disklabel(void); +static void xbsd_edit_disklabel(void); +static void xbsd_write_bootstrap(void); +static void xbsd_change_fstype(void); +static int xbsd_get_part_index(int max); +static int xbsd_check_new_partition(int *i); +static void xbsd_list_types(void); +static u_short xbsd_dkcksum(struct xbsd_disklabel *lp); +static int xbsd_initlabel(struct partition *p, struct xbsd_disklabel *d); +static int xbsd_readlabel(struct partition *p, struct xbsd_disklabel *d); +static int xbsd_writelabel(struct partition *p, struct xbsd_disklabel *d); + +#if defined (__alpha__) +static void alpha_bootblock_checksum(char *boot); +#endif + +#if !defined (__alpha__) +static int xbsd_translate_fstype(int linux_type); +static void xbsd_link_part(void); +static struct partition *xbsd_part; +static int xbsd_part_index; +#endif + +#if defined (__alpha__) +/* We access this through a uint64_t * when checksumming */ +static char disklabelbuffer[BSD_BBSIZE] ATTRIBUTE_ALIGNED(8); +#else +static char disklabelbuffer[BSD_BBSIZE]; +#endif + +static struct xbsd_disklabel xbsd_dlabel; + +#define bsd_cround(n) \ + (display_in_cyl_units ? ((n)/xbsd_dlabel.d_secpercyl) + 1 : (n)) + +/* + * Test whether the whole disk has BSD disk label magic. + * + * Note: often reformatting with DOS-type label leaves the BSD magic, + * so this does not mean that there is a BSD disk label. + */ +static int +check_osf_label(void) +{ + if (xbsd_readlabel(NULL, &xbsd_dlabel) == 0) + return 0; + return 1; +} + +static int +btrydev(const char * dev) +{ + if (xbsd_readlabel (NULL, &xbsd_dlabel) == 0) + return -1; + printf(_("\nBSD label for device: %s\n"), dev); + xbsd_print_disklabel (0); + return 0; +} + +static void +bmenu(void) +{ + puts (_("Command action")); + puts (_("\td\tdelete a BSD partition")); + puts (_("\te\tedit drive data")); + puts (_("\ti\tinstall bootstrap")); + puts (_("\tl\tlist known filesystem types")); + puts (_("\tm\tprint this menu")); + puts (_("\tn\tadd a new BSD partition")); + puts (_("\tp\tprint BSD partition table")); + puts (_("\tq\tquit without saving changes")); + puts (_("\tr\treturn to main menu")); + puts (_("\ts\tshow complete disklabel")); + puts (_("\tt\tchange a partition's filesystem id")); + puts (_("\tu\tchange units (cylinders/sectors)")); + puts (_("\tw\twrite disklabel to disk")); +#if !defined (__alpha__) + puts (_("\tx\tlink BSD partition to non-BSD partition")); +#endif +} + +#if !defined (__alpha__) +static int +hidden(int type) +{ + return type ^ 0x10; +} + +static int +is_bsd_partition_type(int type) +{ + return (type == FREEBSD_PARTITION || + type == hidden(FREEBSD_PARTITION) || + type == NETBSD_PARTITION || + type == hidden(NETBSD_PARTITION)); +} +#endif + +static void +bsd_select(void) +{ +#if !defined (__alpha__) + int t, ss; + struct partition *p; + + for (t = 0; t < 4; t++) { + p = get_part_table(t); + if (p && is_bsd_partition_type(p->sys_ind)) { + xbsd_part = p; + xbsd_part_index = t; + ss = get_start_sect(xbsd_part); + if (ss == 0) { + fprintf(stderr, _("Partition %s has invalid starting sector 0.\n"), + partname(disk_device, t+1, 0)); + return; + } + printf(_("Reading disklabel of %s at sector %d.\n"), + partname(disk_device, t+1, 0), ss + BSD_LABELSECTOR); + if (xbsd_readlabel(xbsd_part, &xbsd_dlabel) == 0) + if (xbsd_create_disklabel() == 0) + return; + break; + } + } + + if (t == 4) { + printf(_("There is no *BSD partition on %s.\n"), disk_device); + return; + } + +#elif defined (__alpha__) + + if (xbsd_readlabel(NULL, &xbsd_dlabel) == 0) + if (xbsd_create_disklabel() == 0) + exit (EXIT_SUCCESS); + +#endif + + while (1) { + putchar('\n'); + switch (tolower(read_nonempty(_("BSD disklabel command (m for help): ")))) { + case 'd': + xbsd_delete_part(); + break; + case 'e': + xbsd_edit_disklabel(); + break; + case 'i': + xbsd_write_bootstrap(); + break; + case 'l': + xbsd_list_types(); + break; + case 'n': + xbsd_new_part(); + break; + case 'p': + xbsd_print_disklabel(0); + break; + case 'q': + close(fd); + exit(EXIT_SUCCESS); + case 'r': + return; + case 's': + xbsd_print_disklabel(1); + break; + case 't': + xbsd_change_fstype(); + break; + case 'u': + change_units(); + break; + case 'w': + xbsd_write_disklabel(); + break; +#if !defined (__alpha__) + case 'x': + xbsd_link_part(); + break; +#endif + default: + bmenu(); + break; + } + } +} + +static void +xbsd_delete_part(void) +{ + int i; + + i = xbsd_get_part_index(xbsd_dlabel.d_npartitions); + xbsd_dlabel.d_partitions[i].p_size = 0; + xbsd_dlabel.d_partitions[i].p_offset = 0; + xbsd_dlabel.d_partitions[i].p_fstype = BSD_FS_UNUSED; + if (xbsd_dlabel.d_npartitions == i + 1) + while (xbsd_dlabel.d_partitions[xbsd_dlabel.d_npartitions-1].p_size == 0) + xbsd_dlabel.d_npartitions--; +} + +static void +xbsd_new_part(void) +{ + off_t begin, end; + char mesg[256]; + int i; + + if (!xbsd_check_new_partition(&i)) + return; + +#if !defined (__alpha__) && !defined (__powerpc__) && !defined (__hppa__) + begin = get_start_sect(xbsd_part); + end = begin + get_nr_sects(xbsd_part) - 1; +#else + begin = 0; + end = xbsd_dlabel.d_secperunit - 1; +#endif + + snprintf(mesg, sizeof(mesg), _("First %s"), str_units(SINGULAR)); + begin = read_int(bsd_cround(begin), bsd_cround(begin), bsd_cround(end), + 0, mesg); + + if (display_in_cyl_units) + begin = (begin - 1) * xbsd_dlabel.d_secpercyl; + + snprintf(mesg, sizeof(mesg), _("Last %s or +size or +sizeM or +sizeK"), + str_units(SINGULAR)); + end = read_int(bsd_cround (begin), bsd_cround (end), bsd_cround (end), + bsd_cround (begin), mesg); + + if (display_in_cyl_units) + end = end * xbsd_dlabel.d_secpercyl - 1; + + xbsd_dlabel.d_partitions[i].p_size = end - begin + 1; + xbsd_dlabel.d_partitions[i].p_offset = begin; + xbsd_dlabel.d_partitions[i].p_fstype = BSD_FS_UNUSED; +} + +static void +xbsd_print_disklabel(int show_all) +{ + struct xbsd_disklabel *lp = &xbsd_dlabel; + struct xbsd_partition *pp; + int i, j; + + if (show_all) { +#if defined (__alpha__) + printf("# %s:\n", disk_device); +#else + printf("# %s:\n", partname(disk_device, xbsd_part_index+1, 0)); +#endif + if ((unsigned) lp->d_type < BSD_DKMAXTYPES) + printf(_("type: %s\n"), xbsd_dktypenames[lp->d_type]); + else + printf(_("type: %d\n"), lp->d_type); + printf(_("disk: %.*s\n"), (int) sizeof(lp->d_typename), lp->d_typename); + printf(_("label: %.*s\n"), (int) sizeof(lp->d_packname), lp->d_packname); + printf(_("flags:")); + if (lp->d_flags & BSD_D_REMOVABLE) + printf(_(" removable")); + if (lp->d_flags & BSD_D_ECC) + printf(_(" ecc")); + if (lp->d_flags & BSD_D_BADSECT) + printf(_(" badsect")); + puts(""); + /* On various machines the fields of *lp are short/int/long */ + /* In order to avoid problems, we cast them all to long. */ + printf(_("bytes/sector: %ld\n"), (long) lp->d_secsize); + printf(_("sectors/track: %ld\n"), (long) lp->d_nsectors); + printf(_("tracks/cylinder: %ld\n"), (long) lp->d_ntracks); + printf(_("sectors/cylinder: %ld\n"), (long) lp->d_secpercyl); + printf(_("cylinders: %ld\n"), (long) lp->d_ncylinders); + printf(_("rpm: %d\n"), lp->d_rpm); + printf(_("interleave: %d\n"), lp->d_interleave); + printf(_("trackskew: %d\n"), lp->d_trackskew); + printf(_("cylinderskew: %d\n"), lp->d_cylskew); + printf(_("headswitch: %ld\t\t# milliseconds\n"), + (long) lp->d_headswitch); + printf(_("track-to-track seek: %ld\t# milliseconds\n"), + (long) lp->d_trkseek); + printf(_("drivedata: ")); + for (i = NDDATA - 1; i >= 0; i--) + if (lp->d_drivedata[i]) + break; + if (i < 0) + i = 0; + for (j = 0; j <= i; j++) + printf("%ld ", (long) lp->d_drivedata[j]); + } + printf(_("\n%d partitions:\n"), lp->d_npartitions); + printf(_("# start end size fstype [fsize bsize cpg]\n")); + pp = lp->d_partitions; + for (i = 0; i < lp->d_npartitions; i++, pp++) { + if (pp->p_size) { + if (display_in_cyl_units && lp->d_secpercyl) { + printf(" %c: %8ld%c %8ld%c %8ld%c ", + 'a' + i, + (long) pp->p_offset / lp->d_secpercyl + 1, + (pp->p_offset % lp->d_secpercyl) ? '*' : ' ', + (long) (pp->p_offset + pp->p_size + lp->d_secpercyl - 1) / lp->d_secpercyl, + ((pp->p_offset + pp->p_size) % lp->d_secpercyl) ? '*' : ' ', + (long) pp->p_size / lp->d_secpercyl, + (pp->p_size % lp->d_secpercyl) ? '*' : ' ' + ); + } else { + printf(" %c: %8ld %8ld %8ld ", + 'a' + i, + (long) pp->p_offset, + (long) pp->p_offset + pp->p_size - 1, + (long) pp->p_size + ); + } + + if ((unsigned) pp->p_fstype < BSD_FSMAXTYPES) + printf("%8.8s", xbsd_fstypes[pp->p_fstype].name); + else + printf("%8x", pp->p_fstype); + + switch (pp->p_fstype) { + case BSD_FS_UNUSED: + printf(" %5ld %5ld %5.5s ", + (long) pp->p_fsize, (long) pp->p_fsize * pp->p_frag, ""); + break; + case BSD_FS_BSDFFS: + printf(" %5ld %5ld %5d ", + (long) pp->p_fsize, (long) pp->p_fsize * pp->p_frag, pp->p_cpg); + break; + default: + printf("%22.22s", ""); + break; + } + puts(""); + } + } +} + +static void +xbsd_write_disklabel(void) +{ +#if defined (__alpha__) + printf(_("Writing disklabel to %s.\n"), disk_device); + xbsd_writelabel(NULL, &xbsd_dlabel); +#else + printf(_("Writing disklabel to %s.\n"), + partname(disk_device, xbsd_part_index + 1, 0)); + xbsd_writelabel(xbsd_part, &xbsd_dlabel); +#endif + reread_partition_table(0); /* no exit yet */ +} + +static int +xbsd_create_disklabel(void) +{ + char c; + +#if defined (__alpha__) + fprintf(stderr, _("%s contains no disklabel.\n"), disk_device); +#else + fprintf(stderr, _("%s contains no disklabel.\n"), + partname(disk_device, xbsd_part_index + 1, 0)); +#endif + + while (1) { + c = read_nonempty(_("Do you want to create a disklabel? (y/n) ")); + if (c == 'y' || c == 'Y') { + if (xbsd_initlabel( +#if defined (__alpha__) || defined (__powerpc__) || defined (__hppa__) || \ + defined (__s390__) || defined (__s390x__) + NULL, &xbsd_dlabel +#else + xbsd_part, &xbsd_dlabel/* not used, xbsd_part_index*/ +#endif + ) == 1) { + xbsd_print_disklabel (1); + return 1; + } else + return 0; + } else if (c == 'n') + return 0; + } +} + +static int +edit_int(int def, char *mesg) +{ + do { + fputs(mesg, stdout); + printf(" (%d): ", def); + if (!read_line()) + return def; + } while (!isdigit(*line_ptr)); + return atoi(line_ptr); +} + +static void +xbsd_edit_disklabel(void) +{ + struct xbsd_disklabel *d; + + d = &xbsd_dlabel; + +#if defined (__alpha__) || defined (__ia64__) + d->d_secsize = (u_long) edit_int((u_long) d->d_secsize ,_("bytes/sector")); + d->d_nsectors = (u_long) edit_int((u_long) d->d_nsectors ,_("sectors/track")); + d->d_ntracks = (u_long) edit_int((u_long) d->d_ntracks ,_("tracks/cylinder")); + d->d_ncylinders = (u_long) edit_int((u_long) d->d_ncylinders ,_("cylinders")); +#endif + + /* d->d_secpercyl can be != d->d_nsectors * d->d_ntracks */ + while (1) { + d->d_secpercyl = (u_long) edit_int((u_long) d->d_nsectors * d->d_ntracks, + _("sectors/cylinder")); + if (d->d_secpercyl <= d->d_nsectors * d->d_ntracks) + break; + + printf(_("Must be <= sectors/track * tracks/cylinder (default).\n")); + } + d->d_rpm = (u_short) edit_int((u_short) d->d_rpm ,_("rpm")); + d->d_interleave = (u_short) edit_int((u_short) d->d_interleave,_("interleave")); + d->d_trackskew = (u_short) edit_int((u_short) d->d_trackskew ,_("trackskew")); + d->d_cylskew = (u_short) edit_int((u_short) d->d_cylskew ,_("cylinderskew")); + d->d_headswitch = (u_long) edit_int((u_long) d->d_headswitch ,_("headswitch")); + d->d_trkseek = (u_long) edit_int((u_long) d->d_trkseek ,_("track-to-track seek")); + + d->d_secperunit = d->d_secpercyl * d->d_ncylinders; +} + +static int +xbsd_get_bootstrap (char *path, void *ptr, int size) +{ + int fdb; + + if ((fdb = open (path, O_RDONLY)) < 0) { + perror(path); + return 0; + } + if (read(fdb, ptr, size) < 0) { + perror(path); + close(fdb); + return 0; + } + printf(" ... %s\n", path); + close(fdb); + return 1; +} + +static void +sync_disks(void) +{ + printf(_("\nSyncing disks.\n")); + sync(); + sleep(4); /* What? */ +} + +static void +xbsd_write_bootstrap(void) +{ + char *bootdir = BSD_LINUX_BOOTDIR; + char path[MAXPATHLEN]; + char *dkbasename; + struct xbsd_disklabel dl; + char *d, *p, *e; + int sector; + + if (xbsd_dlabel.d_type == BSD_DTYPE_SCSI) + dkbasename = "sd"; + else + dkbasename = "wd"; + + printf(_("Bootstrap: %sboot -> boot%s (%s): "), + dkbasename, dkbasename, dkbasename); + if (read_line()) { + line_ptr[strlen(line_ptr)-1] = '\0'; + dkbasename = line_ptr; + } + snprintf(path, sizeof(path), "%s/%sboot", bootdir, dkbasename); + if (!xbsd_get_bootstrap(path, disklabelbuffer, (int) xbsd_dlabel.d_secsize)) + return; + +/* We need a backup of the disklabel (xbsd_dlabel might have changed). */ + d = &disklabelbuffer[BSD_LABELSECTOR * SECTOR_SIZE]; + memmove(&dl, d, sizeof(struct xbsd_disklabel)); + +/* The disklabel will be overwritten by 0's from bootxx anyway */ + memset(d, 0, sizeof(struct xbsd_disklabel)); + + snprintf(path, sizeof(path), "%s/boot%s", bootdir, dkbasename); + if (!xbsd_get_bootstrap(path, &disklabelbuffer[xbsd_dlabel.d_secsize], + (int) xbsd_dlabel.d_bbsize - xbsd_dlabel.d_secsize)) + return; + + e = d + sizeof(struct xbsd_disklabel); + for (p = d; p < e; p++) + if (*p) { + fprintf(stderr, _("Bootstrap overlaps with disk label!\n")); + exit(EXIT_FAILURE); + } + + memmove(d, &dl, sizeof(struct xbsd_disklabel)); + +#if defined (__powerpc__) || defined (__hppa__) + sector = 0; +#elif defined (__alpha__) + sector = 0; + alpha_bootblock_checksum(disklabelbuffer); +#else + sector = get_start_sect(xbsd_part); +#endif + + if (lseek(fd, sector * SECTOR_SIZE, SEEK_SET) == -1) + fdisk_fatal(unable_to_seek); + if (BSD_BBSIZE != write(fd, disklabelbuffer, BSD_BBSIZE)) + fdisk_fatal(unable_to_write); + +#if defined (__alpha__) + printf(_("Bootstrap installed on %s.\n"), disk_device); +#else + printf(_("Bootstrap installed on %s.\n"), + partname (disk_device, xbsd_part_index+1, 0)); +#endif + + sync_disks(); +} + +static void +xbsd_change_fstype(void) +{ + int i; + + i = xbsd_get_part_index(xbsd_dlabel.d_npartitions); + xbsd_dlabel.d_partitions[i].p_fstype = read_hex(xbsd_fstypes); +} + +static int +xbsd_get_part_index(int max) +{ + char prompt[256]; + char l; + + snprintf(prompt, sizeof(prompt), _("Partition (a-%c): "), 'a' + max - 1); + do + l = tolower(read_nonempty(prompt)); + while (l < 'a' || l > 'a' + max - 1); + return l - 'a'; +} + +static int +xbsd_check_new_partition(int *i) +{ + /* room for more? various BSD flavours have different maxima */ + if (xbsd_dlabel.d_npartitions == BSD_MAXPARTITIONS) { + int t; + + for (t = 0; t < BSD_MAXPARTITIONS; t++) + if (xbsd_dlabel.d_partitions[t].p_size == 0) + break; + + if (t == BSD_MAXPARTITIONS) { + fprintf(stderr, _("The maximum number of partitions " + "has been created\n")); + return 0; + } + } + + *i = xbsd_get_part_index (BSD_MAXPARTITIONS); + + if (*i >= xbsd_dlabel.d_npartitions) + xbsd_dlabel.d_npartitions = (*i) + 1; + + if (xbsd_dlabel.d_partitions[*i].p_size != 0) { + fprintf(stderr, _("This partition already exists.\n")); + return 0; + } + + return 1; +} + +static void +xbsd_list_types(void) +{ + list_types(xbsd_fstypes); +} + +static u_short +xbsd_dkcksum(struct xbsd_disklabel *lp) +{ + u_short *start, *end; + u_short sum = 0; + + start = (u_short *) lp; + end = (u_short *) &lp->d_partitions[lp->d_npartitions]; + while (start < end) + sum ^= *start++; + return sum; +} + +static int +xbsd_initlabel(struct partition *p, struct xbsd_disklabel *d) +{ + struct xbsd_partition *pp; + + get_geometry(); + memset(d, 0, sizeof(struct xbsd_disklabel)); + + d->d_magic = BSD_DISKMAGIC; + + if (strncmp(disk_device, "/dev/sd", 7) == 0) + d->d_type = BSD_DTYPE_SCSI; + else + d->d_type = BSD_DTYPE_ST506; + +#if !defined (__alpha__) + d->d_flags = BSD_D_DOSPART; +#else + d->d_flags = 0; +#endif + d->d_secsize = SECTOR_SIZE; /* bytes/sector */ + d->d_nsectors = sectors; /* sectors/track */ + d->d_ntracks = heads; /* tracks/cylinder (heads) */ + d->d_ncylinders = cylinders; + d->d_secpercyl = sectors * heads;/* sectors/cylinder */ + if (d->d_secpercyl == 0) + d->d_secpercyl = 1; /* avoid segfaults */ + d->d_secperunit = d->d_secpercyl * d->d_ncylinders; + + d->d_rpm = 3600; + d->d_interleave = 1; + d->d_trackskew = 0; + d->d_cylskew = 0; + d->d_headswitch = 0; + d->d_trkseek = 0; + + d->d_magic2 = BSD_DISKMAGIC; + d->d_bbsize = BSD_BBSIZE; + d->d_sbsize = BSD_SBSIZE; + +#if !defined (__alpha__) + d->d_npartitions = 4; + pp = &d->d_partitions[2]; /* Partition C should be + the NetBSD partition */ + pp->p_offset = get_start_sect(p); + pp->p_size = get_nr_sects(p); + pp->p_fstype = BSD_FS_UNUSED; + pp = &d->d_partitions[3]; /* Partition D should be + the whole disk */ + pp->p_offset = 0; + pp->p_size = d->d_secperunit; + pp->p_fstype = BSD_FS_UNUSED; +#elif defined (__alpha__) + d->d_npartitions = 3; + pp = &d->d_partitions[2]; /* Partition C should be + the whole disk */ + pp->p_offset = 0; + pp->p_size = d->d_secperunit; + pp->p_fstype = BSD_FS_UNUSED; +#endif + + return 1; +} + +/* + * Read a xbsd_disklabel from sector 0 or from the starting sector of p. + * If it has the right magic, return 1. + */ +static int +xbsd_readlabel (struct partition *p, struct xbsd_disklabel *d) +{ + int t, sector; + + /* p is used only to get the starting sector */ +#if !defined (__alpha__) + sector = (p ? get_start_sect(p) : 0); +#elif defined (__alpha__) + sector = 0; +#endif + + if (lseek(fd, sector * SECTOR_SIZE, SEEK_SET) == -1) + fdisk_fatal(unable_to_seek); + if (BSD_BBSIZE != read(fd, disklabelbuffer, BSD_BBSIZE)) + fdisk_fatal(unable_to_read); + + memmove(d, &disklabelbuffer[BSD_LABELSECTOR * SECTOR_SIZE + BSD_LABELOFFSET], + sizeof(struct xbsd_disklabel)); + + if (d->d_magic != BSD_DISKMAGIC || d->d_magic2 != BSD_DISKMAGIC) + return 0; + + for (t = d->d_npartitions; t < BSD_MAXPARTITIONS; t++) { + d->d_partitions[t].p_size = 0; + d->d_partitions[t].p_offset = 0; + d->d_partitions[t].p_fstype = BSD_FS_UNUSED; + } + + if (d->d_npartitions > BSD_MAXPARTITIONS) + fprintf(stderr, _("Warning: too many partitions " + "(%d, maximum is %d).\n"), + d->d_npartitions, BSD_MAXPARTITIONS); + return 1; +} + +static int +xbsd_writelabel (struct partition *p, struct xbsd_disklabel *d) +{ + unsigned int sector; + +#if !defined (__alpha__) && !defined (__powerpc__) && !defined (__hppa__) + sector = get_start_sect(p) + BSD_LABELSECTOR; +#else + sector = BSD_LABELSECTOR; +#endif + + d->d_checksum = 0; + d->d_checksum = xbsd_dkcksum (d); + + /* This is necessary if we want to write the bootstrap later, + otherwise we'd write the old disklabel with the bootstrap. + */ + memmove(&disklabelbuffer[BSD_LABELSECTOR * SECTOR_SIZE + BSD_LABELOFFSET], + d, sizeof(struct xbsd_disklabel)); + +#if defined (__alpha__) && BSD_LABELSECTOR == 0 + alpha_bootblock_checksum(disklabelbuffer); + if (lseek(fd, 0, SEEK_SET) == -1) + fdisk_fatal(unable_to_seek); + if (BSD_BBSIZE != write(fd, disklabelbuffer, BSD_BBSIZE)) + fdisk_fatal(unable_to_write); +#else + if (lseek(fd, sector * SECTOR_SIZE + BSD_LABELOFFSET, SEEK_SET) == -1) + fdisk_fatal(unable_to_seek); + if (sizeof(struct xbsd_disklabel) != write(fd, d, sizeof(struct xbsd_disklabel))) + fdisk_fatal(unable_to_write); +#endif + sync_disks(); + return 1; +} + + +#if !defined (__alpha__) +static int +xbsd_translate_fstype(int linux_type) +{ + switch (linux_type) { + case 0x01: /* DOS 12-bit FAT */ + case 0x04: /* DOS 16-bit <32M */ + case 0x06: /* DOS 16-bit >=32M */ + case 0xe1: /* DOS access */ + case 0xe3: /* DOS R/O */ + case 0xf2: /* DOS secondary */ + return BSD_FS_MSDOS; + case 0x07: /* OS/2 HPFS */ + return BSD_FS_HPFS; + default: + return BSD_FS_OTHER; + } +} + +static void +xbsd_link_part(void) +{ + int k, i; + struct partition *p; + + k = get_partition(1, partitions); + + if (!xbsd_check_new_partition(&i)) + return; + + p = get_part_table(k); + + xbsd_dlabel.d_partitions[i].p_size = get_nr_sects(p); + xbsd_dlabel.d_partitions[i].p_offset = get_start_sect(p); + xbsd_dlabel.d_partitions[i].p_fstype = xbsd_translate_fstype(p->sys_ind); +} +#endif + +#if defined (__alpha__) + +#if !defined(__GLIBC__) +typedef unsigned long long uint64_t; +#endif + +static void +alpha_bootblock_checksum(char *boot) +{ + uint64_t *dp, sum; + int i; + + dp = (uint64_t *)boot; + sum = 0; + for (i = 0; i < 63; i++) + sum += dp[i]; + dp[63] = sum; +} +#endif /* __alpha__ */ + +#endif /* OSF_LABEL */ diff --git a/util-linux/fdisk_sgi.c b/util-linux/fdisk_sgi.c new file mode 100644 index 000000000..548a70bdc --- /dev/null +++ b/util-linux/fdisk_sgi.c @@ -0,0 +1,885 @@ +#ifdef CONFIG_FEATURE_SGI_LABEL + +/* + * Copyright (C) Andreas Neuper, Sep 1998. + * This file may be modified and redistributed under + * the terms of the GNU Public License. + */ + +struct device_parameter { /* 48 bytes */ + unsigned char skew; + unsigned char gap1; + unsigned char gap2; + unsigned char sparecyl; + unsigned short pcylcount; + unsigned short head_vol0; + unsigned short ntrks; /* tracks in cyl 0 or vol 0 */ + unsigned char cmd_tag_queue_depth; + unsigned char unused0; + unsigned short unused1; + unsigned short nsect; /* sectors/tracks in cyl 0 or vol 0 */ + unsigned short bytes; + unsigned short ilfact; + unsigned int flags; /* controller flags */ + unsigned int datarate; + unsigned int retries_on_error; + unsigned int ms_per_word; + unsigned short xylogics_gap1; + unsigned short xylogics_syncdelay; + unsigned short xylogics_readdelay; + unsigned short xylogics_gap2; + unsigned short xylogics_readgate; + unsigned short xylogics_writecont; +}; + +/* + * controller flags + */ +#define SECTOR_SLIP 0x01 +#define SECTOR_FWD 0x02 +#define TRACK_FWD 0x04 +#define TRACK_MULTIVOL 0x08 +#define IGNORE_ERRORS 0x10 +#define RESEEK 0x20 +#define ENABLE_CMDTAGQ 0x40 + +typedef struct { + unsigned int magic; /* expect SGI_LABEL_MAGIC */ + unsigned short boot_part; /* active boot partition */ + unsigned short swap_part; /* active swap partition */ + unsigned char boot_file[16]; /* name of the bootfile */ + struct device_parameter devparam; /* 1 * 48 bytes */ + struct volume_directory { /* 15 * 16 bytes */ + unsigned char vol_file_name[8]; /* a character array */ + unsigned int vol_file_start; /* number of logical block */ + unsigned int vol_file_size; /* number of bytes */ + } directory[15]; + struct sgi_partinfo { /* 16 * 12 bytes */ + unsigned int num_sectors; /* number of blocks */ + unsigned int start_sector; /* must be cylinder aligned */ + unsigned int id; + } partitions[16]; + unsigned int csum; + unsigned int fillbytes; +} sgi_partition; + +typedef struct { + unsigned int magic; /* looks like a magic number */ + unsigned int a2; + unsigned int a3; + unsigned int a4; + unsigned int b1; + unsigned short b2; + unsigned short b3; + unsigned int c[16]; + unsigned short d[3]; + unsigned char scsi_string[50]; + unsigned char serial[137]; + unsigned short check1816; + unsigned char installer[225]; +} sgiinfo; + +#define SGI_LABEL_MAGIC 0x0be5a941 +#define SGI_LABEL_MAGIC_SWAPPED 0x41a9e50b +#define SGI_INFO_MAGIC 0x00072959 +#define SGI_INFO_MAGIC_SWAPPED 0x59290700 + +#define SGI_SSWAP16(x) (sgi_other_endian ? __swap16(x) : (uint16_t)(x)) +#define SGI_SSWAP32(x) (sgi_other_endian ? __swap32(x) : (uint32_t)(x)) + +#define sgilabel ((sgi_partition *)MBRbuffer) +#define sgiparam (sgilabel->devparam) + +/* + * + * fdisksgilabel.c + * + * Copyright (C) Andreas Neuper, Sep 1998. + * This file may be modified and redistributed under + * the terms of the GNU Public License. + * + * Sat Mar 20 EST 1999 Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * Internationalization + */ + + +static int sgi_other_endian; +static int debug; +static short sgi_volumes = 1; + +/* + * only dealing with free blocks here + */ + +typedef struct { + unsigned int first; + unsigned int last; +} freeblocks; +static freeblocks freelist[17]; /* 16 partitions can produce 17 vacant slots */ + +static void +setfreelist(int i, unsigned int f, unsigned int l) +{ + freelist[i].first = f; + freelist[i].last = l; +} + +static void +add2freelist(unsigned int f, unsigned int l) +{ + int i; + for (i = 0; i < 17 ; i++) + if (freelist[i].last == 0) + break; + setfreelist(i, f, l); +} + +static void +clearfreelist(void) +{ + int i; + + for (i = 0; i < 17 ; i++) + setfreelist(i, 0, 0); +} + +static unsigned int +isinfreelist(unsigned int b) +{ + int i; + + for (i = 0; i < 17 ; i++) + if (freelist[i].first <= b && freelist[i].last >= b) + return freelist[i].last; + return 0; +} + /* return last vacant block of this stride (never 0). */ + /* the '>=' is not quite correct, but simplifies the code */ +/* + * end of free blocks section + */ + +static const struct systypes sgi_sys_types[] = { +/* SGI_VOLHDR */ { "\x00" "SGI volhdr" }, +/* 0x01 */ { "\x01" "SGI trkrepl" }, +/* 0x02 */ { "\x02" "SGI secrepl" }, +/* SGI_SWAP */ { "\x03" "SGI raw" }, +/* 0x04 */ { "\x04" "SGI bsd" }, +/* 0x05 */ { "\x05" "SGI sysv" }, +/* SGI_ENTIRE_DISK */ { "\x06" "SGI volume" }, +/* SGI_EFS */ { "\x07" "SGI efs" }, +/* 0x08 */ { "\x08" "SGI lvol" }, +/* 0x09 */ { "\x09" "SGI rlvol" }, +/* SGI_XFS */ { "\x0a" "SGI xfs" }, +/* SGI_XFSLOG */ { "\x0b" "SGI xfslog" }, +/* SGI_XLV */ { "\x0c" "SGI xlv" }, +/* SGI_XVM */ { "\x0d" "SGI xvm" }, +/* LINUX_SWAP */ { "\x82" "Linux swap" }, +/* LINUX_NATIVE */ { "\x83" "Linux native" }, +/* LINUX_LVM */ { "\x8d" "Linux LVM" }, +/* LINUX_RAID */ { "\xfd" "Linux RAID" }, + { NULL } +}; + + +static int +sgi_get_nsect(void) +{ + return SGI_SSWAP16(sgilabel->devparam.nsect); +} + +static int +sgi_get_ntrks(void) +{ + return SGI_SSWAP16(sgilabel->devparam.ntrks); +} + +static unsigned int +two_s_complement_32bit_sum(unsigned int* base, int size /* in bytes */) +{ + int i = 0; + unsigned int sum = 0; + + size /= sizeof(unsigned int); + for (i = 0; i < size; i++) + sum -= SGI_SSWAP32(base[i]); + return sum; +} + +static int +check_sgi_label(void) +{ + if (sizeof(sgilabel) > 512) { + fprintf(stderr, + _("According to MIPS Computer Systems, Inc the " + "Label must not contain more than 512 bytes\n")); + exit(1); + } + + if (sgilabel->magic != SGI_LABEL_MAGIC + && sgilabel->magic != SGI_LABEL_MAGIC_SWAPPED) { + current_label_type = label_dos; + return 0; + } + + sgi_other_endian = (sgilabel->magic == SGI_LABEL_MAGIC_SWAPPED); + /* + * test for correct checksum + */ + if (two_s_complement_32bit_sum((unsigned int*)sgilabel, + sizeof(*sgilabel))) { + fprintf(stderr, + _("Detected sgi disklabel with wrong checksum.\n")); + } + update_units(); + current_label_type = label_sgi; + partitions = 16; + sgi_volumes = 15; + return 1; +} + +static unsigned int +sgi_get_start_sector(int i) +{ + return SGI_SSWAP32(sgilabel->partitions[i].start_sector); +} + +static unsigned int +sgi_get_num_sectors(int i) +{ + return SGI_SSWAP32(sgilabel->partitions[i].num_sectors); +} + +static int +sgi_get_sysid(int i) +{ + return SGI_SSWAP32(sgilabel->partitions[i].id); +} + +static int +sgi_get_bootpartition(void) +{ + return SGI_SSWAP16(sgilabel->boot_part); +} + +static int +sgi_get_swappartition(void) +{ + return SGI_SSWAP16(sgilabel->swap_part); +} + +static void +sgi_list_table(int xtra) +{ + int i, w, wd; + int kpi = 0; /* kernel partition ID */ + + if(xtra) { + printf(_("\nDisk %s (SGI disk label): %d heads, %d sectors\n" + "%d cylinders, %d physical cylinders\n" + "%d extra sects/cyl, interleave %d:1\n" + "%s\n" + "Units = %s of %d * 512 bytes\n\n"), + disk_device, heads, sectors, cylinders, + SGI_SSWAP16(sgiparam.pcylcount), + SGI_SSWAP16(sgiparam.sparecyl), + SGI_SSWAP16(sgiparam.ilfact), + (char *)sgilabel, + str_units(PLURAL), units_per_sector); + } else { + printf( _("\nDisk %s (SGI disk label): " + "%d heads, %d sectors, %d cylinders\n" + "Units = %s of %d * 512 bytes\n\n"), + disk_device, heads, sectors, cylinders, + str_units(PLURAL), units_per_sector ); + } + + w = strlen(disk_device); + wd = strlen(_("Device")); + if (w < wd) + w = wd; + + printf(_("----- partitions -----\n" + "Pt# %*s Info Start End Sectors Id System\n"), + w + 2, _("Device")); + for (i = 0 ; i < partitions; i++) { + if( sgi_get_num_sectors(i) || debug ) { + uint32_t start = sgi_get_start_sector(i); + uint32_t len = sgi_get_num_sectors(i); + kpi++; /* only count nonempty partitions */ + printf( + "%2d: %s %4s %9ld %9ld %9ld %2x %s\n", +/* fdisk part number */ i+1, +/* device */ partname(disk_device, kpi, w+3), +/* flags */ (sgi_get_swappartition() == i) ? "swap" : +/* flags */ (sgi_get_bootpartition() == i) ? "boot" : " ", +/* start */ (long) scround(start), +/* end */ (long) scround(start+len)-1, +/* no odd flag on end */(long) len, +/* type id */ sgi_get_sysid(i), +/* type name */ partition_type(sgi_get_sysid(i))); + } + } + printf(_("----- Bootinfo -----\nBootfile: %s\n" + "----- Directory Entries -----\n"), + sgilabel->boot_file); + for (i = 0 ; i < sgi_volumes; i++) { + if (sgilabel->directory[i].vol_file_size) { + uint32_t start = SGI_SSWAP32(sgilabel->directory[i].vol_file_start); + uint32_t len = SGI_SSWAP32(sgilabel->directory[i].vol_file_size); + unsigned char *name = sgilabel->directory[i].vol_file_name; + + printf(_("%2d: %-10s sector%5u size%8u\n"), + i, (char*)name, (unsigned int) start, (unsigned int) len); + } + } +} + +static void +sgi_set_bootpartition(int i) +{ + sgilabel->boot_part = SGI_SSWAP16(((short)i)); +} + +static unsigned int +sgi_get_lastblock(void) +{ + return heads * sectors * cylinders; +} + +static void +sgi_set_swappartition(int i) +{ + sgilabel->swap_part = SGI_SSWAP16(((short)i)); +} + +static int +sgi_check_bootfile(const char* aFile) +{ + if (strlen(aFile) < 3) /* "/a\n" is minimum */ { + printf(_("\nInvalid Bootfile!\n" + "\tThe bootfile must be an absolute non-zero pathname,\n" + "\te.g. \"/unix\" or \"/unix.save\".\n")); + return 0; + } else { + if (strlen(aFile) > 16) { + printf(_("\n\tName of Bootfile too long: " + "16 bytes maximum.\n")); + return 0; + } else { + if (aFile[0] != '/') { + printf(_("\n\tBootfile must have a " + "fully qualified pathname.\n")); + return 0; + } + } + } + if (strncmp(aFile, (char*)sgilabel->boot_file, 16)) { + printf(_("\n\tBe aware, that the bootfile is not checked for existence.\n\t" + "SGI's default is \"/unix\" and for backup \"/unix.save\".\n")); + /* filename is correct and did change */ + return 1; + } + return 0; /* filename did not change */ +} + +static const char * +sgi_get_bootfile(void) +{ + return (char*)sgilabel->boot_file; +} + +static void +sgi_set_bootfile(const char* aFile) +{ + int i = 0; + + if (sgi_check_bootfile(aFile)) { + while (i < 16) { + if ((aFile[i] != '\n') /* in principle caught again by next line */ + && (strlen(aFile) > i)) + sgilabel->boot_file[i] = aFile[i]; + else + sgilabel->boot_file[i] = 0; + i++; + } + printf(_("\n\tBootfile is changed to \"%s\".\n"), sgilabel->boot_file); + } +} + +static void +create_sgiinfo(void) +{ + /* I keep SGI's habit to write the sgilabel to the second block */ + sgilabel->directory[0].vol_file_start = SGI_SSWAP32(2); + sgilabel->directory[0].vol_file_size = SGI_SSWAP32(sizeof(sgiinfo)); + strncpy((char*)sgilabel->directory[0].vol_file_name, "sgilabel", 8); +} + +static sgiinfo *fill_sgiinfo(void); + +static void +sgi_write_table(void) +{ + sgilabel->csum = 0; + sgilabel->csum = SGI_SSWAP32(two_s_complement_32bit_sum( + (unsigned int*)sgilabel, sizeof(*sgilabel))); + assert(two_s_complement_32bit_sum( + (unsigned int*)sgilabel, sizeof(*sgilabel)) == 0); + + if (lseek(fd, 0, SEEK_SET) < 0) + fdisk_fatal(unable_to_seek); + if (write(fd, sgilabel, SECTOR_SIZE) != SECTOR_SIZE) + fdisk_fatal(unable_to_write); + if (!strncmp((char*)sgilabel->directory[0].vol_file_name, "sgilabel", 8)) { + /* + * keep this habit of first writing the "sgilabel". + * I never tested whether it works without (AN 981002). + */ + sgiinfo *info = fill_sgiinfo(); + int infostartblock = SGI_SSWAP32(sgilabel->directory[0].vol_file_start); + if (lseek(fd, infostartblock*SECTOR_SIZE, SEEK_SET) < 0) + fdisk_fatal(unable_to_seek); + if (write(fd, info, SECTOR_SIZE) != SECTOR_SIZE) + fdisk_fatal(unable_to_write); + free(info); + } +} + +static int +compare_start(int *x, int *y) +{ + /* + * sort according to start sectors + * and prefers largest partition: + * entry zero is entire disk entry + */ + unsigned int i = *x; + unsigned int j = *y; + unsigned int a = sgi_get_start_sector(i); + unsigned int b = sgi_get_start_sector(j); + unsigned int c = sgi_get_num_sectors(i); + unsigned int d = sgi_get_num_sectors(j); + + if (a == b) + return (d > c) ? 1 : (d == c) ? 0 : -1; + return (a > b) ? 1 : -1; +} + + +static int +verify_sgi(int verbose) +{ + int Index[16]; /* list of valid partitions */ + int sortcount = 0; /* number of used partitions, i.e. non-zero lengths */ + int entire = 0, i = 0; + unsigned int start = 0; + long long gap = 0; /* count unused blocks */ + unsigned int lastblock = sgi_get_lastblock(); + + clearfreelist(); + for (i = 0; i < 16; i++) { + if (sgi_get_num_sectors(i) != 0) { + Index[sortcount++] = i; + if (sgi_get_sysid(i) == SGI_ENTIRE_DISK) { + if (entire++ == 1) { + if (verbose) + printf(_("More than one entire disk entry present.\n")); + } + } + } + } + if (sortcount == 0) { + if (verbose) + printf(_("No partitions defined\n")); + return (lastblock > 0) ? 1 : (lastblock == 0) ? 0 : -1; + } + qsort(Index, sortcount, sizeof(Index[0]), (void*)compare_start); + if (sgi_get_sysid(Index[0]) == SGI_ENTIRE_DISK) { + if ((Index[0] != 10) && verbose) + printf(_("IRIX likes when Partition 11 covers the entire disk.\n")); + if ((sgi_get_start_sector(Index[0]) != 0) && verbose) + printf(_("The entire disk partition should start " + "at block 0,\n" + "not at diskblock %d.\n"), + sgi_get_start_sector(Index[0])); + if (debug) /* I do not understand how some disks fulfil it */ + if ((sgi_get_num_sectors(Index[0]) != lastblock) && verbose) + printf(_("The entire disk partition is only %d diskblock large,\n" + "but the disk is %d diskblocks long.\n"), + sgi_get_num_sectors(Index[0]), lastblock); + lastblock = sgi_get_num_sectors(Index[0]); + } else { + if (verbose) + printf(_("One Partition (#11) should cover the entire disk.\n")); + if (debug > 2) + printf("sysid=%d\tpartition=%d\n", + sgi_get_sysid(Index[0]), Index[0]+1); + } + for (i = 1, start = 0; i < sortcount; i++) { + int cylsize = sgi_get_nsect() * sgi_get_ntrks(); + + if ((sgi_get_start_sector(Index[i]) % cylsize) != 0) { + if (debug) /* I do not understand how some disks fulfil it */ + if (verbose) + printf(_("Partition %d does not start on cylinder boundary.\n"), + Index[i]+1); + } + if (sgi_get_num_sectors(Index[i]) % cylsize != 0) { + if (debug) /* I do not understand how some disks fulfil it */ + if (verbose) + printf(_("Partition %d does not end on cylinder boundary.\n"), + Index[i]+1); + } + /* We cannot handle several "entire disk" entries. */ + if (sgi_get_sysid(Index[i]) == SGI_ENTIRE_DISK) continue; + if (start > sgi_get_start_sector(Index[i])) { + if (verbose) + printf(_("The Partition %d and %d overlap by %d sectors.\n"), + Index[i-1]+1, Index[i]+1, + start - sgi_get_start_sector(Index[i])); + if (gap > 0) gap = -gap; + if (gap == 0) gap = -1; + } + if (start < sgi_get_start_sector(Index[i])) { + if (verbose) + printf(_("Unused gap of %8u sectors - sectors %8u-%u\n"), + sgi_get_start_sector(Index[i]) - start, + start, sgi_get_start_sector(Index[i])-1); + gap += sgi_get_start_sector(Index[i]) - start; + add2freelist(start, sgi_get_start_sector(Index[i])); + } + start = sgi_get_start_sector(Index[i]) + + sgi_get_num_sectors(Index[i]); + if (debug > 1) { + if (verbose) + printf("%2d:%12d\t%12d\t%12d\n", Index[i], + sgi_get_start_sector(Index[i]), + sgi_get_num_sectors(Index[i]), + sgi_get_sysid(Index[i])); + } + } + if (start < lastblock) { + if (verbose) + printf(_("Unused gap of %8u sectors - sectors %8u-%u\n"), + lastblock - start, start, lastblock-1); + gap += lastblock - start; + add2freelist(start, lastblock); + } + /* + * Done with arithmetics + * Go for details now + */ + if (verbose) { + if (!sgi_get_num_sectors(sgi_get_bootpartition())) { + printf(_("\nThe boot partition does not exist.\n")); + } + if (!sgi_get_num_sectors(sgi_get_swappartition())) { + printf(_("\nThe swap partition does not exist.\n")); + } else { + if ((sgi_get_sysid(sgi_get_swappartition()) != SGI_SWAP) + && (sgi_get_sysid(sgi_get_swappartition()) != LINUX_SWAP)) + printf(_("\nThe swap partition has no swap type.\n")); + } + if (sgi_check_bootfile("/unix")) + printf(_("\tYou have chosen an unusual boot file name.\n")); + } + return (gap > 0) ? 1 : (gap == 0) ? 0 : -1; +} + +static int +sgi_gaps(void) +{ + /* + * returned value is: + * = 0 : disk is properly filled to the rim + * < 0 : there is an overlap + * > 0 : there is still some vacant space + */ + return verify_sgi(0); +} + +static void +sgi_change_sysid(int i, int sys) +{ + if( sgi_get_num_sectors(i) == 0 ) { /* caught already before, ... */ + printf(_("Sorry You may change the Tag of non-empty partitions.\n")); + return; + } + if (((sys != SGI_ENTIRE_DISK) && (sys != SGI_VOLHDR)) + && (sgi_get_start_sector(i) < 1) ) { + read_maybe_empty( + _("It is highly recommended that the partition at offset 0\n" + "is of type \"SGI volhdr\", the IRIX system will rely on it to\n" + "retrieve from its directory standalone tools like sash and fx.\n" + "Only the \"SGI volume\" entire disk section may violate this.\n" + "Type YES if you are sure about tagging this partition differently.\n")); + if (strcmp(line_ptr, _("YES\n"))) + return; + } + sgilabel->partitions[i].id = SGI_SSWAP32(sys); +} + +/* returns partition index of first entry marked as entire disk */ +static int +sgi_entire(void) +{ + int i; + + for (i = 0; i < 16; i++) + if (sgi_get_sysid(i) == SGI_VOLUME) + return i; + return -1; +} + +static void +sgi_set_partition(int i, unsigned int start, unsigned int length, int sys) +{ + sgilabel->partitions[i].id = SGI_SSWAP32(sys); + sgilabel->partitions[i].num_sectors = SGI_SSWAP32(length); + sgilabel->partitions[i].start_sector = SGI_SSWAP32(start); + set_changed(i); + if (sgi_gaps() < 0) /* rebuild freelist */ + printf(_("Do You know, You got a partition overlap on the disk?\n")); +} + +static void +sgi_set_entire(void) +{ + int n; + + for (n = 10; n < partitions; n++) { + if(!sgi_get_num_sectors(n) ) { + sgi_set_partition(n, 0, sgi_get_lastblock(), SGI_VOLUME); + break; + } + } +} + +static void +sgi_set_volhdr(void) +{ + int n; + + for (n = 8; n < partitions; n++) { + if (!sgi_get_num_sectors(n)) { + /* + * 5 cylinders is an arbitrary value I like + * IRIX 5.3 stored files in the volume header + * (like sash, symmon, fx, ide) with ca. 3200 + * sectors. + */ + if (heads * sectors * 5 < sgi_get_lastblock()) + sgi_set_partition(n, 0, heads * sectors * 5, SGI_VOLHDR); + break; + } + } +} + +static void +sgi_delete_partition(int i) +{ + sgi_set_partition(i, 0, 0, 0); +} + +static void +sgi_add_partition(int n, int sys) +{ + char mesg[256]; + unsigned int first = 0, last = 0; + + if (n == 10) { + sys = SGI_VOLUME; + } else if (n == 8) { + sys = 0; + } + if(sgi_get_num_sectors(n)) { + printf(_("Partition %d is already defined. Delete " + "it before re-adding it.\n"), n + 1); + return; + } + if ((sgi_entire() == -1) && (sys != SGI_VOLUME)) { + printf(_("Attempting to generate entire disk entry automatically.\n")); + sgi_set_entire(); + sgi_set_volhdr(); + } + if ((sgi_gaps() == 0) && (sys != SGI_VOLUME)) { + printf(_("The entire disk is already covered with partitions.\n")); + return; + } + if (sgi_gaps() < 0) { + printf(_("You got a partition overlap on the disk. Fix it first!\n")); + return; + } + snprintf(mesg, sizeof(mesg), _("First %s"), str_units(SINGULAR)); + while (1) { + if(sys == SGI_VOLUME) { + last = sgi_get_lastblock(); + first = read_int(0, 0, last-1, 0, mesg); + if (first != 0) { + printf(_("It is highly recommended that eleventh partition\n" + "covers the entire disk and is of type 'SGI volume'\n")); + } + } else { + first = freelist[0].first; + last = freelist[0].last; + first = read_int(scround(first), scround(first), scround(last)-1, + 0, mesg); + } + if (display_in_cyl_units) + first *= units_per_sector; + else + first = first; /* align to cylinder if you know how ... */ + if(!last ) + last = isinfreelist(first); + if(last == 0) { + printf(_("You will get a partition overlap on the disk. " + "Fix it first!\n")); + } else + break; + } + snprintf(mesg, sizeof(mesg), _(" Last %s"), str_units(SINGULAR)); + last = read_int(scround(first), scround(last)-1, scround(last)-1, + scround(first), mesg)+1; + if (display_in_cyl_units) + last *= units_per_sector; + else + last = last; /* align to cylinder if You know how ... */ + if ( (sys == SGI_VOLUME) && (first != 0 || last != sgi_get_lastblock() ) ) + printf(_("It is highly recommended that eleventh partition\n" + "covers the entire disk and is of type 'SGI volume'\n")); + sgi_set_partition(n, first, last-first, sys); +} + +#ifdef CONFIG_FEATURE_FDISK_ADVANCED +static void +create_sgilabel(void) +{ + struct hd_geometry geometry; + struct { + unsigned int start; + unsigned int nsect; + int sysid; + } old[4]; + int i = 0; + long longsectors; /* the number of sectors on the device */ + int res; /* the result from the ioctl */ + int sec_fac; /* the sector factor */ + + sec_fac = sector_size / 512; /* determine the sector factor */ + + fprintf( stderr, + _("Building a new SGI disklabel. Changes will remain in memory only,\n" + "until you decide to write them. After that, of course, the previous\n" + "content will be unrecoverably lost.\n\n")); + + sgi_other_endian = (BYTE_ORDER == LITTLE_ENDIAN); + res = ioctl(fd, BLKGETSIZE, &longsectors); + if (!ioctl(fd, HDIO_GETGEO, &geometry)) { + heads = geometry.heads; + sectors = geometry.sectors; + if (res == 0) { + /* the get device size ioctl was successful */ + cylinders = longsectors / (heads * sectors); + cylinders /= sec_fac; + } else { + /* otherwise print error and use truncated version */ + cylinders = geometry.cylinders; + fprintf(stderr, + _("Warning: BLKGETSIZE ioctl failed on %s. " + "Using geometry cylinder value of %d.\n" + "This value may be truncated for devices" + " > 33.8 GB.\n"), disk_device, cylinders); + } + } + for (i = 0; i < 4; i++) { + old[i].sysid = 0; + if (valid_part_table_flag(MBRbuffer)) { + if(get_part_table(i)->sys_ind) { + old[i].sysid = get_part_table(i)->sys_ind; + old[i].start = get_start_sect(get_part_table(i)); + old[i].nsect = get_nr_sects(get_part_table(i)); + printf(_("Trying to keep parameters of partition %d.\n"), i); + if (debug) + printf(_("ID=%02x\tSTART=%d\tLENGTH=%d\n"), + old[i].sysid, old[i].start, old[i].nsect); + } + } + } + + memset(MBRbuffer, 0, sizeof(MBRbuffer)); + sgilabel->magic = SGI_SSWAP32(SGI_LABEL_MAGIC); + sgilabel->boot_part = SGI_SSWAP16(0); + sgilabel->swap_part = SGI_SSWAP16(1); + + /* sizeof(sgilabel->boot_file) = 16 > 6 */ + memset(sgilabel->boot_file, 0, 16); + strcpy((char*)sgilabel->boot_file, "/unix"); + + sgilabel->devparam.skew = (0); + sgilabel->devparam.gap1 = (0); + sgilabel->devparam.gap2 = (0); + sgilabel->devparam.sparecyl = (0); + sgilabel->devparam.pcylcount = SGI_SSWAP16(geometry.cylinders); + sgilabel->devparam.head_vol0 = SGI_SSWAP16(0); + sgilabel->devparam.ntrks = SGI_SSWAP16(geometry.heads); + /* tracks/cylinder (heads) */ + sgilabel->devparam.cmd_tag_queue_depth = (0); + sgilabel->devparam.unused0 = (0); + sgilabel->devparam.unused1 = SGI_SSWAP16(0); + sgilabel->devparam.nsect = SGI_SSWAP16(geometry.sectors); + /* sectors/track */ + sgilabel->devparam.bytes = SGI_SSWAP16(512); + sgilabel->devparam.ilfact = SGI_SSWAP16(1); + sgilabel->devparam.flags = SGI_SSWAP32(TRACK_FWD| + IGNORE_ERRORS|RESEEK); + sgilabel->devparam.datarate = SGI_SSWAP32(0); + sgilabel->devparam.retries_on_error = SGI_SSWAP32(1); + sgilabel->devparam.ms_per_word = SGI_SSWAP32(0); + sgilabel->devparam.xylogics_gap1 = SGI_SSWAP16(0); + sgilabel->devparam.xylogics_syncdelay = SGI_SSWAP16(0); + sgilabel->devparam.xylogics_readdelay = SGI_SSWAP16(0); + sgilabel->devparam.xylogics_gap2 = SGI_SSWAP16(0); + sgilabel->devparam.xylogics_readgate = SGI_SSWAP16(0); + sgilabel->devparam.xylogics_writecont = SGI_SSWAP16(0); + memset( &(sgilabel->directory), 0, sizeof(struct volume_directory)*15 ); + memset( &(sgilabel->partitions), 0, sizeof(struct sgi_partinfo)*16 ); + current_label_type = label_sgi; + partitions = 16; + sgi_volumes = 15; + sgi_set_entire(); + sgi_set_volhdr(); + for (i = 0; i < 4; i++) { + if(old[i].sysid) { + sgi_set_partition(i, old[i].start, old[i].nsect, old[i].sysid); + } + } +} + +static void +sgi_set_xcyl(void) +{ + /* do nothing in the beginning */ +} +#endif /* CONFIG_FEATURE_FDISK_ADVANCED */ + +/* _____________________________________________________________ + */ + +static sgiinfo * +fill_sgiinfo(void) +{ + sgiinfo *info = calloc(1, sizeof(sgiinfo)); + + info->magic = SGI_SSWAP32(SGI_INFO_MAGIC); + info->b1 = SGI_SSWAP32(-1); + info->b2 = SGI_SSWAP16(-1); + info->b3 = SGI_SSWAP16(1); + /* You may want to replace this string !!!!!!! */ + strcpy( (char*)info->scsi_string, "IBM OEM 0662S12 3 30" ); + strcpy( (char*)info->serial, "0000" ); + info->check1816 = SGI_SSWAP16(18*256 +16 ); + strcpy( (char*)info->installer, "Sfx version 5.3, Oct 18, 1994" ); + return info; +} +#endif /* SGI_LABEL */ diff --git a/util-linux/fdisk_sun.c b/util-linux/fdisk_sun.c new file mode 100644 index 000000000..1e8f2e525 --- /dev/null +++ b/util-linux/fdisk_sun.c @@ -0,0 +1,729 @@ +#ifdef CONFIG_FEATURE_SUN_LABEL + +#define SUN_LABEL_MAGIC 0xDABE +#define SUN_LABEL_MAGIC_SWAPPED 0xBEDA +#define SUN_SSWAP16(x) (sun_other_endian ? __swap16(x) : (uint16_t)(x)) +#define SUN_SSWAP32(x) (sun_other_endian ? __swap32(x) : (uint32_t)(x)) + +/* Copied from linux/major.h */ +#define FLOPPY_MAJOR 2 + +#define SCSI_IOCTL_GET_IDLUN 0x5382 + +/* + * fdisksunlabel.c + * + * I think this is mostly, or entirely, due to + * Jakub Jelinek (jj@sunsite.mff.cuni.cz), July 1996 + * + * Merged with fdisk for other architectures, aeb, June 1998. + * + * Sat Mar 20 EST 1999 Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * Internationalization + */ + + +static int sun_other_endian; +static int scsi_disk; +static int floppy; + +#ifndef IDE0_MAJOR +#define IDE0_MAJOR 3 +#endif +#ifndef IDE1_MAJOR +#define IDE1_MAJOR 22 +#endif + +static void +guess_device_type(void) +{ + struct stat bootstat; + + if (fstat(fd, &bootstat) < 0) { + scsi_disk = 0; + floppy = 0; + } else if (S_ISBLK(bootstat.st_mode) + && (major(bootstat.st_rdev) == IDE0_MAJOR || + major(bootstat.st_rdev) == IDE1_MAJOR)) { + scsi_disk = 0; + floppy = 0; + } else if (S_ISBLK(bootstat.st_mode) + && major(bootstat.st_rdev) == FLOPPY_MAJOR) { + scsi_disk = 0; + floppy = 1; + } else { + scsi_disk = 1; + floppy = 0; + } +} + +static const struct systypes sun_sys_types[] = { + { "\x00" "Empty" }, /* 0 */ + { "\x01" "Boot" }, /* 1 */ + { "\x02" "SunOS root" }, /* 2 */ + { "\x03" "SunOS swap" }, /* SUNOS_SWAP */ + { "\x04" "SunOS usr" }, /* 4 */ + { "\x05" "Whole disk" }, /* SUN_WHOLE_DISK */ + { "\x06" "SunOS stand" }, /* 6 */ + { "\x07" "SunOS var" }, /* 7 */ + { "\x08" "SunOS home" }, /* 8 */ + { "\x82" "Linux swap" }, /* LINUX_SWAP */ + { "\x83" "Linux native" }, /* LINUX_NATIVE */ + { "\x8e" "Linux LVM" }, /* 0x8e */ +/* New (2.2.x) raid partition with autodetect using persistent superblock */ + { "\xfd" "Linux raid autodetect" }, /* 0xfd */ + { NULL } +}; + + +static void +set_sun_partition(int i, uint start, uint stop, int sysid) +{ + sunlabel->infos[i].id = sysid; + sunlabel->partitions[i].start_cylinder = + SUN_SSWAP32(start / (heads * sectors)); + sunlabel->partitions[i].num_sectors = + SUN_SSWAP32(stop - start); + set_changed(i); +} + +static int +check_sun_label(void) +{ + unsigned short *ush; + int csum; + + if (sunlabel->magic != SUN_LABEL_MAGIC + && sunlabel->magic != SUN_LABEL_MAGIC_SWAPPED) { + current_label_type = label_dos; + sun_other_endian = 0; + return 0; + } + sun_other_endian = (sunlabel->magic == SUN_LABEL_MAGIC_SWAPPED); + ush = ((unsigned short *) (sunlabel + 1)) - 1; + for (csum = 0; ush >= (unsigned short *)sunlabel;) csum ^= *ush--; + if (csum) { + fprintf(stderr,_("Detected sun disklabel with wrong checksum.\n" + "Probably you'll have to set all the values,\n" + "e.g. heads, sectors, cylinders and partitions\n" + "or force a fresh label (s command in main menu)\n")); + } else { + heads = SUN_SSWAP16(sunlabel->ntrks); + cylinders = SUN_SSWAP16(sunlabel->ncyl); + sectors = SUN_SSWAP16(sunlabel->nsect); + } + update_units(); + current_label_type = label_sun; + partitions = 8; + return 1; +} + +static const struct sun_predefined_drives { + const char *vendor; + const char *model; + unsigned short sparecyl; + unsigned short ncyl; + unsigned short nacyl; + unsigned short pcylcount; + unsigned short ntrks; + unsigned short nsect; + unsigned short rspeed; +} sun_drives[] = { + { "Quantum","ProDrive 80S",1,832,2,834,6,34,3662}, + { "Quantum","ProDrive 105S",1,974,2,1019,6,35,3662}, + { "CDC","Wren IV 94171-344",3,1545,2,1549,9,46,3600}, + { "IBM","DPES-31080",0,4901,2,4903,4,108,5400}, + { "IBM","DORS-32160",0,1015,2,1017,67,62,5400}, + { "IBM","DNES-318350",0,11199,2,11474,10,320,7200}, + { "SEAGATE","ST34371",0,3880,2,3882,16,135,7228}, + { "","SUN0104",1,974,2,1019,6,35,3662}, + { "","SUN0207",4,1254,2,1272,9,36,3600}, + { "","SUN0327",3,1545,2,1549,9,46,3600}, + { "","SUN0340",0,1538,2,1544,6,72,4200}, + { "","SUN0424",2,1151,2,2500,9,80,4400}, + { "","SUN0535",0,1866,2,2500,7,80,5400}, + { "","SUN0669",5,1614,2,1632,15,54,3600}, + { "","SUN1.0G",5,1703,2,1931,15,80,3597}, + { "","SUN1.05",0,2036,2,2038,14,72,5400}, + { "","SUN1.3G",6,1965,2,3500,17,80,5400}, + { "","SUN2.1G",0,2733,2,3500,19,80,5400}, + { "IOMEGA","Jaz",0,1019,2,1021,64,32,5394}, +}; + +static const struct sun_predefined_drives * +sun_autoconfigure_scsi(void) +{ + const struct sun_predefined_drives *p = NULL; + +#ifdef SCSI_IOCTL_GET_IDLUN + unsigned int id[2]; + char buffer[2048]; + char buffer2[2048]; + FILE *pfd; + char *vendor; + char *model; + char *q; + int i; + + if (ioctl(fd, SCSI_IOCTL_GET_IDLUN, &id)) + return NULL; + + sprintf(buffer, + "Host: scsi%d Channel: %02d Id: %02d Lun: %02d\n", + /* This is very wrong (works only if you have one HBA), + but I haven't found a way how to get hostno + from the current kernel */ + 0, + (id[0]>>16) & 0xff, + id[0] & 0xff, + (id[0]>>8) & 0xff + ); + pfd = fopen("/proc/scsi/scsi", "r"); + if (!pfd) { + return NULL; + } + while (fgets(buffer2, 2048, pfd)) { + if (strcmp(buffer, buffer2)) + continue; + if (!fgets(buffer2, 2048, pfd)) + break; + q = strstr(buffer2, "Vendor: "); + if (!q) + break; + q += 8; + vendor = q; + q = strstr(q, " "); + *q++ = '\0'; /* truncate vendor name */ + q = strstr(q, "Model: "); + if (!q) + break; + *q = '\0'; + q += 7; + model = q; + q = strstr(q, " Rev: "); + if (!q) + break; + *q = '\0'; + for (i = 0; i < SIZE(sun_drives); i++) { + if (*sun_drives[i].vendor && strcasecmp(sun_drives[i].vendor, vendor)) + continue; + if (!strstr(model, sun_drives[i].model)) + continue; + printf(_("Autoconfigure found a %s%s%s\n"), + sun_drives[i].vendor, + (*sun_drives[i].vendor) ? " " : "", + sun_drives[i].model); + p = sun_drives + i; + break; + } + break; + } + fclose(pfd); +#endif + return p; +} + +static void +create_sunlabel(void) +{ + struct hd_geometry geometry; + unsigned int ndiv; + int i; + unsigned char c; + const struct sun_predefined_drives *p = NULL; + + fprintf(stderr, + _("Building a new sun disklabel. Changes will remain in memory only,\n" + "until you decide to write them. After that, of course, the previous\n" + "content won't be recoverable.\n\n")); + sun_other_endian = BB_LITTLE_ENDIAN; + memset(MBRbuffer, 0, sizeof(MBRbuffer)); + sunlabel->magic = SUN_SSWAP16(SUN_LABEL_MAGIC); + if (!floppy) { + puts(_("Drive type\n" + " ? auto configure\n" + " 0 custom (with hardware detected defaults)")); + for (i = 0; i < SIZE(sun_drives); i++) { + printf(" %c %s%s%s\n", + i + 'a', sun_drives[i].vendor, + (*sun_drives[i].vendor) ? " " : "", + sun_drives[i].model); + } + while (1) { + c = read_nonempty(_("Select type (? for auto, 0 for custom): ")); + if (c >= 'a' && c < 'a' + SIZE(sun_drives)) { + p = sun_drives + c - 'a'; + break; + } else if (c >= 'A' && c < 'A' + SIZE(sun_drives)) { + p = sun_drives + c - 'A'; + break; + } else if (c == '0') { + break; + } else if (c == '?' && scsi_disk) { + p = sun_autoconfigure_scsi(); + if (!p) + printf(_("Autoconfigure failed.\n")); + else + break; + } + } + } + if (!p || floppy) { + if (!ioctl(fd, HDIO_GETGEO, &geometry)) { + heads = geometry.heads; + sectors = geometry.sectors; + cylinders = geometry.cylinders; + } else { + heads = 0; + sectors = 0; + cylinders = 0; + } + if (floppy) { + sunlabel->nacyl = 0; + sunlabel->pcylcount = SUN_SSWAP16(cylinders); + sunlabel->rspeed = SUN_SSWAP16(300); + sunlabel->ilfact = SUN_SSWAP16(1); + sunlabel->sparecyl = 0; + } else { + heads = read_int(1,heads,1024,0,_("Heads")); + sectors = read_int(1,sectors,1024,0,_("Sectors/track")); + if (cylinders) + cylinders = read_int(1,cylinders-2,65535,0,_("Cylinders")); + else + cylinders = read_int(1,0,65535,0,_("Cylinders")); + sunlabel->nacyl = SUN_SSWAP16(read_int(0,2,65535,0, _("Alternate cylinders"))); + sunlabel->pcylcount = SUN_SSWAP16(read_int(0,cylinders+SUN_SSWAP16(sunlabel->nacyl), 65535,0, _("Physical cylinders"))); + sunlabel->rspeed = SUN_SSWAP16(read_int(1,5400,100000,0, _("Rotation speed (rpm)"))); + sunlabel->ilfact = SUN_SSWAP16(read_int(1,1,32,0, _("Interleave factor"))); + sunlabel->sparecyl = SUN_SSWAP16(read_int(0,0,sectors,0, _("Extra sectors per cylinder"))); + } + } else { + sunlabel->sparecyl = SUN_SSWAP16(p->sparecyl); + sunlabel->ncyl = SUN_SSWAP16(p->ncyl); + sunlabel->nacyl = SUN_SSWAP16(p->nacyl); + sunlabel->pcylcount = SUN_SSWAP16(p->pcylcount); + sunlabel->ntrks = SUN_SSWAP16(p->ntrks); + sunlabel->nsect = SUN_SSWAP16(p->nsect); + sunlabel->rspeed = SUN_SSWAP16(p->rspeed); + sunlabel->ilfact = SUN_SSWAP16(1); + cylinders = p->ncyl; + heads = p->ntrks; + sectors = p->nsect; + puts(_("You may change all the disk params from the x menu")); + } + + snprintf((char *)(sunlabel->info), sizeof(sunlabel->info), + "%s%s%s cyl %d alt %d hd %d sec %d", + p ? p->vendor : "", (p && *p->vendor) ? " " : "", + p ? p->model : (floppy ? _("3,5\" floppy") : _("Linux custom")), + cylinders, SUN_SSWAP16(sunlabel->nacyl), heads, sectors); + + sunlabel->ntrks = SUN_SSWAP16(heads); + sunlabel->nsect = SUN_SSWAP16(sectors); + sunlabel->ncyl = SUN_SSWAP16(cylinders); + if (floppy) + set_sun_partition(0, 0, cylinders * heads * sectors, LINUX_NATIVE); + else { + if (cylinders * heads * sectors >= 150 * 2048) { + ndiv = cylinders - (50 * 2048 / (heads * sectors)); /* 50M swap */ + } else + ndiv = cylinders * 2 / 3; + set_sun_partition(0, 0, ndiv * heads * sectors, LINUX_NATIVE); + set_sun_partition(1, ndiv * heads * sectors, cylinders * heads * sectors, LINUX_SWAP); + sunlabel->infos[1].flags |= 0x01; /* Not mountable */ + } + set_sun_partition(2, 0, cylinders * heads * sectors, SUN_WHOLE_DISK); + { + unsigned short *ush = (unsigned short *)sunlabel; + unsigned short csum = 0; + while (ush < (unsigned short *)(&sunlabel->csum)) + csum ^= *ush++; + sunlabel->csum = csum; + } + + set_all_unchanged(); + set_changed(0); + get_boot(create_empty_sun); +} + +static void +toggle_sunflags(int i, unsigned char mask) +{ + if (sunlabel->infos[i].flags & mask) + sunlabel->infos[i].flags &= ~mask; + else + sunlabel->infos[i].flags |= mask; + set_changed(i); +} + +static void +fetch_sun(uint *starts, uint *lens, uint *start, uint *stop) +{ + int i, continuous = 1; + + *start = 0; + *stop = cylinders * heads * sectors; + for (i = 0; i < partitions; i++) { + if (sunlabel->partitions[i].num_sectors + && sunlabel->infos[i].id + && sunlabel->infos[i].id != SUN_WHOLE_DISK) { + starts[i] = SUN_SSWAP32(sunlabel->partitions[i].start_cylinder) * heads * sectors; + lens[i] = SUN_SSWAP32(sunlabel->partitions[i].num_sectors); + if (continuous) { + if (starts[i] == *start) + *start += lens[i]; + else if (starts[i] + lens[i] >= *stop) + *stop = starts[i]; + else + continuous = 0; + /* There will be probably more gaps + than one, so lets check afterwards */ + } + } else { + starts[i] = 0; + lens[i] = 0; + } + } +} + +static uint *verify_sun_starts; + +static int +verify_sun_cmp(int *a, int *b) +{ + if (*a == -1) return 1; + if (*b == -1) return -1; + if (verify_sun_starts[*a] > verify_sun_starts[*b]) return 1; + return -1; +} + +static void +verify_sun(void) +{ + uint starts[8], lens[8], start, stop; + int i,j,k,starto,endo; + int array[8]; + + verify_sun_starts = starts; + fetch_sun(starts,lens,&start,&stop); + for (k = 0; k < 7; k++) { + for (i = 0; i < 8; i++) { + if (k && (lens[i] % (heads * sectors))) { + printf(_("Partition %d doesn't end on cylinder boundary\n"), i+1); + } + if (lens[i]) { + for (j = 0; j < i; j++) + if (lens[j]) { + if (starts[j] == starts[i]+lens[i]) { + starts[j] = starts[i]; lens[j] += lens[i]; + lens[i] = 0; + } else if (starts[i] == starts[j]+lens[j]){ + lens[j] += lens[i]; + lens[i] = 0; + } else if (!k) { + if (starts[i] < starts[j]+lens[j] + && starts[j] < starts[i]+lens[i]) { + starto = starts[i]; + if (starts[j] > starto) + starto = starts[j]; + endo = starts[i]+lens[i]; + if (starts[j]+lens[j] < endo) + endo = starts[j]+lens[j]; + printf(_("Partition %d overlaps with others in " + "sectors %d-%d\n"), i+1, starto, endo); + } + } + } + } + } + } + for (i = 0; i < 8; i++) { + if (lens[i]) + array[i] = i; + else + array[i] = -1; + } + qsort(array,SIZE(array),sizeof(array[0]), + (int (*)(const void *,const void *)) verify_sun_cmp); + if (array[0] == -1) { + printf(_("No partitions defined\n")); + return; + } + stop = cylinders * heads * sectors; + if (starts[array[0]]) + printf(_("Unused gap - sectors 0-%d\n"),starts[array[0]]); + for (i = 0; i < 7 && array[i+1] != -1; i++) { + printf(_("Unused gap - sectors %d-%d\n"),starts[array[i]]+lens[array[i]],starts[array[i+1]]); + } + start = starts[array[i]] + lens[array[i]]; + if (start < stop) + printf(_("Unused gap - sectors %d-%d\n"),start,stop); +} + +static void +add_sun_partition(int n, int sys) +{ + uint start, stop, stop2; + uint starts[8], lens[8]; + int whole_disk = 0; + + char mesg[256]; + int i, first, last; + + if (sunlabel->partitions[n].num_sectors && sunlabel->infos[n].id) { + printf(_("Partition %d is already defined. Delete " + "it before re-adding it.\n"), n + 1); + return; + } + + fetch_sun(starts,lens,&start,&stop); + if (stop <= start) { + if (n == 2) + whole_disk = 1; + else { + printf(_("Other partitions already cover the whole disk.\nDelete " + "some/shrink them before retry.\n")); + return; + } + } + snprintf(mesg, sizeof(mesg), _("First %s"), str_units(SINGULAR)); + while (1) { + if (whole_disk) + first = read_int(0, 0, 0, 0, mesg); + else + first = read_int(scround(start), scround(stop)+1, + scround(stop), 0, mesg); + if (display_in_cyl_units) + first *= units_per_sector; + else + /* Starting sector has to be properly aligned */ + first = (first + heads * sectors - 1) / (heads * sectors); + if (n == 2 && first != 0) + printf("\ +It is highly recommended that the third partition covers the whole disk\n\ +and is of type `Whole disk'\n"); + /* ewt asks to add: "don't start a partition at cyl 0" + However, edmundo@rano.demon.co.uk writes: + "In addition to having a Sun partition table, to be able to + boot from the disc, the first partition, /dev/sdX1, must + start at cylinder 0. This means that /dev/sdX1 contains + the partition table and the boot block, as these are the + first two sectors of the disc. Therefore you must be + careful what you use /dev/sdX1 for. In particular, you must + not use a partition starting at cylinder 0 for Linux swap, + as that would overwrite the partition table and the boot + block. You may, however, use such a partition for a UFS + or EXT2 file system, as these file systems leave the first + 1024 bytes undisturbed. */ + /* On the other hand, one should not use partitions + starting at block 0 in an md, or the label will + be trashed. */ + for (i = 0; i < partitions; i++) + if (lens[i] && starts[i] <= first && starts[i] + lens[i] > first) + break; + if (i < partitions && !whole_disk) { + if (n == 2 && !first) { + whole_disk = 1; + break; + } + printf(_("Sector %d is already allocated\n"), first); + } else + break; + } + stop = cylinders * heads * sectors; + stop2 = stop; + for (i = 0; i < partitions; i++) { + if (starts[i] > first && starts[i] < stop) + stop = starts[i]; + } + snprintf(mesg, sizeof(mesg), + _("Last %s or +size or +sizeM or +sizeK"), + str_units(SINGULAR)); + if (whole_disk) + last = read_int(scround(stop2), scround(stop2), scround(stop2), + 0, mesg); + else if (n == 2 && !first) + last = read_int(scround(first), scround(stop2), scround(stop2), + scround(first), mesg); + else + last = read_int(scround(first), scround(stop), scround(stop), + scround(first), mesg); + if (display_in_cyl_units) + last *= units_per_sector; + if (n == 2 && !first) { + if (last >= stop2) { + whole_disk = 1; + last = stop2; + } else if (last > stop) { + printf(_("You haven't covered the whole disk with " + "the 3rd partition, but your value\n" + "%d %s covers some other partition. " + "Your entry has been changed\n" + "to %d %s\n"), + scround(last), str_units(SINGULAR), + scround(stop), str_units(SINGULAR)); + last = stop; + } + } else if (!whole_disk && last > stop) + last = stop; + + if (whole_disk) + sys = SUN_WHOLE_DISK; + set_sun_partition(n, first, last, sys); +} + +static void +sun_delete_partition(int i) +{ + unsigned int nsec; + + if (i == 2 + && sunlabel->infos[i].id == SUN_WHOLE_DISK + && !sunlabel->partitions[i].start_cylinder + && (nsec = SUN_SSWAP32(sunlabel->partitions[i].num_sectors)) == heads * sectors * cylinders) + printf(_("If you want to maintain SunOS/Solaris compatibility, " + "consider leaving this\n" + "partition as Whole disk (5), starting at 0, with %u " + "sectors\n"), nsec); + sunlabel->infos[i].id = 0; + sunlabel->partitions[i].num_sectors = 0; +} + +static void +sun_change_sysid(int i, int sys) +{ + if (sys == LINUX_SWAP && !sunlabel->partitions[i].start_cylinder) { + read_maybe_empty( + _("It is highly recommended that the partition at offset 0\n" + "is UFS, EXT2FS filesystem or SunOS swap. Putting Linux swap\n" + "there may destroy your partition table and bootblock.\n" + "Type YES if you're very sure you would like that partition\n" + "tagged with 82 (Linux swap): ")); + if (strcmp (line_ptr, _("YES\n"))) + return; + } + switch (sys) { + case SUNOS_SWAP: + case LINUX_SWAP: + /* swaps are not mountable by default */ + sunlabel->infos[i].flags |= 0x01; + break; + default: + /* assume other types are mountable; + user can change it anyway */ + sunlabel->infos[i].flags &= ~0x01; + break; + } + sunlabel->infos[i].id = sys; +} + +static void +sun_list_table(int xtra) +{ + int i, w; + + w = strlen(disk_device); + if (xtra) + printf( + _("\nDisk %s (Sun disk label): %d heads, %d sectors, %d rpm\n" + "%d cylinders, %d alternate cylinders, %d physical cylinders\n" + "%d extra sects/cyl, interleave %d:1\n" + "%s\n" + "Units = %s of %d * 512 bytes\n\n"), + disk_device, heads, sectors, SUN_SSWAP16(sunlabel->rspeed), + cylinders, SUN_SSWAP16(sunlabel->nacyl), + SUN_SSWAP16(sunlabel->pcylcount), + SUN_SSWAP16(sunlabel->sparecyl), + SUN_SSWAP16(sunlabel->ilfact), + (char *)sunlabel, + str_units(PLURAL), units_per_sector); + else + printf( + _("\nDisk %s (Sun disk label): %d heads, %d sectors, %d cylinders\n" + "Units = %s of %d * 512 bytes\n\n"), + disk_device, heads, sectors, cylinders, + str_units(PLURAL), units_per_sector); + + printf(_("%*s Flag Start End Blocks Id System\n"), + w + 1, _("Device")); + for (i = 0 ; i < partitions; i++) { + if (sunlabel->partitions[i].num_sectors) { + uint32_t start = SUN_SSWAP32(sunlabel->partitions[i].start_cylinder) * heads * sectors; + uint32_t len = SUN_SSWAP32(sunlabel->partitions[i].num_sectors); + printf("%s %c%c %9ld %9ld %9ld%c %2x %s\n", + partname(disk_device, i+1, w), /* device */ + (sunlabel->infos[i].flags & 0x01) ? 'u' : ' ', /* flags */ + (sunlabel->infos[i].flags & 0x10) ? 'r' : ' ', + (long) scround(start), /* start */ + (long) scround(start+len), /* end */ + (long) len / 2, len & 1 ? '+' : ' ', /* odd flag on end */ + sunlabel->infos[i].id, /* type id */ + partition_type(sunlabel->infos[i].id)); /* type name */ + } + } +} + +#ifdef CONFIG_FEATURE_FDISK_ADVANCED + +static void +sun_set_alt_cyl(void) +{ + sunlabel->nacyl = + SUN_SSWAP16(read_int(0,SUN_SSWAP16(sunlabel->nacyl), 65535, 0, + _("Number of alternate cylinders"))); +} + +static void +sun_set_ncyl(int cyl) +{ + sunlabel->ncyl = SUN_SSWAP16(cyl); +} + +static void +sun_set_xcyl(void) +{ + sunlabel->sparecyl = + SUN_SSWAP16(read_int(0, SUN_SSWAP16(sunlabel->sparecyl), sectors, 0, + _("Extra sectors per cylinder"))); +} + +static void +sun_set_ilfact(void) +{ + sunlabel->ilfact = + SUN_SSWAP16(read_int(1, SUN_SSWAP16(sunlabel->ilfact), 32, 0, + _("Interleave factor"))); +} + +static void +sun_set_rspeed(void) +{ + sunlabel->rspeed = + SUN_SSWAP16(read_int(1, SUN_SSWAP16(sunlabel->rspeed), 100000, 0, + _("Rotation speed (rpm)"))); +} + +static void +sun_set_pcylcount(void) +{ + sunlabel->pcylcount = + SUN_SSWAP16(read_int(0, SUN_SSWAP16(sunlabel->pcylcount), 65535, 0, + _("Number of physical cylinders"))); +} +#endif /* CONFIG_FEATURE_FDISK_ADVANCED */ + +static void +sun_write_table(void) +{ + unsigned short *ush = (unsigned short *)sunlabel; + unsigned short csum = 0; + + while (ush < (unsigned short *)(&sunlabel->csum)) + csum ^= *ush++; + sunlabel->csum = csum; + if (lseek(fd, 0, SEEK_SET) < 0) + fdisk_fatal(unable_to_seek); + if (write(fd, sunlabel, SECTOR_SIZE) != SECTOR_SIZE) + fdisk_fatal(unable_to_write); +} +#endif /* SUN_LABEL */ diff --git a/util-linux/freeramdisk.c b/util-linux/freeramdisk.c new file mode 100644 index 000000000..2293d3ee6 --- /dev/null +++ b/util-linux/freeramdisk.c @@ -0,0 +1,34 @@ +/* vi: set sw=4 ts=4: */ +/* + * freeramdisk and fdflush implementations for busybox + * + * Copyright (C) 2000 and written by Emanuele Caratti <wiz@iol.it> + * Adjusted a bit by Erik Andersen <andersen@codepoet.org> + * Unified with fdflush by Tito Ragusa <farmatito@tiscali.it> + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* From <linux/fd.h> */ +#define FDFLUSH _IO(2,0x4b) + +int freeramdisk_main(int argc, char **argv) +{ + int result; + int fd; + + if (argc != 2) bb_show_usage(); + + fd = xopen(argv[1], O_RDWR); + + // Act like freeramdisk, fdflush, or both depending on configuration. + result = ioctl(fd, (ENABLE_FREERAMDISK && applet_name[1]=='r') + || !ENABLE_FDFLUSH ? BLKFLSBUF : FDFLUSH); + + if (ENABLE_FEATURE_CLEAN_UP) close(fd); + + if (result) bb_perror_msg_and_die("%s", argv[1]); + return EXIT_SUCCESS; +} diff --git a/util-linux/fsck_minix.c b/util-linux/fsck_minix.c new file mode 100644 index 000000000..9c831bd59 --- /dev/null +++ b/util-linux/fsck_minix.c @@ -0,0 +1,1417 @@ +/* vi: set sw=4 ts=4: */ +/* + * fsck.c - a file system consistency checker for Linux. + * + * (C) 1991, 1992 Linus Torvalds. + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +/* + * 09.11.91 - made the first rudimentary functions + * + * 10.11.91 - updated, does checking, no repairs yet. + * Sent out to the mailing-list for testing. + * + * 14.11.91 - Testing seems to have gone well. Added some + * correction-code, and changed some functions. + * + * 15.11.91 - More correction code. Hopefully it notices most + * cases now, and tries to do something about them. + * + * 16.11.91 - More corrections (thanks to Mika Jalava). Most + * things seem to work now. Yeah, sure. + * + * + * 19.04.92 - Had to start over again from this old version, as a + * kernel bug ate my enhanced fsck in february. + * + * 28.02.93 - added support for different directory entry sizes.. + * + * Sat Mar 6 18:59:42 1993, faith@cs.unc.edu: Output namelen with + * super-block information + * + * Sat Oct 9 11:17:11 1993, faith@cs.unc.edu: make exit status conform + * to that required by fsutil + * + * Mon Jan 3 11:06:52 1994 - Dr. Wettstein (greg%wind.uucp@plains.nodak.edu) + * Added support for file system valid flag. Also + * added program_version variable and output of + * program name and version number when program + * is executed. + * + * 30.10.94 - added support for v2 filesystem + * (Andreas Schwab, schwab@issan.informatik.uni-dortmund.de) + * + * 10.12.94 - added test to prevent checking of mounted fs adapted + * from Theodore Ts'o's (tytso@athena.mit.edu) e2fsck + * program. (Daniel Quinlan, quinlan@yggdrasil.com) + * + * 01.07.96 - Fixed the v2 fs stuff to use the right #defines and such + * for modern libcs (janl@math.uio.no, Nicolai Langfeldt) + * + * 02.07.96 - Added C bit fiddling routines from rmk@ecs.soton.ac.uk + * (Russell King). He made them for ARM. It would seem + * that the ARM is powerful enough to do this in C whereas + * i386 and m64k must use assembly to get it fast >:-) + * This should make minix fsck system-independent. + * (janl@math.uio.no, Nicolai Langfeldt) + * + * 04.11.96 - Added minor fixes from Andreas Schwab to avoid compiler + * warnings. Added mc68k bitops from + * Joerg Dorchain <dorchain@mpi-sb.mpg.de>. + * + * 06.11.96 - Added v2 code submitted by Joerg Dorchain, but written by + * Andreas Schwab. + * + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org> + * - added Native Language Support + * + * + * I've had no time to add comments - hopefully the function names + * are comments enough. As with all file system checkers, this assumes + * the file system is quiescent - don't use it on a mounted device + * unless you can be sure nobody is writing to it (and remember that the + * kernel can write to it when it searches for files). + * + * Usage: fsck [-larvsm] device + * -l for a listing of all the filenames + * -a for automatic repairs (not implemented) + * -r for repairs (interactive) (not implemented) + * -v for verbose (tells how many files) + * -s for super-block info + * -m for minix-like "mode not cleared" warnings + * -f force filesystem check even if filesystem marked as valid + * + * The device may be a block device or a image of one, but this isn't + * enforced (but it's not much fun on a character device :-). + */ + +#include "busybox.h" +#include <mntent.h> + +/* + * This is the original minix inode layout on disk. + * Note the 8-bit gid and atime and ctime. + */ +struct minix_inode { + uint16_t i_mode; + uint16_t i_uid; + uint32_t i_size; + uint32_t i_time; + uint8_t i_gid; + uint8_t i_nlinks; + uint16_t i_zone[9]; +}; + +/* + * The new minix inode has all the time entries, as well as + * long block numbers and a third indirect block (7+1+1+1 + * instead of 7+1+1). Also, some previously 8-bit values are + * now 16-bit. The inode is now 64 bytes instead of 32. + */ +struct minix2_inode { + uint16_t i_mode; + uint16_t i_nlinks; + uint16_t i_uid; + uint16_t i_gid; + uint32_t i_size; + uint32_t i_atime; + uint32_t i_mtime; + uint32_t i_ctime; + uint32_t i_zone[10]; +}; + +enum { + MINIX_ROOT_INO = 1, + MINIX_LINK_MAX = 250, + MINIX2_LINK_MAX = 65530, + + MINIX_I_MAP_SLOTS = 8, + MINIX_Z_MAP_SLOTS = 64, + MINIX_SUPER_MAGIC = 0x137F, /* original minix fs */ + MINIX_SUPER_MAGIC2 = 0x138F, /* minix fs, 30 char names */ + MINIX2_SUPER_MAGIC = 0x2468, /* minix V2 fs */ + MINIX2_SUPER_MAGIC2 = 0x2478, /* minix V2 fs, 30 char names */ + MINIX_VALID_FS = 0x0001, /* Clean fs. */ + MINIX_ERROR_FS = 0x0002, /* fs has errors. */ + + MINIX_INODES_PER_BLOCK = ((BLOCK_SIZE)/(sizeof (struct minix_inode))), + MINIX2_INODES_PER_BLOCK = ((BLOCK_SIZE)/(sizeof (struct minix2_inode))), + + MINIX_V1 = 0x0001, /* original minix fs */ + MINIX_V2 = 0x0002 /* minix V2 fs */ +}; + +#define INODE_VERSION(inode) inode->i_sb->u.minix_sb.s_version + +/* + * minix super-block data on disk + */ +struct minix_super_block { + uint16_t s_ninodes; + uint16_t s_nzones; + uint16_t s_imap_blocks; + uint16_t s_zmap_blocks; + uint16_t s_firstdatazone; + uint16_t s_log_zone_size; + uint32_t s_max_size; + uint16_t s_magic; + uint16_t s_state; + uint32_t s_zones; +}; + +struct minix_dir_entry { + uint16_t inode; + char name[0]; +}; + + +#define NAME_MAX 255 /* # chars in a file name */ + +#define MINIX_INODES_PER_BLOCK ((BLOCK_SIZE)/(sizeof (struct minix_inode))) + +#ifndef BLKGETSIZE +#define BLKGETSIZE _IO(0x12,96) /* return device size */ +#endif + +#ifndef __linux__ +#define volatile +#endif + +enum { ROOT_INO = 1 }; + +#define UPPER(size,n) ((size+((n)-1))/(n)) +#define INODE_SIZE (sizeof(struct minix_inode)) +#ifdef CONFIG_FEATURE_MINIX2 +#define INODE_SIZE2 (sizeof(struct minix2_inode)) +#define INODE_BLOCKS UPPER(INODES, (version2 ? MINIX2_INODES_PER_BLOCK \ + : MINIX_INODES_PER_BLOCK)) +#else +#define INODE_BLOCKS UPPER(INODES, (MINIX_INODES_PER_BLOCK)) +#endif +#define INODE_BUFFER_SIZE (INODE_BLOCKS * BLOCK_SIZE) + +#define BITS_PER_BLOCK (BLOCK_SIZE<<3) + +static char *program_version = "1.2 - 11/11/96"; +static char *device_name; +static int IN; +static int repair, automatic, verbose, list, show, warn_mode, force; +static int directory, regular, blockdev, chardev, links, symlinks, total; + +static int changed; /* flags if the filesystem has been changed */ +static int errors_uncorrected; /* flag if some error was not corrected */ +static int dirsize = 16; +static int namelen = 14; +static struct termios termios; +static int termios_set; + +static char *inode_buffer; +#define Inode (((struct minix_inode *) inode_buffer)-1) +#define Inode2 (((struct minix2_inode *) inode_buffer)-1) +static char super_block_buffer[BLOCK_SIZE]; + +#define Super (*(struct minix_super_block *)super_block_buffer) +#define INODES ((unsigned long)Super.s_ninodes) +#ifdef CONFIG_FEATURE_MINIX2 +static int version2; +#define ZONES ((unsigned long)(version2 ? Super.s_zones : Super.s_nzones)) +#else +#define ZONES ((unsigned long)(Super.s_nzones)) +#endif +#define IMAPS ((unsigned long)Super.s_imap_blocks) +#define ZMAPS ((unsigned long)Super.s_zmap_blocks) +#define FIRSTZONE ((unsigned long)Super.s_firstdatazone) +#define ZONESIZE ((unsigned long)Super.s_log_zone_size) +#define MAXSIZE ((unsigned long)Super.s_max_size) +#define MAGIC (Super.s_magic) +#define NORM_FIRSTZONE (2+IMAPS+ZMAPS+INODE_BLOCKS) + +static char *inode_map; +static char *zone_map; + +static unsigned char *inode_count; +static unsigned char *zone_count; + +static void recursive_check(unsigned int ino); +#ifdef CONFIG_FEATURE_MINIX2 +static void recursive_check2(unsigned int ino); +#endif + +static int bit(char *a, unsigned int i) +{ + return (a[i >> 3] & (1<<(i & 7))) != 0; +} +#define inode_in_use(x) (bit(inode_map,(x))) +#define zone_in_use(x) (bit(zone_map,(x)-FIRSTZONE+1)) + +#define mark_inode(x) (setbit(inode_map,(x)),changed=1) +#define unmark_inode(x) (clrbit(inode_map,(x)),changed=1) + +#define mark_zone(x) (setbit(zone_map,(x)-FIRSTZONE+1),changed=1) +#define unmark_zone(x) (clrbit(zone_map,(x)-FIRSTZONE+1),changed=1) + +static void leave(int) ATTRIBUTE_NORETURN; +static void leave(int status) +{ + if (termios_set) + tcsetattr(0, TCSANOW, &termios); + exit(status); +} + +static void die(const char *str) +{ + bb_error_msg("%s", str); + leave(8); +} + +/* File-name data */ +enum { MAX_DEPTH = 32 }; +static int name_depth; +static char *current_name; +static char *name_component[MAX_DEPTH+1]; + +/* Wed Feb 9 15:17:06 MST 2000 */ +/* dynamically allocate name_list (instead of making it static) */ +static void alloc_current_name(void) +{ + current_name = xmalloc(MAX_DEPTH * (BUFSIZ + 1)); + current_name[0] = '/'; + current_name[1] = '\0'; + name_component[0] = ¤t_name[0]; +} + +#ifdef CONFIG_FEATURE_CLEAN_UP +/* execute this atexit() to deallocate name_list[] */ +/* piptigger was here */ +static void free_current_name(void) +{ + free(current_name); +} +#endif + +static void push_filename(const char *name) +{ + // /dir/dir/dir/file + // ^ ^ ^ + // [0] [1] [2] <-name_component[i] + if (name_depth < MAX_DEPTH) { + int len; + char *p = name_component[name_depth]; + *p++ = '/'; + len = sprintf(p, "%.*s", namelen, name); + name_component[name_depth + 1] = p + len; + } + name_depth++; +} + +static void pop_filename(void) { + name_depth--; + if (name_depth < MAX_DEPTH) { + *name_component[name_depth] = '\0'; + if (!name_depth) { + current_name[0] = '/'; + current_name[1] = '\0'; + } + } +} + +static int ask(const char *string, int def) +{ + int c; + + if (!repair) { + puts(""); + errors_uncorrected = 1; + return 0; + } + if (automatic) { + puts(""); + if (!def) + errors_uncorrected = 1; + return def; + } + printf(def ? "%s (y/n)? " : "%s (n/y)? ", string); + for (;;) { + fflush(stdout); + if ((c = getchar()) == EOF) { + if (!def) + errors_uncorrected = 1; + return def; + } + c = toupper(c); + if (c == 'Y') { + def = 1; + break; + } else if (c == 'N') { + def = 0; + break; + } else if (c == ' ' || c == '\n') + break; + } + if (def) + printf("y\n"); + else { + printf("n\n"); + errors_uncorrected = 1; + } + return def; +} + +/* + * Make certain that we aren't checking a filesystem that is on a + * mounted partition. Code adapted from e2fsck, Copyright (C) 1993, + * 1994 Theodore Ts'o. Also licensed under GPL. + */ +static void check_mount(void) +{ + FILE *f; + struct mntent *mnt; + int cont; + int fd; + + if ((f = setmntent(MOUNTED, "r")) == NULL) + return; + while ((mnt = getmntent(f)) != NULL) + if (strcmp(device_name, mnt->mnt_fsname) == 0) + break; + endmntent(f); + if (!mnt) + return; + + /* + * If the root is mounted read-only, then /etc/mtab is + * probably not correct; so we won't issue a warning based on + * it. + */ + fd = open(MOUNTED, O_RDWR); + if (fd < 0 && errno == EROFS) + return; + else + close(fd); + + printf("%s is mounted. ", device_name); + cont = 0; + if (isatty(0) && isatty(1)) + cont = ask("Do you really want to continue", 0); + if (!cont) { + printf("Check aborted\n"); + exit(0); + } + return; +} + +/* + * check_zone_nr checks to see that *nr is a valid zone nr. If it + * isn't, it will possibly be repaired. Check_zone_nr sets *corrected + * if an error was corrected, and returns the zone (0 for no zone + * or a bad zone-number). + */ +static int check_zone_nr2(uint32_t *nr, int *corrected) +{ + const char *msg; + if (!*nr) + return 0; + if (*nr < FIRSTZONE) + msg = "< FIRSTZONE"; + else if (*nr >= ZONES) + msg = ">= ZONES"; + else + return *nr; + printf("Zone nr %s in file '%s'. ", msg, current_name); + if (ask("Remove block", 1)) { + *nr = 0; + *corrected = 1; + } + return 0; +} + +static int check_zone_nr(uint16_t *nr, int *corrected) +{ + uint32_t nr32 = *nr; + int r = check_zone_nr2(&nr32, corrected); + *nr = (uint16_t)nr32; + return r; +} + +/* + * read-block reads block nr into the buffer at addr. + */ +static void read_block(unsigned int nr, char *addr) +{ + if (!nr) { + memset(addr, 0, BLOCK_SIZE); + return; + } + if (BLOCK_SIZE * nr != lseek(IN, BLOCK_SIZE * nr, SEEK_SET)) { + printf("%s: cannot seek to block in file '%s'\n", + bb_msg_read_error, current_name); + errors_uncorrected = 1; + memset(addr, 0, BLOCK_SIZE); + } else if (BLOCK_SIZE != read(IN, addr, BLOCK_SIZE)) { + printf("%s: bad block in file '%s'\n", + bb_msg_read_error, current_name); + errors_uncorrected = 1; + memset(addr, 0, BLOCK_SIZE); + } +} + +/* + * write_block writes block nr to disk. + */ +static void write_block(unsigned int nr, char *addr) +{ + if (!nr) + return; + if (nr < FIRSTZONE || nr >= ZONES) { + printf("Internal error: trying to write bad block\n" + "Write request ignored\n"); + errors_uncorrected = 1; + return; + } + if (BLOCK_SIZE * nr != lseek(IN, BLOCK_SIZE * nr, SEEK_SET)) + die("Seek failed in write_block"); + if (BLOCK_SIZE != write(IN, addr, BLOCK_SIZE)) { + printf("%s: bad block in file '%s'\n", + bb_msg_write_error, current_name); + errors_uncorrected = 1; + } +} + +/* + * map_block calculates the absolute block nr of a block in a file. + * It sets 'changed' if the inode has needed changing, and re-writes + * any indirect blocks with errors. + */ +static int map_block(struct minix_inode *inode, unsigned int blknr) +{ + uint16_t ind[BLOCK_SIZE >> 1]; + uint16_t dind[BLOCK_SIZE >> 1]; + int blk_chg, block, result; + + if (blknr < 7) + return check_zone_nr(inode->i_zone + blknr, &changed); + blknr -= 7; + if (blknr < 512) { + block = check_zone_nr(inode->i_zone + 7, &changed); + read_block(block, (char *) ind); + blk_chg = 0; + result = check_zone_nr(blknr + ind, &blk_chg); + if (blk_chg) + write_block(block, (char *) ind); + return result; + } + blknr -= 512; + block = check_zone_nr(inode->i_zone + 8, &changed); + read_block(block, (char *) dind); + blk_chg = 0; + result = check_zone_nr(dind + (blknr / 512), &blk_chg); + if (blk_chg) + write_block(block, (char *) dind); + block = result; + read_block(block, (char *) ind); + blk_chg = 0; + result = check_zone_nr(ind + (blknr % 512), &blk_chg); + if (blk_chg) + write_block(block, (char *) ind); + return result; +} + +#ifdef CONFIG_FEATURE_MINIX2 +static int map_block2(struct minix2_inode *inode, unsigned int blknr) +{ + uint32_t ind[BLOCK_SIZE >> 2]; + uint32_t dind[BLOCK_SIZE >> 2]; + uint32_t tind[BLOCK_SIZE >> 2]; + int blk_chg, block, result; + + if (blknr < 7) + return check_zone_nr2(inode->i_zone + blknr, &changed); + blknr -= 7; + if (blknr < 256) { + block = check_zone_nr2(inode->i_zone + 7, &changed); + read_block(block, (char *) ind); + blk_chg = 0; + result = check_zone_nr2(blknr + ind, &blk_chg); + if (blk_chg) + write_block(block, (char *) ind); + return result; + } + blknr -= 256; + if (blknr >= 256 * 256) { + block = check_zone_nr2(inode->i_zone + 8, &changed); + read_block(block, (char *) dind); + blk_chg = 0; + result = check_zone_nr2(dind + blknr / 256, &blk_chg); + if (blk_chg) + write_block(block, (char *) dind); + block = result; + read_block(block, (char *) ind); + blk_chg = 0; + result = check_zone_nr2(ind + blknr % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *) ind); + return result; + } + blknr -= 256 * 256; + block = check_zone_nr2(inode->i_zone + 9, &changed); + read_block(block, (char *) tind); + blk_chg = 0; + result = check_zone_nr2(tind + blknr / (256 * 256), &blk_chg); + if (blk_chg) + write_block(block, (char *) tind); + block = result; + read_block(block, (char *) dind); + blk_chg = 0; + result = check_zone_nr2(dind + (blknr / 256) % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *) dind); + block = result; + read_block(block, (char *) ind); + blk_chg = 0; + result = check_zone_nr2(ind + blknr % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *) ind); + return result; +} +#endif + +static void write_super_block(void) +{ + /* + * Set the state of the filesystem based on whether or not there + * are uncorrected errors. The filesystem valid flag is + * unconditionally set if we get this far. + */ + Super.s_state |= MINIX_VALID_FS; + if (errors_uncorrected) + Super.s_state |= MINIX_ERROR_FS; + else + Super.s_state &= ~MINIX_ERROR_FS; + + if (BLOCK_SIZE != lseek(IN, BLOCK_SIZE, SEEK_SET)) + die("Seek failed in write_super_block"); + if (BLOCK_SIZE != write(IN, super_block_buffer, BLOCK_SIZE)) + die("Unable to write super-block"); +} + +static void write_tables(void) +{ + write_super_block(); + + if (IMAPS * BLOCK_SIZE != write(IN, inode_map, IMAPS * BLOCK_SIZE)) + die("Unable to write inode map"); + if (ZMAPS * BLOCK_SIZE != write(IN, zone_map, ZMAPS * BLOCK_SIZE)) + die("Unable to write zone map"); + if (INODE_BUFFER_SIZE != write(IN, inode_buffer, INODE_BUFFER_SIZE)) + die("Unable to write inodes"); +} + +static void get_dirsize(void) +{ + int block; + char blk[BLOCK_SIZE]; + int size; + +#ifdef CONFIG_FEATURE_MINIX2 + if (version2) + block = Inode2[ROOT_INO].i_zone[0]; + else +#endif + block = Inode[ROOT_INO].i_zone[0]; + read_block(block, blk); + for (size = 16; size < BLOCK_SIZE; size <<= 1) { + if (strcmp(blk + size + 2, "..") == 0) { + dirsize = size; + namelen = size - 2; + return; + } + } + /* use defaults */ +} + +static void read_superblock(void) +{ + if (BLOCK_SIZE != lseek(IN, BLOCK_SIZE, SEEK_SET)) + die("Seek failed"); + if (BLOCK_SIZE != read(IN, super_block_buffer, BLOCK_SIZE)) + die("Unable to read super block"); + /* already initialized to: + namelen = 14; + dirsize = 16; + version2 = 0; + */ + if (MAGIC == MINIX_SUPER_MAGIC) { + } else if (MAGIC == MINIX_SUPER_MAGIC2) { + namelen = 30; + dirsize = 32; +#ifdef CONFIG_FEATURE_MINIX2 + } else if (MAGIC == MINIX2_SUPER_MAGIC) { + version2 = 1; + } else if (MAGIC == MINIX2_SUPER_MAGIC2) { + namelen = 30; + dirsize = 32; + version2 = 1; +#endif + } else + die("Bad magic number in super-block"); + if (ZONESIZE != 0 || BLOCK_SIZE != 1024) + die("Only 1k blocks/zones supported"); + if (IMAPS * BLOCK_SIZE * 8 < INODES + 1) + die("Bad s_imap_blocks field in super-block"); + if (ZMAPS * BLOCK_SIZE * 8 < ZONES - FIRSTZONE + 1) + die("Bad s_zmap_blocks field in super-block"); +} + +static void read_tables(void) +{ + inode_map = xzalloc(IMAPS * BLOCK_SIZE); + zone_map = xzalloc(ZMAPS * BLOCK_SIZE); + inode_buffer = xmalloc(INODE_BUFFER_SIZE); + inode_count = xmalloc(INODES + 1); + zone_count = xmalloc(ZONES); + if (IMAPS * BLOCK_SIZE != read(IN, inode_map, IMAPS * BLOCK_SIZE)) + die("Unable to read inode map"); + if (ZMAPS * BLOCK_SIZE != read(IN, zone_map, ZMAPS * BLOCK_SIZE)) + die("Unable to read zone map"); + if (INODE_BUFFER_SIZE != read(IN, inode_buffer, INODE_BUFFER_SIZE)) + die("Unable to read inodes"); + if (NORM_FIRSTZONE != FIRSTZONE) { + printf("Warning: Firstzone!=Norm_firstzone\n"); + errors_uncorrected = 1; + } + get_dirsize(); + if (show) { + printf("%ld inodes\n" + "%ld blocks\n" + "Firstdatazone=%ld (%ld)\n" + "Zonesize=%d\n" + "Maxsize=%ld\n" + "Filesystem state=%d\n" + "namelen=%d\n\n", + INODES, + ZONES, + FIRSTZONE, NORM_FIRSTZONE, + BLOCK_SIZE << ZONESIZE, + MAXSIZE, + Super.s_state, + namelen); + } +} + +static struct minix_inode *get_inode(unsigned int nr) +{ + struct minix_inode *inode; + + if (!nr || nr > INODES) + return NULL; + total++; + inode = Inode + nr; + if (!inode_count[nr]) { + if (!inode_in_use(nr)) { + printf("Inode %d is marked as 'unused', but it is used " + "for file '%s'\n", nr, current_name); + if (repair) { + if (ask("Mark as 'in use'", 1)) + mark_inode(nr); + } else { + errors_uncorrected = 1; + } + } + if (S_ISDIR(inode->i_mode)) + directory++; + else if (S_ISREG(inode->i_mode)) + regular++; + else if (S_ISCHR(inode->i_mode)) + chardev++; + else if (S_ISBLK(inode->i_mode)) + blockdev++; + else if (S_ISLNK(inode->i_mode)) + symlinks++; + else if (S_ISSOCK(inode->i_mode)); + else if (S_ISFIFO(inode->i_mode)); + else { + printf("%s has mode %05o\n", current_name, inode->i_mode); + } + + } else + links++; + if (!++inode_count[nr]) { + printf("Warning: inode count too big\n"); + inode_count[nr]--; + errors_uncorrected = 1; + } + return inode; +} + +#ifdef CONFIG_FEATURE_MINIX2 +static struct minix2_inode *get_inode2(unsigned int nr) +{ + struct minix2_inode *inode; + + if (!nr || nr > INODES) + return NULL; + total++; + inode = Inode2 + nr; + if (!inode_count[nr]) { + if (!inode_in_use(nr)) { + printf("Inode %d is marked as 'unused', but it is used " + "for file '%s'\n", nr, current_name); + if (repair) { + if (ask("Mark as 'in use'", 1)) + mark_inode(nr); + else + errors_uncorrected = 1; + } + } + if (S_ISDIR(inode->i_mode)) + directory++; + else if (S_ISREG(inode->i_mode)) + regular++; + else if (S_ISCHR(inode->i_mode)) + chardev++; + else if (S_ISBLK(inode->i_mode)) + blockdev++; + else if (S_ISLNK(inode->i_mode)) + symlinks++; + else if (S_ISSOCK(inode->i_mode)); + else if (S_ISFIFO(inode->i_mode)); + else { + printf("%s has mode %05o\n", current_name, inode->i_mode); + } + } else + links++; + if (!++inode_count[nr]) { + printf("Warning: inode count too big\n"); + inode_count[nr]--; + errors_uncorrected = 1; + } + return inode; +} +#endif + +static void check_root(void) +{ + struct minix_inode *inode = Inode + ROOT_INO; + + if (!inode || !S_ISDIR(inode->i_mode)) + die("Root inode isn't a directory"); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void check_root2(void) +{ + struct minix2_inode *inode = Inode2 + ROOT_INO; + + if (!inode || !S_ISDIR(inode->i_mode)) + die("Root inode isn't a directory"); +} +#endif + +static int add_zone(uint16_t *znr, int *corrected) +{ + int result; + int block; + + result = 0; + block = check_zone_nr(znr, corrected); + if (!block) + return 0; + if (zone_count[block]) { + printf("Already used block is reused in file '%s'. ", + current_name); + if (ask("Clear", 1)) { + *znr = 0; + block = 0; + *corrected = 1; + return 0; + } + } + if (!zone_in_use(block)) { + printf("Block %d in file '%s' is marked as 'unused'. ", + block, current_name); + if (ask("Correct", 1)) + mark_zone(block); + } + if (!++zone_count[block]) + zone_count[block]--; + return block; +} + +#ifdef CONFIG_FEATURE_MINIX2 +static int add_zone2(uint32_t *znr, int *corrected) +{ + int result; + int block; + + result = 0; + block = check_zone_nr2(znr, corrected); + if (!block) + return 0; + if (zone_count[block]) { + printf("Already used block is reused in file '%s'. ", + current_name); + if (ask("Clear", 1)) { + *znr = 0; + block = 0; + *corrected = 1; + return 0; + } + } + if (!zone_in_use(block)) { + printf("Block %d in file '%s' is marked as 'unused'. ", + block, current_name); + if (ask("Correct", 1)) + mark_zone(block); + } + if (!++zone_count[block]) + zone_count[block]--; + return block; +} +#endif + +static void add_zone_ind(uint16_t *znr, int *corrected) +{ + static char blk[BLOCK_SIZE]; + int i, chg_blk = 0; + int block; + + block = add_zone(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < (BLOCK_SIZE >> 1); i++) + add_zone(i + (uint16_t *) blk, &chg_blk); + if (chg_blk) + write_block(block, blk); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void add_zone_ind2(uint32_t *znr, int *corrected) +{ + static char blk[BLOCK_SIZE]; + int i, chg_blk = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < BLOCK_SIZE >> 2; i++) + add_zone2(i + (uint32_t *) blk, &chg_blk); + if (chg_blk) + write_block(block, blk); +} +#endif + +static void add_zone_dind(uint16_t *znr, int *corrected) +{ + static char blk[BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < (BLOCK_SIZE >> 1); i++) + add_zone_ind(i + (uint16_t *) blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void add_zone_dind2(uint32_t *znr, int *corrected) +{ + static char blk[BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < BLOCK_SIZE >> 2; i++) + add_zone_ind2(i + (uint32_t *) blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} + +static void add_zone_tind2(uint32_t *znr, int *corrected) +{ + static char blk[BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < BLOCK_SIZE >> 2; i++) + add_zone_dind2(i + (uint32_t *) blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} +#endif + +static void check_zones(unsigned int i) +{ + struct minix_inode *inode; + + if (!i || i > INODES) + return; + if (inode_count[i] > 1) /* have we counted this file already? */ + return; + inode = Inode + i; + if (!S_ISDIR(inode->i_mode) && !S_ISREG(inode->i_mode) && + !S_ISLNK(inode->i_mode)) return; + for (i = 0; i < 7; i++) + add_zone(i + inode->i_zone, &changed); + add_zone_ind(7 + inode->i_zone, &changed); + add_zone_dind(8 + inode->i_zone, &changed); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void check_zones2(unsigned int i) +{ + struct minix2_inode *inode; + + if (!i || i > INODES) + return; + if (inode_count[i] > 1) /* have we counted this file already? */ + return; + inode = Inode2 + i; + if (!S_ISDIR(inode->i_mode) && !S_ISREG(inode->i_mode) + && !S_ISLNK(inode->i_mode)) + return; + for (i = 0; i < 7; i++) + add_zone2(i + inode->i_zone, &changed); + add_zone_ind2(7 + inode->i_zone, &changed); + add_zone_dind2(8 + inode->i_zone, &changed); + add_zone_tind2(9 + inode->i_zone, &changed); +} +#endif + +static void check_file(struct minix_inode *dir, unsigned int offset) +{ + static char blk[BLOCK_SIZE]; + struct minix_inode *inode; + int ino; + char *name; + int block; + + block = map_block(dir, offset / BLOCK_SIZE); + read_block(block, blk); + name = blk + (offset % BLOCK_SIZE) + 2; + ino = *(uint16_t *) (name - 2); + if (ino > INODES) { + printf("%s contains a bad inode number for file '%.*s'. ", + current_name, namelen, name); + if (ask("Remove", 1)) { + *(uint16_t *) (name - 2) = 0; + write_block(block, blk); + } + ino = 0; + } + push_filename(name); + inode = get_inode(ino); + pop_filename(); + if (!offset) { + if (!inode || strcmp(".", name)) { + printf("%s: bad directory: '.' isn't first\n", current_name); + errors_uncorrected = 1; + } else + return; + } + if (offset == dirsize) { + if (!inode || strcmp("..", name)) { + printf("%s: bad directory: '..' isn't second\n", current_name); + errors_uncorrected = 1; + } else + return; + } + if (!inode) + return; + push_filename(name); + if (list) { + if (verbose) + printf("%6d %07o %3d ", ino, inode->i_mode, inode->i_nlinks); + printf("%s%s\n", current_name, S_ISDIR(inode->i_mode) ? ":" : ""); + } + check_zones(ino); + if (inode && S_ISDIR(inode->i_mode)) + recursive_check(ino); + pop_filename(); + return; +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void check_file2(struct minix2_inode *dir, unsigned int offset) +{ + static char blk[BLOCK_SIZE]; + struct minix2_inode *inode; + int ino; + char *name; + int block; + + block = map_block2(dir, offset / BLOCK_SIZE); + read_block(block, blk); + name = blk + (offset % BLOCK_SIZE) + 2; + ino = *(uint16_t *) (name - 2); + if (ino > INODES) { + printf("%s contains a bad inode number for file '%.*s'. ", + current_name, namelen, name); + if (ask("Remove", 1)) { + *(uint16_t *) (name - 2) = 0; + write_block(block, blk); + } + ino = 0; + } + push_filename(name); + inode = get_inode2(ino); + pop_filename(); + if (!offset) { + if (!inode || strcmp(".", name)) { + printf("%s: bad directory: '.' isn't first\n", current_name); + errors_uncorrected = 1; + } else + return; + } + if (offset == dirsize) { + if (!inode || strcmp("..", name)) { + printf("%s: bad directory: '..' isn't second\n", current_name); + errors_uncorrected = 1; + } else + return; + } + if (!inode) + return; + push_filename(name); + if (list) { + if (verbose) + printf("%6d %07o %3d ", ino, inode->i_mode, inode->i_nlinks); + printf("%s%s\n", current_name, S_ISDIR(inode->i_mode) ? ":" : ""); + } + check_zones2(ino); + if (inode && S_ISDIR(inode->i_mode)) + recursive_check2(ino); + pop_filename(); + return; +} +#endif + +static void recursive_check(unsigned int ino) +{ + struct minix_inode *dir; + unsigned int offset; + + dir = Inode + ino; + if (!S_ISDIR(dir->i_mode)) + die("Internal error"); + if (dir->i_size < 2 * dirsize) { + printf("%s: bad directory: size<32", current_name); + errors_uncorrected = 1; + } + for (offset = 0; offset < dir->i_size; offset += dirsize) + check_file(dir, offset); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void recursive_check2(unsigned int ino) +{ + struct minix2_inode *dir; + unsigned int offset; + + dir = Inode2 + ino; + if (!S_ISDIR(dir->i_mode)) + die("Internal error"); + if (dir->i_size < 2 * dirsize) { + printf("%s: bad directory: size<32", current_name); + errors_uncorrected = 1; + } + for (offset = 0; offset < dir->i_size; offset += dirsize) + check_file2(dir, offset); +} +#endif + +static int bad_zone(int i) +{ + char buffer[1024]; + + if (BLOCK_SIZE * i != lseek(IN, BLOCK_SIZE * i, SEEK_SET)) + die("Seek failed in bad_zone"); + return (BLOCK_SIZE != read(IN, buffer, BLOCK_SIZE)); +} + +static void check_counts(void) +{ + int i; + + for (i = 1; i <= INODES; i++) { + if (warn_mode && Inode[i].i_mode && !inode_in_use(i)) { + printf("Inode %d has non-zero mode. ", i); + if (ask("Clear", 1)) { + Inode[i].i_mode = 0; + changed = 1; + } + } + if (!inode_count[i]) { + if (!inode_in_use(i)) + continue; + printf("Unused inode %d is marked as 'used' in the bitmap. ", i); + if (ask("Clear", 1)) + unmark_inode(i); + continue; + } + if (!inode_in_use(i)) { + printf("Inode %d is used, but marked as 'unused' in the bitmap. ", i); + if (ask("Set", 1)) + mark_inode(i); + } + if (Inode[i].i_nlinks != inode_count[i]) { + printf("Inode %d (mode=%07o), i_nlinks=%d, counted=%d. ", + i, Inode[i].i_mode, Inode[i].i_nlinks, inode_count[i]); + if (ask("Set i_nlinks to count", 1)) { + Inode[i].i_nlinks = inode_count[i]; + changed = 1; + } + } + } + for (i = FIRSTZONE; i < ZONES; i++) { + if (zone_in_use(i) == zone_count[i]) + continue; + if (!zone_count[i]) { + if (bad_zone(i)) + continue; + printf("Zone %d is marked 'in use', but no file uses it. ", i); + if (ask("Unmark", 1)) + unmark_zone(i); + continue; + } + printf("Zone %d: %sin use, counted=%d\n", + i, zone_in_use(i) ? "" : "not ", zone_count[i]); + } +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void check_counts2(void) +{ + int i; + + for (i = 1; i <= INODES; i++) { + if (warn_mode && Inode2[i].i_mode && !inode_in_use(i)) { + printf("Inode %d has non-zero mode. ", i); + if (ask("Clear", 1)) { + Inode2[i].i_mode = 0; + changed = 1; + } + } + if (!inode_count[i]) { + if (!inode_in_use(i)) + continue; + printf("Unused inode %d is marked as 'used' in the bitmap. ", i); + if (ask("Clear", 1)) + unmark_inode(i); + continue; + } + if (!inode_in_use(i)) { + printf("Inode %d is used, but marked as 'unused' in the bitmap. ", i); + if (ask("Set", 1)) + mark_inode(i); + } + if (Inode2[i].i_nlinks != inode_count[i]) { + printf("Inode %d (mode=%07o), i_nlinks=%d, counted=%d. ", + i, Inode2[i].i_mode, Inode2[i].i_nlinks, + inode_count[i]); + if (ask("Set i_nlinks to count", 1)) { + Inode2[i].i_nlinks = inode_count[i]; + changed = 1; + } + } + } + for (i = FIRSTZONE; i < ZONES; i++) { + if (zone_in_use(i) == zone_count[i]) + continue; + if (!zone_count[i]) { + if (bad_zone(i)) + continue; + printf("Zone %d is marked 'in use', but no file uses it. ", i); + if (ask("Unmark", 1)) + unmark_zone(i); + continue; + } + printf("Zone %d: %sin use, counted=%d\n", + i, zone_in_use(i) ? "" : "not ", zone_count[i]); + } +} +#endif + +static void check(void) +{ + memset(inode_count, 0, (INODES + 1) * sizeof(*inode_count)); + memset(zone_count, 0, ZONES * sizeof(*zone_count)); + check_zones(ROOT_INO); + recursive_check(ROOT_INO); + check_counts(); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void check2(void) +{ + memset(inode_count, 0, (INODES + 1) * sizeof(*inode_count)); + memset(zone_count, 0, ZONES * sizeof(*zone_count)); + check_zones2(ROOT_INO); + recursive_check2(ROOT_INO); + check_counts2(); +} +#endif + +int fsck_minix_main(int argc, char **argv) +{ + struct termios tmp; + int retcode = 0; + + alloc_current_name(); +#ifdef CONFIG_FEATURE_CLEAN_UP + /* Don't bother to free memory. Exit does + * that automagically, so we can save a few bytes */ + atexit(free_current_name); +#endif + + if (INODE_SIZE * MINIX_INODES_PER_BLOCK != BLOCK_SIZE) + die("Bad inode size"); +#ifdef CONFIG_FEATURE_MINIX2 + if (INODE_SIZE2 * MINIX2_INODES_PER_BLOCK != BLOCK_SIZE) + die("Bad v2 inode size"); +#endif + while (argc-- > 1) { + argv++; + if (argv[0][0] != '-') { + if (device_name) + bb_show_usage(); + else + device_name = argv[0]; + } else + while (*++argv[0]) + switch (argv[0][0]) { + case 'l': + list = 1; + break; + case 'a': + automatic = 1; + repair = 1; + break; + case 'r': + automatic = 0; + repair = 1; + break; + case 'v': + verbose = 1; + break; + case 's': + show = 1; + break; + case 'm': + warn_mode = 1; + break; + case 'f': + force = 1; + break; + default: + bb_show_usage(); + } + } + if (!device_name) + bb_show_usage(); + check_mount(); /* trying to check a mounted filesystem? */ + if (repair && !automatic) { + if (!isatty(0) || !isatty(1)) + die("Need terminal for interactive repairs"); + } + IN = open(device_name, repair ? O_RDWR : O_RDONLY); + if (IN < 0){ + printf("Unable to open device '%s'\n", device_name); + leave(8); + } + sync(); /* paranoia? */ + read_superblock(); + + /* + * Determine whether or not we should continue with the checking. + * This is based on the status of the filesystem valid and error + * flags and whether or not the -f switch was specified on the + * command line. + */ + printf("%s, %s\n", applet_name, program_version); + if (!(Super.s_state & MINIX_ERROR_FS) && + (Super.s_state & MINIX_VALID_FS) && !force) { + if (repair) + printf("%s is clean, check is skipped\n", device_name); + return retcode; + } else if (force) + printf("Forcing filesystem check on %s\n", device_name); + else if (repair) + printf("Filesystem on %s is dirty, needs checking\n", + device_name); + + read_tables(); + + if (repair && !automatic) { + tcgetattr(0, &termios); + tmp = termios; + tmp.c_lflag &= ~(ICANON | ECHO); + tcsetattr(0, TCSANOW, &tmp); + termios_set = 1; + } +#ifdef CONFIG_FEATURE_MINIX2 + if (version2) { + check_root2(); + check2(); + } else +#endif + { + check_root(); + check(); + } + if (verbose) { + int i, free_cnt; + + for (i = 1, free_cnt = 0; i <= INODES; i++) + if (!inode_in_use(i)) + free_cnt++; + printf("\n%6ld inodes used (%ld%%)\n", (INODES - free_cnt), + 100 * (INODES - free_cnt) / INODES); + for (i = FIRSTZONE, free_cnt = 0; i < ZONES; i++) + if (!zone_in_use(i)) + free_cnt++; + printf("%6ld zones used (%ld%%)\n\n" + "%6d regular files\n" + "%6d directories\n" + "%6d character device files\n" + "%6d block device files\n" + "%6d links\n" + "%6d symbolic links\n" + "------\n" + "%6d files\n", + (ZONES - free_cnt), 100 * (ZONES - free_cnt) / ZONES, + regular, directory, chardev, blockdev, + links - 2 * directory + 1, symlinks, + total - 2 * directory + 1); + } + if (changed) { + write_tables(); + printf("FILE SYSTEM HAS BEEN CHANGED\n"); + sync(); + } else if (repair) + write_super_block(); + + if (repair && !automatic) + tcsetattr(0, TCSANOW, &termios); + + if (changed) + retcode += 3; + if (errors_uncorrected) + retcode += 4; + return retcode; +} diff --git a/util-linux/getopt.c b/util-linux/getopt.c new file mode 100644 index 000000000..64f568aa9 --- /dev/null +++ b/util-linux/getopt.c @@ -0,0 +1,365 @@ +/* vi: set sw=4 ts=4: */ +/* + * getopt.c - Enhanced implementation of BSD getopt(1) + * Copyright (c) 1997, 1998, 1999, 2000 Frodo Looijaard <frodol@dds.nl> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + * Version 1.0-b4: Tue Sep 23 1997. First public release. + * Version 1.0: Wed Nov 19 1997. + * Bumped up the version number to 1.0 + * Fixed minor typo (CSH instead of TCSH) + * Version 1.0.1: Tue Jun 3 1998 + * Fixed sizeof instead of strlen bug + * Bumped up the version number to 1.0.1 + * Version 1.0.2: Thu Jun 11 1998 (not present) + * Fixed gcc-2.8.1 warnings + * Fixed --version/-V option (not present) + * Version 1.0.5: Tue Jun 22 1999 + * Make -u option work (not present) + * Version 1.0.6: Tue Jun 27 2000 + * No important changes + * Version 1.1.0: Tue Jun 30 2000 + * Added NLS support (partly written by Arkadiusz Mi<B6>kiewicz + * <misiek@misiek.eu.org>) + * Ported to Busybox - Alfred M. Szmidt <ams@trillian.itslinux.org> + * Removed --version/-V and --help/-h in + * Removed parse_error(), using bb_error_msg() from Busybox instead + * Replaced our_malloc with xmalloc and our_realloc with xrealloc + * + */ + +#include "busybox.h" +#include <getopt.h> + +/* NON_OPT is the code that is returned when a non-option is found in '+' + mode */ +enum { + NON_OPT = 1, +/* LONG_OPT is the code that is returned when a long option is found. */ + LONG_OPT = 2 +}; + +/* The shells recognized. */ +typedef enum {BASH,TCSH} shell_t; + + +/* Some global variables that tells us how to parse. */ +static shell_t shell=BASH; /* The shell we generate output for. */ +static int quiet_errors; /* 0 is not quiet. */ +static int quiet_output; /* 0 is not quiet. */ +static int quote=1; /* 1 is do quote. */ +static int alternative; /* 0 is getopt_long, 1 is getopt_long_only */ + +/* Function prototypes */ +static const char *normalize(const char *arg); +static int generate_output(char * argv[],int argc,const char *optstr, + const struct option *longopts); +static void add_long_options(char *options); +static void add_longopt(const char *name,int has_arg); +static void set_shell(const char *new_shell); + + +/* + * This function 'normalizes' a single argument: it puts single quotes around + * it and escapes other special characters. If quote is false, it just + * returns its argument. + * Bash only needs special treatment for single quotes; tcsh also recognizes + * exclamation marks within single quotes, and nukes whitespace. + * This function returns a pointer to a buffer that is overwritten by + * each call. + */ +const char *normalize(const char *arg) +{ + static char *BUFFER=NULL; + const char *argptr=arg; + char *bufptr; + + free(BUFFER); + + if (!quote) { /* Just copy arg */ + BUFFER=xstrdup(arg); + return BUFFER; + } + + /* Each character in arg may take up to four characters in the result: + For a quote we need a closing quote, a backslash, a quote and an + opening quote! We need also the global opening and closing quote, + and one extra character for '\0'. */ + BUFFER=xmalloc(strlen(arg)*4+3); + + bufptr=BUFFER; + *bufptr++='\''; + + while (*argptr) { + if (*argptr == '\'') { + /* Quote: replace it with: '\'' */ + *bufptr++='\''; + *bufptr++='\\'; + *bufptr++='\''; + *bufptr++='\''; + } else if (shell==TCSH && *argptr=='!') { + /* Exclamation mark: replace it with: \! */ + *bufptr++='\''; + *bufptr++='\\'; + *bufptr++='!'; + *bufptr++='\''; + } else if (shell==TCSH && *argptr=='\n') { + /* Newline: replace it with: \n */ + *bufptr++='\\'; + *bufptr++='n'; + } else if (shell==TCSH && isspace(*argptr)) { + /* Non-newline whitespace: replace it with \<ws> */ + *bufptr++='\''; + *bufptr++='\\'; + *bufptr++=*argptr; + *bufptr++='\''; + } else + /* Just copy */ + *bufptr++=*argptr; + argptr++; + } + *bufptr++='\''; + *bufptr++='\0'; + return BUFFER; +} + +/* + * Generate the output. argv[0] is the program name (used for reporting errors). + * argv[1..] contains the options to be parsed. argc must be the number of + * elements in argv (ie. 1 if there are no options, only the program name), + * optstr must contain the short options, and longopts the long options. + * Other settings are found in global variables. + */ +int generate_output(char * argv[],int argc,const char *optstr, + const struct option *longopts) +{ + int exit_code = 0; /* We assume everything will be OK */ + int opt; + int longindex; + const char *charptr; + + if (quiet_errors) /* No error reporting from getopt(3) */ + opterr=0; + optind=0; /* Reset getopt(3) */ + + while ((opt = (alternative? + getopt_long_only(argc,argv,optstr,longopts,&longindex): + getopt_long(argc,argv,optstr,longopts,&longindex))) + != EOF) + if (opt == '?' || opt == ':' ) + exit_code = 1; + else if (!quiet_output) { + if (opt == LONG_OPT) { + printf(" --%s",longopts[longindex].name); + if (longopts[longindex].has_arg) + printf(" %s", + normalize(optarg?optarg:"")); + } else if (opt == NON_OPT) + printf(" %s",normalize(optarg)); + else { + printf(" -%c",opt); + charptr = strchr(optstr,opt); + if (charptr != NULL && *++charptr == ':') + printf(" %s", + normalize(optarg?optarg:"")); + } + } + + if (! quiet_output) { + printf(" --"); + while (optind < argc) + printf(" %s",normalize(argv[optind++])); + puts(""); + } + return exit_code; +} + +static struct option *long_options; +static int long_options_length; /* Length of array */ +static int long_options_nr; /* Nr of used elements in array */ +enum { LONG_OPTIONS_INCR = 10 }; +#define init_longopt() add_longopt(NULL,0) + +/* Register a long option. The contents of name is copied. */ +void add_longopt(const char *name, int has_arg) +{ + if (!name) { /* init */ + free(long_options); + long_options=NULL; + long_options_length=0; + long_options_nr=0; + } + + if (long_options_nr == long_options_length) { + long_options_length += LONG_OPTIONS_INCR; + long_options=xrealloc(long_options, + sizeof(struct option) * + long_options_length); + } + + long_options[long_options_nr].name=NULL; + long_options[long_options_nr].has_arg=0; + long_options[long_options_nr].flag=NULL; + long_options[long_options_nr].val=0; + + if (long_options_nr) { /* Not for init! */ + long_options[long_options_nr-1].has_arg=has_arg; + long_options[long_options_nr-1].flag=NULL; + long_options[long_options_nr-1].val=LONG_OPT; + long_options[long_options_nr-1].name=xstrdup(name); + } + long_options_nr++; +} + + +/* + * Register several long options. options is a string of long options, + * separated by commas or whitespace. + * This nukes options! + */ +void add_long_options(char *options) +{ + int arg_opt, tlen; + char *tokptr=strtok(options,", \t\n"); + while (tokptr) { + arg_opt=no_argument; + tlen=strlen(tokptr); + if (tlen > 0) { + if (tokptr[tlen-1] == ':') { + if (tlen > 1 && tokptr[tlen-2] == ':') { + tokptr[tlen-2]='\0'; + tlen -= 2; + arg_opt=optional_argument; + } else { + tokptr[tlen-1]='\0'; + tlen -= 1; + arg_opt=required_argument; + } + if (tlen == 0) + bb_error_msg("empty long option after -l or --long argument"); + } + add_longopt(tokptr,arg_opt); + } + tokptr=strtok(NULL,", \t\n"); + } +} + +void set_shell(const char *new_shell) +{ + if (!strcmp(new_shell,"bash")) + shell=BASH; + else if (!strcmp(new_shell,"tcsh")) + shell=TCSH; + else if (!strcmp(new_shell,"sh")) + shell=BASH; + else if (!strcmp(new_shell,"csh")) + shell=TCSH; + else + bb_error_msg("unknown shell after -s or --shell argument"); +} + + +/* Exit codes: + * 0) No errors, successful operation. + * 1) getopt(3) returned an error. + * 2) A problem with parameter parsing for getopt(1). + * 3) Internal error, out of memory + * 4) Returned for -T + */ + +static const struct option longopts[]= +{ + {"options",required_argument,NULL,'o'}, + {"longoptions",required_argument,NULL,'l'}, + {"quiet",no_argument,NULL,'q'}, + {"quiet-output",no_argument,NULL,'Q'}, + {"shell",required_argument,NULL,'s'}, + {"test",no_argument,NULL,'T'}, + {"unquoted",no_argument,NULL,'u'}, + {"alternative",no_argument,NULL,'a'}, + {"name",required_argument,NULL,'n'}, + {NULL,0,NULL,0} +}; + +/* Stop scanning as soon as a non-option argument is found! */ +static const char shortopts[]="+ao:l:n:qQs:Tu"; + + +int getopt_main(int argc, char *argv[]) +{ + const char *optstr = NULL; + char *name = NULL; + int opt; + int compatible=0; + + init_longopt(); + + if (getenv("GETOPT_COMPATIBLE")) + compatible=1; + + if (argc == 1) { + if (compatible) { + /* For some reason, the original getopt gave no error + when there were no arguments. */ + printf(" --\n"); + return 0; + } else + bb_error_msg_and_die("missing optstring argument"); + } + + if (argv[1][0] != '-' || compatible) { + char *s; + + quote=0; + s=xmalloc(strlen(argv[1])+1); + strcpy(s,argv[1]+strspn(argv[1],"-+")); + argv[1]=argv[0]; + return generate_output(argv+1,argc-1,s,long_options); + } + + while ((opt = getopt_long(argc,argv,shortopts,longopts,NULL)) != EOF) + switch (opt) { + case 'a': + alternative=1; + break; + case 'o': + optstr = optarg; + break; + case 'l': + add_long_options(optarg); + break; + case 'n': + name = optarg; + break; + case 'q': + quiet_errors=1; + break; + case 'Q': + quiet_output=1; + break; + case 's': + set_shell(optarg); + break; + case 'T': + return 4; + case 'u': + quote=0; + break; + default: + bb_show_usage(); + } + + if (!optstr) { + if (optind >= argc) + bb_error_msg_and_die("missing optstring argument"); + else optstr=argv[optind++]; + } + if (name) + argv[optind-1]=name; + else + argv[optind-1]=argv[0]; + return generate_output(argv+optind-1,argc-optind+1,optstr,long_options); +} diff --git a/util-linux/hexdump.c b/util-linux/hexdump.c new file mode 100644 index 000000000..5cb245feb --- /dev/null +++ b/util-linux/hexdump.c @@ -0,0 +1,101 @@ +/* vi: set sw=4 ts=4: */ +/* + * hexdump implementation for busybox + * Based on code from util-linux v 2.11l + * + * Copyright (c) 1989 + * The Regents of the University of California. All rights reserved. + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ + +#include "busybox.h" +#include <getopt.h> +#include "dump.h" + +static void bb_dump_addfile(char *name) +{ + char *p; + FILE *fp; + char *buf; + + fp = xfopen(name, "r"); + + while ((buf = xmalloc_getline(fp)) != NULL) { + p = skip_whitespace(buf); + + if (*p && (*p != '#')) { + bb_dump_add(p); + } + free(buf); + } + fclose(fp); +} + +static const char * const add_strings[] = { + "\"%07.7_ax \" 16/1 \"%03o \" \"\\n\"", /* b */ + "\"%07.7_ax \" 16/1 \"%3_c \" \"\\n\"", /* c */ + "\"%07.7_ax \" 8/2 \" %05u \" \"\\n\"", /* d */ + "\"%07.7_ax \" 8/2 \" %06o \" \"\\n\"", /* o */ + "\"%07.7_ax \" 8/2 \" %04x \" \"\\n\"", /* x */ +}; + +static const char add_first[] = "\"%07.7_Ax\n\""; + +static const char hexdump_opts[] = "bcdoxCe:f:n:s:v"; + +static const struct suffix_mult suffixes[] = { + {"b", 512 }, + {"k", 1024 }, + {"m", 1024*1024 }, + {NULL, 0 } +}; + +int hexdump_main(int argc, char **argv) +{ + const char *p; + int ch; + + bb_dump_vflag = FIRST; + bb_dump_length = -1; + + while ((ch = getopt(argc, argv, hexdump_opts)) > 0) { + p = strchr(hexdump_opts, ch); + if (!p) + bb_show_usage(); + if ((p - hexdump_opts) < 5) { + bb_dump_add(add_first); + bb_dump_add(add_strings[(int)(p - hexdump_opts)]); + } else if (ch == 'C') { + bb_dump_add("\"%08.8_Ax\n\""); + bb_dump_add("\"%08.8_ax \" 8/1 \"%02x \" \" \" 8/1 \"%02x \" "); + bb_dump_add("\" |\" 16/1 \"%_p\" \"|\\n\""); + } else { + /* Save a little bit of space below by omitting the 'else's. */ + if (ch == 'e') { + bb_dump_add(optarg); + } /* else */ + if (ch == 'f') { + bb_dump_addfile(optarg); + } /* else */ + if (ch == 'n') { + bb_dump_length = xatoi_u(optarg); + } /* else */ + if (ch == 's') { + bb_dump_skip = xatoul_range_sfx(optarg, 0, LONG_MAX, suffixes); + } /* else */ + if (ch == 'v') { + bb_dump_vflag = ALL; + } + } + } + + if (!bb_dump_fshead) { + bb_dump_add(add_first); + bb_dump_add("\"%07.7_ax \" 8/2 \"%04x \" \"\\n\""); + } + + argv += optind; + + return bb_dump_dump(argv); +} diff --git a/util-linux/hwclock.c b/util-linux/hwclock.c new file mode 100644 index 000000000..8fcd8c99c --- /dev/null +++ b/util-linux/hwclock.c @@ -0,0 +1,213 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini hwclock implementation for busybox + * + * Copyright (C) 2002 Robert Griebl <griebl@gmx.de> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. +*/ + + +#include <sys/ioctl.h> +#include <sys/utsname.h> +#include <getopt.h> +#include "busybox.h" + +/* Copied from linux/rtc.h to eliminate the kernel dependency */ +struct linux_rtc_time { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +#define RTC_SET_TIME _IOW('p', 0x0a, struct linux_rtc_time) /* Set RTC time */ +#define RTC_RD_TIME _IOR('p', 0x09, struct linux_rtc_time) /* Read RTC time */ + +#if ENABLE_FEATURE_HWCLOCK_LONG_OPTIONS +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +#endif + +static int xopen_rtc(int flags) +{ + int rtc; + rtc = open("/dev/rtc", flags); + if (rtc < 0) { + rtc = open("/dev/misc/rtc", flags); + if (rtc < 0) + bb_perror_msg_and_die("cannot access RTC"); + } + return rtc; +} + +static time_t read_rtc(int utc) +{ + struct tm tm; + char *oldtz = 0; + time_t t = 0; + int rtc = xopen_rtc(O_RDONLY); + + memset(&tm, 0, sizeof(struct tm)); + if (ioctl(rtc, RTC_RD_TIME, &tm) < 0 ) + bb_perror_msg_and_die("cannot read time from RTC"); + tm.tm_isdst = -1; /* not known */ + + close(rtc); + + if (utc) { + oldtz = getenv("TZ"); + setenv("TZ", "UTC 0", 1); + tzset(); + } + + t = mktime(&tm); + + if (utc) { + if (oldtz) + setenv("TZ", oldtz, 1); + else + unsetenv("TZ"); + tzset(); + } + return t; +} + +static void write_rtc(time_t t, int utc) +{ + struct tm tm; + int rtc = xopen_rtc(O_WRONLY); + + tm = *(utc ? gmtime(&t) : localtime(&t)); + tm.tm_isdst = 0; + + if (ioctl(rtc, RTC_SET_TIME, &tm) < 0) + bb_perror_msg_and_die("cannot set the RTC time"); + + close(rtc); +} + +static int show_clock(int utc) +{ + struct tm *ptm; + time_t t; + RESERVE_CONFIG_BUFFER(buffer, 64); + + t = read_rtc(utc); + ptm = localtime(&t); /* Sets 'tzname[]' */ + + safe_strncpy(buffer, ctime(&t), 64); + if (buffer[0]) + buffer[strlen(buffer) - 1] = 0; + + //printf("%s %.6f seconds %s\n", buffer, 0.0, utc ? "" : (ptm->tm_isdst ? tzname[1] : tzname[0])); + printf( "%s %.6f seconds\n", buffer, 0.0); + RELEASE_CONFIG_BUFFER(buffer); + + return 0; +} + +static int to_sys_clock(int utc) +{ + struct timeval tv = { 0, 0 }; + const struct timezone tz = { timezone/60 - 60*daylight, 0 }; + + tv.tv_sec = read_rtc(utc); + + if (settimeofday(&tv, &tz)) + bb_perror_msg_and_die("settimeofday() failed"); + + return 0; +} + +static int from_sys_clock(int utc) +{ + struct timeval tv = { 0, 0 }; + struct timezone tz = { 0, 0 }; + + if (gettimeofday(&tv, &tz)) + bb_perror_msg_and_die("gettimeofday() failed"); + + write_rtc(tv.tv_sec, utc); + return 0; +} + +#ifdef CONFIG_FEATURE_HWCLOCK_ADJTIME_FHS +# define ADJTIME_PATH "/var/lib/hwclock/adjtime" +#else +# define ADJTIME_PATH "/etc/adjtime" +#endif +static int check_utc(void) +{ + int utc = 0; + FILE *f = fopen(ADJTIME_PATH, "r"); + + if (f) { + RESERVE_CONFIG_BUFFER(buffer, 128); + + while (fgets(buffer, sizeof(buffer), f)) { + int len = strlen(buffer); + + while (len && isspace(buffer[len - 1])) + len--; + + buffer[len] = 0; + + if (strncmp(buffer, "UTC", 3) == 0 ) { + utc = 1; + break; + } + } + fclose(f); + RELEASE_CONFIG_BUFFER(buffer); + } + return utc; +} + +#define HWCLOCK_OPT_LOCALTIME 0x01 +#define HWCLOCK_OPT_UTC 0x02 +#define HWCLOCK_OPT_SHOW 0x04 +#define HWCLOCK_OPT_HCTOSYS 0x08 +#define HWCLOCK_OPT_SYSTOHC 0x10 + +int hwclock_main(int argc, char **argv ) +{ + unsigned opt; + int utc; + +#if ENABLE_FEATURE_HWCLOCK_LONG_OPTIONS + static const struct option hwclock_long_options[] = { + { "localtime", 0, 0, 'l' }, + { "utc", 0, 0, 'u' }, + { "show", 0, 0, 'r' }, + { "hctosys", 0, 0, 's' }, + { "systohc", 0, 0, 'w' }, + { 0, 0, 0, 0 } + }; + applet_long_options = hwclock_long_options; +#endif + opt_complementary = "?:r--ws:w--rs:s--wr:l--u:u--l"; + opt = getopt32(argc, argv, "lursw"); + + /* If -u or -l wasn't given check if we are using utc */ + if (opt & (HWCLOCK_OPT_UTC | HWCLOCK_OPT_LOCALTIME)) + utc = opt & HWCLOCK_OPT_UTC; + else + utc = check_utc(); + + if (opt & HWCLOCK_OPT_HCTOSYS) { + return to_sys_clock(utc); + } + else if (opt & HWCLOCK_OPT_SYSTOHC) { + return from_sys_clock(utc); + } else { + /* default HWCLOCK_OPT_SHOW */ + return show_clock(utc); + } +} diff --git a/util-linux/ipcrm.c b/util-linux/ipcrm.c new file mode 100644 index 000000000..507e58fe3 --- /dev/null +++ b/util-linux/ipcrm.c @@ -0,0 +1,217 @@ +/* vi: set sw=4 ts=4: */ +/* + * ipcrm.c - utility to allow removal of IPC objects and data structures. + * + * 01 Sept 2004 - Rodney Radford <rradford@mindspring.com> + * Adapted for busybox from util-linux-2.12a. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* X/OPEN tells us to use <sys/{types,ipc,sem}.h> for semctl() */ +/* X/OPEN tells us to use <sys/{types,ipc,msg}.h> for msgctl() */ +#include <sys/ipc.h> +#include <sys/shm.h> +#include <sys/msg.h> +#include <sys/sem.h> + +#if defined (__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) +/* union semun is defined by including <sys/sem.h> */ +#else +/* according to X/OPEN we have to define it ourselves */ +union semun { + int val; + struct semid_ds *buf; + unsigned short int *array; + struct seminfo *__buf; +}; +#endif + +#ifndef CONFIG_IPCRM_DROP_LEGACY + +typedef enum type_id { + SHM, + SEM, + MSG +} type_id; + +static int remove_ids(type_id type, int argc, char **argv) +{ + unsigned long id; + int ret = 0; /* silence gcc */ + int nb_errors = 0; + union semun arg; + + arg.val = 0; + + while (argc) { + id = bb_strtoul(argv[0], NULL, 10); + if (errno || id > INT_MAX) { + bb_error_msg("invalid id: %s", argv[0]); + nb_errors++; + } else { + if (type == SEM) + ret = semctl(id, 0, IPC_RMID, arg); + else if (type == MSG) + ret = msgctl(id, IPC_RMID, NULL); + else if (type == SHM) + ret = shmctl(id, IPC_RMID, NULL); + + if (ret) { + bb_perror_msg("cannot remove id %s", argv[0]); + nb_errors++; + } + } + argc--; + argv++; + } + + return nb_errors; +} +#endif /* #ifndef CONFIG_IPCRM_DROP_LEGACY */ + + +int ipcrm_main(int argc, char **argv) +{ + int c; + int error = 0; + + /* if the command is executed without parameters, do nothing */ + if (argc == 1) + return 0; +#ifndef CONFIG_IPCRM_DROP_LEGACY + /* check to see if the command is being invoked in the old way if so + then run the old code. Valid commands are msg, shm, sem. */ + { + type_id what = 0; /* silence gcc */ + char w; + + w=argv[1][0]; + if ( ((w == 'm' && argv[1][1] == 's' && argv[1][2] == 'g') + || (argv[1][0] == 's' + && ((w=argv[1][1]) == 'h' || w == 'e') + && argv[1][2] == 'm') + ) && argv[1][3] == '\0' + ) { + + if (argc < 3) + bb_show_usage(); + + if (w == 'h') + what = SHM; + else if (w == 'm') + what = MSG; + else if (w == 'e') + what = SEM; + + if (remove_ids(what, argc-2, &argv[2])) + fflush_stdout_and_exit(1); + printf("resource(s) deleted\n"); + return 0; + } + } +#endif /* #ifndef CONFIG_IPCRM_DROP_LEGACY */ + + /* process new syntax to conform with SYSV ipcrm */ + while ((c = getopt(argc, argv, "q:m:s:Q:M:S:h?")) != -1) { + int result; + int id = 0; + int iskey = (isupper)(c); + + /* needed to delete semaphores */ + union semun arg; + + arg.val = 0; + + if ((c == '?') || (c == 'h')) { + bb_show_usage(); + } + + /* we don't need case information any more */ + c = tolower(c); + + /* make sure the option is in range: allowed are q, m, s */ + if (c != 'q' && c != 'm' && c != 's') { + bb_show_usage(); + } + + if (iskey) { + /* keys are in hex or decimal */ + key_t key = xstrtoul(optarg, 0); + + if (key == IPC_PRIVATE) { + error++; + bb_error_msg("illegal key (%s)", optarg); + continue; + } + + /* convert key to id */ + id = ((c == 'q') ? msgget(key, 0) : + (c == 'm') ? shmget(key, 0, 0) : semget(key, 0, 0)); + + if (id < 0) { + char *errmsg; + const char * const what = "key"; + + error++; + switch (errno) { + case EACCES: + errmsg = "permission denied for"; + break; + case EIDRM: + errmsg = "already removed"; + break; + case ENOENT: + errmsg = "invalid"; + break; + default: + errmsg = "unknown error in"; + break; + } + bb_error_msg("%s %s (%s)", errmsg, what, optarg); + continue; + } + } else { + /* ids are in decimal */ + id = xatoul(optarg); + } + + result = ((c == 'q') ? msgctl(id, IPC_RMID, NULL) : + (c == 'm') ? shmctl(id, IPC_RMID, NULL) : + semctl(id, 0, IPC_RMID, arg)); + + if (result) { + char *errmsg; + const char * const what = iskey ? "key" : "id"; + + error++; + switch (errno) { + case EACCES: + case EPERM: + errmsg = "permission denied for"; + break; + case EINVAL: + errmsg = "invalid"; + break; + case EIDRM: + errmsg = "already removed"; + break; + default: + errmsg = "unknown error in"; + break; + } + bb_error_msg("%s %s (%s)", errmsg, what, optarg); + continue; + } + } + + /* print usage if we still have some arguments left over */ + if (optind != argc) { + bb_show_usage(); + } + + /* exit value reflects the number of errors encountered */ + return error; +} diff --git a/util-linux/ipcs.c b/util-linux/ipcs.c new file mode 100644 index 000000000..b81d07d6d --- /dev/null +++ b/util-linux/ipcs.c @@ -0,0 +1,630 @@ +/* vi: set sw=4 ts=4: */ +/* + * ipcs.c -- provides information on allocated ipc resources. + * + * 01 Sept 2004 - Rodney Radford <rradford@mindspring.com> + * Adapted for busybox from util-linux-2.12a. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <errno.h> +#include <time.h> +#include <pwd.h> +#include <grp.h> + +/* X/OPEN tells us to use <sys/{types,ipc,sem}.h> for semctl() */ +/* X/OPEN tells us to use <sys/{types,ipc,msg}.h> for msgctl() */ +/* X/OPEN tells us to use <sys/{types,ipc,shm}.h> for shmctl() */ +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/sem.h> +#include <sys/msg.h> +#include <sys/shm.h> + + + +/*-------------------------------------------------------------------*/ +/* SHM_DEST and SHM_LOCKED are defined in kernel headers, + but inside #ifdef __KERNEL__ ... #endif */ +#ifndef SHM_DEST +/* shm_mode upper byte flags */ +#define SHM_DEST 01000 /* segment will be destroyed on last detach */ +#define SHM_LOCKED 02000 /* segment will not be swapped */ +#endif + +/* For older kernels the same holds for the defines below */ +#ifndef MSG_STAT +#define MSG_STAT 11 +#define MSG_INFO 12 +#endif + +#ifndef SHM_STAT +#define SHM_STAT 13 +#define SHM_INFO 14 +struct shm_info { + int used_ids; + ulong shm_tot; /* total allocated shm */ + ulong shm_rss; /* total resident shm */ + ulong shm_swp; /* total swapped shm */ + ulong swap_attempts; + ulong swap_successes; +}; +#endif + +#ifndef SEM_STAT +#define SEM_STAT 18 +#define SEM_INFO 19 +#endif + +/* Some versions of libc only define IPC_INFO when __USE_GNU is defined. */ +#ifndef IPC_INFO +#define IPC_INFO 3 +#endif +/*-------------------------------------------------------------------*/ + +/* The last arg of semctl is a union semun, but where is it defined? + X/OPEN tells us to define it ourselves, but until recently + Linux include files would also define it. */ +#if defined (__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) +/* union semun is defined by including <sys/sem.h> */ +#else +/* according to X/OPEN we have to define it ourselves */ +union semun { + int val; + struct semid_ds *buf; + unsigned short int *array; + struct seminfo *__buf; +}; +#endif + +/* X/OPEN (Jan 1987) does not define fields key, seq in struct ipc_perm; + libc 4/5 does not mention struct ipc_term at all, but includes + <linux/ipc.h>, which defines a struct ipc_perm with such fields. + glibc-1.09 has no support for sysv ipc. + glibc 2 uses __key, __seq */ +#if defined (__GNU_LIBRARY__) && __GNU_LIBRARY__ > 1 +#define KEY __key +#else +#define KEY key +#endif + +#define LIMITS 1 +#define STATUS 2 +#define CREATOR 3 +#define TIME 4 +#define PID 5 + +static char format; + +static void print_perms(int id, struct ipc_perm *ipcp) +{ + struct passwd *pw; + struct group *gr; + + printf("%-10d %-10o", id, ipcp->mode & 0777); + + if ((pw = getpwuid(ipcp->cuid))) + printf(" %-10s", pw->pw_name); + else + printf(" %-10d", ipcp->cuid); + if ((gr = getgrgid(ipcp->cgid))) + printf(" %-10s", gr->gr_name); + else + printf(" %-10d", ipcp->cgid); + + if ((pw = getpwuid(ipcp->uid))) + printf(" %-10s", pw->pw_name); + else + printf(" %-10d", ipcp->uid); + if ((gr = getgrgid(ipcp->gid))) + printf(" %-10s\n", gr->gr_name); + else + printf(" %-10d\n", ipcp->gid); +} + + +static void do_shm(void) +{ + int maxid, shmid, id; + struct shmid_ds shmseg; + struct shm_info shm_info; + struct shminfo shminfo; + struct ipc_perm *ipcp = &shmseg.shm_perm; + struct passwd *pw; + + maxid = shmctl(0, SHM_INFO, (struct shmid_ds *) (void *) &shm_info); + if (maxid < 0) { + printf("kernel not configured for %s\n", "shared memory"); + return; + } + + switch (format) { + case LIMITS: + printf("------ Shared Memory %s --------\n", "Limits"); + if ((shmctl(0, IPC_INFO, (struct shmid_ds *) (void *) &shminfo)) < 0) + return; + /* glibc 2.1.3 and all earlier libc's have ints as fields + of struct shminfo; glibc 2.1.91 has unsigned long; ach */ + printf("max number of segments = %lu\n" + "max seg size (kbytes) = %lu\n" + "max total shared memory (pages) = %lu\n" + "min seg size (bytes) = %lu\n", + (unsigned long) shminfo.shmmni, + (unsigned long) (shminfo.shmmax >> 10), + (unsigned long) shminfo.shmall, + (unsigned long) shminfo.shmmin); + return; + + case STATUS: + printf("------ Shared Memory %s --------\n", "Status"); + printf( "segments allocated %d\n" + "pages allocated %ld\n" + "pages resident %ld\n" + "pages swapped %ld\n" + "Swap performance: %ld attempts\t%ld successes\n", + shm_info.used_ids, + shm_info.shm_tot, + shm_info.shm_rss, + shm_info.shm_swp, + shm_info.swap_attempts, shm_info.swap_successes); + return; + + case CREATOR: + printf("------ Shared Memory %s --------\n", "Segment Creators/Owners"); + printf( "%-10s %-10s %-10s %-10s %-10s %-10s\n", + "shmid", "perms", "cuid", "cgid", "uid", "gid"); + break; + + case TIME: + printf("------ Shared Memory %s --------\n", "Attach/Detach/Change Times"); + printf( "%-10s %-10s %-20s %-20s %-20s\n", + "shmid", "owner", "attached", "detached", "changed"); + break; + + case PID: + printf("------ Shared Memory %s --------\n", "Creator/Last-op"); + printf( "%-10s %-10s %-10s %-10s\n", + "shmid", "owner", "cpid", "lpid"); + break; + + default: + printf("------ Shared Memory %s --------\n", "Segments"); + printf( "%-10s %-10s %-10s %-10s %-10s %-10s %-12s\n", + "key", "shmid", "owner", "perms", "bytes", "nattch", + "status"); + break; + } + + for (id = 0; id <= maxid; id++) { + shmid = shmctl(id, SHM_STAT, &shmseg); + if (shmid < 0) + continue; + if (format == CREATOR) { + print_perms(shmid, ipcp); + continue; + } + pw = getpwuid(ipcp->uid); + switch (format) { + case TIME: + if (pw) + printf("%-10d %-10.10s", shmid, pw->pw_name); + else + printf("%-10d %-10d", shmid, ipcp->uid); + /* ctime uses static buffer: use separate calls */ + printf(" %-20.16s", shmseg.shm_atime + ? ctime(&shmseg.shm_atime) + 4 : "Not set"); + printf(" %-20.16s", shmseg.shm_dtime + ? ctime(&shmseg.shm_dtime) + 4 : "Not set"); + printf(" %-20.16s\n", shmseg.shm_ctime + ? ctime(&shmseg.shm_ctime) + 4 : "Not set"); + break; + case PID: + if (pw) + printf("%-10d %-10.10s", shmid, pw->pw_name); + else + printf("%-10d %-10d", shmid, ipcp->uid); + printf(" %-10d %-10d\n", shmseg.shm_cpid, shmseg.shm_lpid); + break; + + default: + printf("0x%08x ", ipcp->KEY); + if (pw) + printf("%-10d %-10.10s", shmid, pw->pw_name); + else + printf("%-10d %-10d", shmid, ipcp->uid); + printf(" %-10o %-10lu %-10ld %-6s %-6s\n", ipcp->mode & 0777, + /* + * earlier: int, Austin has size_t + */ + (unsigned long) shmseg.shm_segsz, + /* + * glibc-2.1.3 and earlier has unsigned short; + * Austin has shmatt_t + */ + (long) shmseg.shm_nattch, + ipcp->mode & SHM_DEST ? "dest" : " ", + ipcp->mode & SHM_LOCKED ? "locked" : " "); + break; + } + } +} + + +static void do_sem(void) +{ + int maxid, semid, id; + struct semid_ds semary; + struct seminfo seminfo; + struct ipc_perm *ipcp = &semary.sem_perm; + struct passwd *pw; + union semun arg; + + arg.array = (ushort *) (void *) &seminfo; + maxid = semctl(0, 0, SEM_INFO, arg); + if (maxid < 0) { + printf("kernel not configured for %s\n", "semaphores"); + return; + } + + switch (format) { + case LIMITS: + printf("------ Semaphore %s --------\n", "Limits"); + arg.array = (ushort *) (void *) &seminfo; /* damn union */ + if ((semctl(0, 0, IPC_INFO, arg)) < 0) + return; + printf("max number of arrays = %d\n" + "max semaphores per array = %d\n" + "max semaphores system wide = %d\n" + "max ops per semop call = %d\n" + "semaphore max value = %d\n", + seminfo.semmni, + seminfo.semmsl, + seminfo.semmns, seminfo.semopm, seminfo.semvmx); + return; + + case STATUS: + printf("------ Semaphore %s --------\n", "Status"); + printf( "used arrays = %d\n" + "allocated semaphores = %d\n", + seminfo.semusz, seminfo.semaem); + return; + + case CREATOR: + printf("------ Semaphore %s --------\n", "Arrays Creators/Owners"); + printf( "%-10s %-10s %-10s %-10s %-10s %-10s\n", + "semid", "perms", "cuid", "cgid", "uid", "gid"); + break; + + case TIME: + printf("------ Shared Memory %s --------\n", "Operation/Change Times"); + printf( "%-8s %-10s %-26.24s %-26.24s\n", + "shmid", "owner", "last-op", "last-changed"); + break; + + case PID: + break; + + default: + printf("------ Semaphore %s --------\n", "Arrays"); + printf( "%-10s %-10s %-10s %-10s %-10s\n", + "key", "semid", "owner", "perms", "nsems"); + break; + } + + for (id = 0; id <= maxid; id++) { + arg.buf = (struct semid_ds *) &semary; + semid = semctl(id, 0, SEM_STAT, arg); + if (semid < 0) + continue; + if (format == CREATOR) { + print_perms(semid, ipcp); + continue; + } + pw = getpwuid(ipcp->uid); + switch (format) { + case TIME: + if (pw) + printf("%-8d %-10.10s", semid, pw->pw_name); + else + printf("%-8d %-10d", semid, ipcp->uid); + /* ctime uses static buffer: use separate calls */ + printf(" %-26.24s", semary.sem_otime + ? ctime(&semary.sem_otime) : "Not set"); + printf(" %-26.24s\n", semary.sem_ctime + ? ctime(&semary.sem_ctime) : "Not set"); + break; + case PID: + break; + + default: + printf("0x%08x ", ipcp->KEY); + if (pw) + printf("%-10d %-10.9s", semid, pw->pw_name); + else + printf("%-10d %-9d", semid, ipcp->uid); + printf(" %-10o %-10ld\n", ipcp->mode & 0777, + /* + * glibc-2.1.3 and earlier has unsigned short; + * glibc-2.1.91 has variation between + * unsigned short and unsigned long + * Austin prescribes unsigned short. + */ + (long) semary.sem_nsems); + break; + } + } +} + + +static void do_msg(void) +{ + int maxid, msqid, id; + struct msqid_ds msgque; + struct msginfo msginfo; + struct ipc_perm *ipcp = &msgque.msg_perm; + struct passwd *pw; + + maxid = msgctl(0, MSG_INFO, (struct msqid_ds *) (void *) &msginfo); + if (maxid < 0) { + printf("kernel not configured for %s\n", "message queues"); + return; + } + + switch (format) { + case LIMITS: + if ((msgctl(0, IPC_INFO, (struct msqid_ds *) (void *) &msginfo)) < 0) + return; + printf("------ Message%s --------\n", "s: Limits"); + printf( "max queues system wide = %d\n" + "max size of message (bytes) = %d\n" + "default max size of queue (bytes) = %d\n", + msginfo.msgmni, msginfo.msgmax, msginfo.msgmnb); + return; + + case STATUS: + printf("------ Message%s --------\n", "s: Status"); + printf( "allocated queues = %d\n" + "used headers = %d\n" + "used space = %d bytes\n", + msginfo.msgpool, msginfo.msgmap, msginfo.msgtql); + return; + + case CREATOR: + printf("------ Message%s --------\n", " Queues: Creators/Owners"); + printf( "%-10s %-10s %-10s %-10s %-10s %-10s\n", + "msqid", "perms", "cuid", "cgid", "uid", "gid"); + break; + + case TIME: + printf("------ Message%s --------\n", " Queues Send/Recv/Change Times"); + printf( "%-8s %-10s %-20s %-20s %-20s\n", + "msqid", "owner", "send", "recv", "change"); + break; + + case PID: + printf("------ Message%s --------\n", " Queues PIDs"); + printf( "%-10s %-10s %-10s %-10s\n", + "msqid", "owner", "lspid", "lrpid"); + break; + + default: + printf("------ Message%s --------\n", " Queues"); + printf( "%-10s %-10s %-10s %-10s %-12s %-12s\n", + "key", "msqid", "owner", "perms", "used-bytes", "messages"); + break; + } + + for (id = 0; id <= maxid; id++) { + msqid = msgctl(id, MSG_STAT, &msgque); + if (msqid < 0) + continue; + if (format == CREATOR) { + print_perms(msqid, ipcp); + continue; + } + pw = getpwuid(ipcp->uid); + switch (format) { + case TIME: + if (pw) + printf("%-8d %-10.10s", msqid, pw->pw_name); + else + printf("%-8d %-10d", msqid, ipcp->uid); + printf(" %-20.16s", msgque.msg_stime + ? ctime(&msgque.msg_stime) + 4 : "Not set"); + printf(" %-20.16s", msgque.msg_rtime + ? ctime(&msgque.msg_rtime) + 4 : "Not set"); + printf(" %-20.16s\n", msgque.msg_ctime + ? ctime(&msgque.msg_ctime) + 4 : "Not set"); + break; + case PID: + if (pw) + printf("%-8d %-10.10s", msqid, pw->pw_name); + else + printf("%-8d %-10d", msqid, ipcp->uid); + printf(" %5d %5d\n", msgque.msg_lspid, msgque.msg_lrpid); + break; + + default: + printf("0x%08x ", ipcp->KEY); + if (pw) + printf("%-10d %-10.10s", msqid, pw->pw_name); + else + printf("%-10d %-10d", msqid, ipcp->uid); + printf(" %-10o %-12ld %-12ld\n", ipcp->mode & 0777, + /* + * glibc-2.1.3 and earlier has unsigned short; + * glibc-2.1.91 has variation between + * unsigned short, unsigned long + * Austin has msgqnum_t + */ + (long) msgque.msg_cbytes, (long) msgque.msg_qnum); + break; + } + } +} + + +static void print_shm(int shmid) +{ + struct shmid_ds shmds; + struct ipc_perm *ipcp = &shmds.shm_perm; + + if (shmctl(shmid, IPC_STAT, &shmds) == -1) { + bb_perror_msg("shmctl"); + return; + } + + printf("\nShared memory Segment shmid=%d\n" + "uid=%d\tgid=%d\tcuid=%d\tcgid=%d\n" + "mode=%#o\taccess_perms=%#o\n" + "bytes=%ld\tlpid=%d\tcpid=%d\tnattch=%ld\n", + shmid, + ipcp->uid, ipcp->gid, ipcp->cuid, ipcp->cgid, + ipcp->mode, ipcp->mode & 0777, + (long) shmds.shm_segsz, shmds.shm_lpid, shmds.shm_cpid, + (long) shmds.shm_nattch); + printf("att_time=%-26.24s\n", + shmds.shm_atime ? ctime(&shmds.shm_atime) : "Not set"); + printf("det_time=%-26.24s\n", + shmds.shm_dtime ? ctime(&shmds.shm_dtime) : "Not set"); + printf("change_time=%-26.24s\n\n", ctime(&shmds.shm_ctime)); +} + + +static void print_msg(int msqid) +{ + struct msqid_ds buf; + struct ipc_perm *ipcp = &buf.msg_perm; + + if (msgctl(msqid, IPC_STAT, &buf) == -1) { + bb_perror_msg("msgctl"); + return; + } + + printf("\nMessage Queue msqid=%d\n" + "uid=%d\tgid=%d\tcuid=%d\tcgid=%d\tmode=%#o\n" + "cbytes=%ld\tqbytes=%ld\tqnum=%ld\tlspid=%d\tlrpid=%d\n", + msqid, ipcp->uid, ipcp->gid, ipcp->cuid, ipcp->cgid, ipcp->mode, + /* + * glibc-2.1.3 and earlier has unsigned short; + * glibc-2.1.91 has variation between + * unsigned short, unsigned long + * Austin has msgqnum_t (for msg_qbytes) + */ + (long) buf.msg_cbytes, (long) buf.msg_qbytes, + (long) buf.msg_qnum, buf.msg_lspid, buf.msg_lrpid); + + printf("send_time=%-26.24s\n", + buf.msg_stime ? ctime(&buf.msg_stime) : "Not set"); + printf("rcv_time=%-26.24s\n", + buf.msg_rtime ? ctime(&buf.msg_rtime) : "Not set"); + printf("change_time=%-26.24s\n\n", + buf.msg_ctime ? ctime(&buf.msg_ctime) : "Not set"); +} + +static void print_sem(int semid) +{ + struct semid_ds semds; + struct ipc_perm *ipcp = &semds.sem_perm; + union semun arg; + unsigned int i; + + arg.buf = &semds; + if (semctl(semid, 0, IPC_STAT, arg)) { + bb_perror_msg("semctl"); + return; + } + + printf("\nSemaphore Array semid=%d\n" + "uid=%d\t gid=%d\t cuid=%d\t cgid=%d\n" + "mode=%#o, access_perms=%#o\n" + "nsems = %ld\n" + "otime = %-26.24s\n", + semid, + ipcp->uid, ipcp->gid, ipcp->cuid, ipcp->cgid, + ipcp->mode, ipcp->mode & 0777, + (long) semds.sem_nsems, + semds.sem_otime ? ctime(&semds.sem_otime) : "Not set"); + printf("ctime = %-26.24s\n" + "%-10s %-10s %-10s %-10s %-10s\n", + ctime(&semds.sem_ctime), + "semnum", "value", "ncount", "zcount", "pid"); + + arg.val = 0; + for (i = 0; i < semds.sem_nsems; i++) { + int val, ncnt, zcnt, pid; + + val = semctl(semid, i, GETVAL, arg); + ncnt = semctl(semid, i, GETNCNT, arg); + zcnt = semctl(semid, i, GETZCNT, arg); + pid = semctl(semid, i, GETPID, arg); + if (val < 0 || ncnt < 0 || zcnt < 0 || pid < 0) { + bb_perror_msg_and_die("semctl"); + } + printf("%-10d %-10d %-10d %-10d %-10d\n", i, val, ncnt, zcnt, pid); + } + puts(""); +} + +int ipcs_main(int argc, char **argv) +{ + int id = 0; + unsigned flags = 0; + unsigned opt; + char *opt_i; +#define flag_print (1<<0) +#define flag_msg (1<<1) +#define flag_sem (1<<2) +#define flag_shm (1<<3) + + opt = getopt32(argc, argv, "i:aqsmtcplu", &opt_i); + if (opt & 0x1) { // -i + id = xatoi(opt_i); + flags |= flag_print; + } + if (opt & 0x2) flags |= flag_msg | flag_sem | flag_shm; // -a + if (opt & 0x4) flags |= flag_msg; // -q + if (opt & 0x8) flags |= flag_sem; // -s + if (opt & 0x10) flags |= flag_shm; // -m + if (opt & 0x20) format = TIME; // -t + if (opt & 0x40) format = CREATOR; // -c + if (opt & 0x80) format = PID; // -p + if (opt & 0x100) format = LIMITS; // -l + if (opt & 0x200) format = STATUS; // -u + + if (flags & flag_print) { + if (flags & flag_shm) { + print_shm(id); + fflush_stdout_and_exit(0); + } + if (flags & flag_sem) { + print_sem(id); + fflush_stdout_and_exit(0); + } + if (flags & flag_msg) { + print_msg(id); + fflush_stdout_and_exit(0); + } + bb_show_usage(); + } + + if (!(flags & (flag_shm | flag_msg | flag_sem))) + flags |= flag_msg | flag_shm | flag_sem; + puts(""); + + if (flags & flag_shm) { + do_shm(); + puts(""); + } + if (flags & flag_sem) { + do_sem(); + puts(""); + } + if (flags & flag_msg) { + do_msg(); + puts(""); + } + fflush_stdout_and_exit(0); +} diff --git a/util-linux/losetup.c b/util-linux/losetup.c new file mode 100644 index 000000000..c7eb85a91 --- /dev/null +++ b/util-linux/losetup.c @@ -0,0 +1,64 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini losetup implementation for busybox + * + * Copyright (C) 2002 Matt Kraai. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include <getopt.h> +#include <stdlib.h> + +#include "busybox.h" + +int losetup_main(int argc, char **argv) +{ + unsigned opt; + char *opt_o; + unsigned long long offset = 0; + + opt = getopt32(argc, argv, "do:", &opt_o); + argc -= optind; + argv += optind; + + if (opt == 0x3) // -d + -o (illegal) + bb_show_usage(); + + if (opt == 0x1) { // -d + /* detach takes exactly one argument */ + if (argc != 1) + bb_show_usage(); + if (!del_loop(argv[0])) + return EXIT_SUCCESS; + bb_perror_nomsg_and_die(); + } + + if (opt == 0x2) // -o + offset = xatoull(opt_o); + + /* -o or no option */ + + if (argc == 2) { + if (set_loop(&argv[0], argv[1], offset) < 0) + bb_perror_nomsg_and_die(); + } else if (argc == 1) { + char *s = query_loop(argv[0]); + if (!s) bb_perror_nomsg_and_die(); + printf("%s: %s\n", argv[0], s); + if (ENABLE_FEATURE_CLEAN_UP) free(s); + } else { + char dev[sizeof(LOOP_NAME"0")] = LOOP_NAME"0"; + char c; + for (c = '0'; c <= '9'; ++c) { + char *s; + dev[sizeof(LOOP_NAME"0")-2] = c; + s = query_loop(dev); + if (s) { + printf("%s: %s\n", dev, s); + if (ENABLE_FEATURE_CLEAN_UP) free(s); + } + } + } + return EXIT_SUCCESS; +} diff --git a/util-linux/mdev.c b/util-linux/mdev.c new file mode 100644 index 000000000..957316d52 --- /dev/null +++ b/util-linux/mdev.c @@ -0,0 +1,268 @@ +/* vi: set sw=4 ts=4: */ +/* + * + * mdev - Mini udev for busybox + * + * Copyright 2005 Rob Landley <rob@landley.net> + * Copyright 2005 Frank Sorenson <frank@tuxrocks.com> + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "xregex.h" + +#define DEV_PATH "/dev" + +struct mdev_globals +{ + int root_major, root_minor; +} mdev_globals; + +#define bbg mdev_globals + +/* mknod in /dev based on a path like "/sys/block/hda/hda1" */ +static void make_device(char *path, int delete) +{ + char *device_name; + int major, minor, type, len; + int mode = 0660; + uid_t uid = 0; + gid_t gid = 0; + char *temp = path + strlen(path); + char *command = NULL; + + /* Try to read major/minor string. Note that the kernel puts \n after + * the data, so we don't need to worry about null terminating the string + * because sscanf() will stop at the first nondigit, which \n is. We + * also depend on path having writeable space after it. */ + + if (!delete) { + strcat(path, "/dev"); + len = open_read_close(path, temp + 1, 64); + *temp++ = 0; + if (len < 1) return; + } + + /* Determine device name, type, major and minor */ + + device_name = strrchr(path, '/') + 1; + type = path[5]=='c' ? S_IFCHR : S_IFBLK; + + /* If we have a config file, look up permissions for this device */ + + if (ENABLE_FEATURE_MDEV_CONF) { + char *conf, *pos, *end; + int line, fd; + + /* mmap the config file */ + fd = open("/etc/mdev.conf", O_RDONLY); + if (fd < 0) + goto end_parse; + len = xlseek(fd, 0, SEEK_END); + conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (!conf) + goto end_parse; + + line = 0; + /* Loop through lines in mmaped file*/ + for (pos=conf; pos-conf<len;) { + int field; + char *end2; + + line++; + /* find end of this line */ + for (end=pos; end-conf<len && *end!='\n'; end++) + ; + + /* Three fields: regex, uid:gid, mode */ + for (field=0; field < (3 + ENABLE_FEATURE_MDEV_EXEC); + field++) + { + /* Skip whitespace */ + while (pos<end && isspace(*pos)) pos++; + if (pos==end || *pos=='#') break; + for (end2=pos; + end2<end && !isspace(*end2) && *end2!='#'; end2++) + ; + + if (field == 0) { + /* Regex to match this device */ + + char *regex = strndupa(pos, end2-pos); + regex_t match; + regmatch_t off; + int result; + + /* Is this it? */ + xregcomp(&match,regex, REG_EXTENDED); + result = regexec(&match, device_name, 1, &off, 0); + regfree(&match); + + /* If not this device, skip rest of line */ + if (result || off.rm_so + || off.rm_eo != strlen(device_name)) + break; + } + if (field == 1) { + /* uid:gid */ + + char *s, *s2; + + /* Find : */ + for (s=pos; s<end2 && *s!=':'; s++) + ; + if (s == end2) break; + + /* Parse UID */ + uid = strtoul(pos, &s2, 10); + if (s != s2) { + struct passwd *pass; + pass = getpwnam(strndupa(pos, s-pos)); + if (!pass) break; + uid = pass->pw_uid; + } + s++; + /* parse GID */ + gid = strtoul(s, &s2, 10); + if (end2 != s2) { + struct group *grp; + grp = getgrnam(strndupa(s, end2-s)); + if (!grp) break; + gid = grp->gr_gid; + } + } + if (field == 2) { + /* mode */ + + mode = strtoul(pos, &pos, 8); + if (pos != end2) break; + } + if (ENABLE_FEATURE_MDEV_EXEC && field == 3) { + // Command to run + char *s = "@$*", *s2; + s2 = strchr(s, *pos++); + if (!s2) { + // Force error + field = 1; + break; + } + if ((s2-s+1) & (1<<delete)) + command = xstrndup(pos, end-pos); + } + + pos = end2; + } + + /* Did everything parse happily? */ + + if (field > 2) break; + if (field) bb_error_msg_and_die("bad line %d",line); + + /* Next line */ + pos = ++end; + } + munmap(conf, len); + end_parse: /* nothing */ ; + } + + umask(0); + if (!delete) { + if (sscanf(temp, "%d:%d", &major, &minor) != 2) return; + if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST) + bb_perror_msg_and_die("mknod %s", device_name); + + if (major == bbg.root_major && minor == bbg.root_minor) + symlink(device_name, "root"); + + if (ENABLE_FEATURE_MDEV_CONF) chown(device_name, uid, gid); + } + if (command) { + int rc; + char *s; + + s = xasprintf("MDEV=%s", device_name); + putenv(s); + rc = system(command); + s[4] = 0; + putenv(s); + free(s); + free(command); + if (rc == -1) bb_perror_msg_and_die("cannot run %s", command); + } + if (delete) unlink(device_name); +} + +/* Recursive search of /sys/block or /sys/class. path must be a writeable + * buffer of size PATH_MAX containing the directory string to start at. */ + +static void find_dev(char *path) +{ + DIR *dir; + size_t len = strlen(path); + struct dirent *entry; + + dir = opendir(path); + if (dir == NULL) + return; + + while ((entry = readdir(dir)) != NULL) { + struct stat st; + + /* Skip "." and ".." (also skips hidden files, which is ok) */ + + if (entry->d_name[0] == '.') + continue; + + // uClibc doesn't fill out entry->d_type reliably. so we use lstat(). + + snprintf(path+len, PATH_MAX-len, "/%s", entry->d_name); + if (!lstat(path, &st) && S_ISDIR(st.st_mode)) find_dev(path); + path[len] = 0; + + /* If there's a dev entry, mknod it */ + + if (!strcmp(entry->d_name, "dev")) make_device(path, 0); + } + + closedir(dir); +} + +int mdev_main(int argc, char *argv[]) +{ + char *action; + char *env_path; + RESERVE_CONFIG_BUFFER(temp,PATH_MAX); + + xchdir(DEV_PATH); + + /* Scan */ + + if (argc == 2 && !strcmp(argv[1],"-s")) { + struct stat st; + + xstat("/", &st); + bbg.root_major = major(st.st_dev); + bbg.root_minor = minor(st.st_dev); + strcpy(temp,"/sys/block"); + find_dev(temp); + strcpy(temp,"/sys/class"); + find_dev(temp); + + /* Hotplug */ + + } else { + action = getenv("ACTION"); + env_path = getenv("DEVPATH"); + if (!action || !env_path) + bb_show_usage(); + + sprintf(temp, "/sys%s", env_path); + if (!strcmp(action, "add")) make_device(temp,0); + else if (!strcmp(action, "remove")) make_device(temp,1); + } + + if (ENABLE_FEATURE_CLEAN_UP) RELEASE_CONFIG_BUFFER(temp); + return 0; +} diff --git a/util-linux/mkfs_minix.c b/util-linux/mkfs_minix.c new file mode 100644 index 000000000..af19da68c --- /dev/null +++ b/util-linux/mkfs_minix.c @@ -0,0 +1,762 @@ +/* vi: set sw=4 ts=4: */ +/* + * mkfs.c - make a linux (minix) file-system. + * + * (C) 1991 Linus Torvalds. This file may be redistributed as per + * the Linux copyright. + */ + +/* + * DD.MM.YY + * + * 24.11.91 - Time began. Used the fsck sources to get started. + * + * 25.11.91 - Corrected some bugs. Added support for ".badblocks" + * The algorithm for ".badblocks" is a bit weird, but + * it should work. Oh, well. + * + * 25.01.92 - Added the -l option for getting the list of bad blocks + * out of a named file. (Dave Rivers, rivers@ponds.uucp) + * + * 28.02.92 - Added %-information when using -c. + * + * 28.02.93 - Added support for other namelengths than the original + * 14 characters so that I can test the new kernel routines.. + * + * 09.10.93 - Make exit status conform to that required by fsutil + * (Rik Faith, faith@cs.unc.edu) + * + * 31.10.93 - Added inode request feature, for backup floppies: use + * 32 inodes, for a news partition use more. + * (Scott Heavner, sdh@po.cwru.edu) + * + * 03.01.94 - Added support for file system valid flag. + * (Dr. Wettstein, greg%wind.uucp@plains.nodak.edu) + * + * 30.10.94 - added support for v2 filesystem + * (Andreas Schwab, schwab@issan.informatik.uni-dortmund.de) + * + * 09.11.94 - Added test to prevent overwrite of mounted fs adapted + * from Theodore Ts'o's (tytso@athena.mit.edu) mke2fs + * program. (Daniel Quinlan, quinlan@yggdrasil.com) + * + * 03.20.95 - Clear first 512 bytes of filesystem to make certain that + * the filesystem is not misidentified as a MS-DOS FAT filesystem. + * (Daniel Quinlan, quinlan@yggdrasil.com) + * + * 02.07.96 - Added small patch from Russell King to make the program a + * good deal more portable (janl@math.uio.no) + * + * Usage: mkfs [-c | -l filename ] [-v] [-nXX] [-iXX] device [size-in-blocks] + * + * -c for readability checking (SLOW!) + * -l for getting a list of bad blocks from a file. + * -n for namelength (currently the kernel only uses 14 or 30) + * -i for number of inodes + * -v for v2 filesystem + * + * The device may be a block device or a image of one, but this isn't + * enforced (but it's not much fun on a character device :-). + * + * Modified for BusyBox by Erik Andersen <andersen@debian.org> -- + * removed getopt based parser and added a hand rolled one. + */ + +#include "busybox.h" +#include <mntent.h> + +#define DEBUG 0 + +/* If debugging, store the very same times/uids/gids for image consistency */ +#if DEBUG +# define CUR_TIME 0 +# define GETUID 0 +# define GETGID 0 +#else +# define CUR_TIME time(NULL) +# define GETUID getuid() +# define GETGID getgid() +#endif + +/* + * This is the original minix inode layout on disk. + * Note the 8-bit gid and atime and ctime. + */ +struct minix1_inode { + uint16_t i_mode; + uint16_t i_uid; + uint32_t i_size; + uint32_t i_time; + uint8_t i_gid; + uint8_t i_nlinks; + uint16_t i_zone[9]; +}; + +/* + * The new minix inode has all the time entries, as well as + * long block numbers and a third indirect block (7+1+1+1 + * instead of 7+1+1). Also, some previously 8-bit values are + * now 16-bit. The inode is now 64 bytes instead of 32. + */ +struct minix2_inode { + uint16_t i_mode; + uint16_t i_nlinks; + uint16_t i_uid; + uint16_t i_gid; + uint32_t i_size; + uint32_t i_atime; + uint32_t i_mtime; + uint32_t i_ctime; + uint32_t i_zone[10]; +}; + +/* + * minix super-block data on disk + */ +struct minix_super_block { + uint16_t s_ninodes; + uint16_t s_nzones; + uint16_t s_imap_blocks; + uint16_t s_zmap_blocks; + uint16_t s_firstdatazone; + uint16_t s_log_zone_size; + uint32_t s_max_size; + uint16_t s_magic; + uint16_t s_state; + uint32_t s_zones; +}; + +struct minix_dir_entry { + uint16_t inode; + char name[0]; +}; + +/* Believe it or not, but mount.h has this one */ +#undef BLOCK_SIZE +enum { + BLOCK_SIZE = 1024, + BITS_PER_BLOCK = BLOCK_SIZE << 3, + + MINIX_ROOT_INO = 1, + MINIX_BAD_INO = 2, + MAX_GOOD_BLOCKS = 512, + + MINIX1_SUPER_MAGIC = 0x137F, /* original minix fs */ + MINIX1_SUPER_MAGIC2 = 0x138F, /* minix fs, 30 char names */ + MINIX2_SUPER_MAGIC = 0x2468, /* minix V2 fs */ + MINIX2_SUPER_MAGIC2 = 0x2478, /* minix V2 fs, 30 char names */ + MINIX_VALID_FS = 0x0001, /* clean fs */ + MINIX_ERROR_FS = 0x0002, /* fs has errors */ + + INODE_SIZE1 = sizeof(struct minix1_inode), + INODE_SIZE2 = sizeof(struct minix2_inode), + MINIX1_INODES_PER_BLOCK = BLOCK_SIZE / sizeof(struct minix1_inode), + MINIX2_INODES_PER_BLOCK = BLOCK_SIZE / sizeof(struct minix2_inode), + + TEST_BUFFER_BLOCKS = 16, +}; + +#if ENABLE_FEATURE_MINIX2 +static int version2; +#else +enum { version2 = 0 }; +#endif + +static char *device_name; +static int dev_fd = -1; +static uint32_t total_blocks; +static int badblocks; +/* default (changed to 30, per Linus's suggestion, Sun Nov 21 08:05:07 1993) */ +static int namelen = 30; +static int dirsize = 32; +static int magic = MINIX1_SUPER_MAGIC2; + +static char root_block[BLOCK_SIZE]; +static char super_block_buffer[BLOCK_SIZE]; +static char boot_block_buffer[512]; +static char *inode_buffer; + +static char *inode_map; +static char *zone_map; + +static int used_good_blocks; +static unsigned short good_blocks_table[MAX_GOOD_BLOCKS]; +static unsigned long req_nr_inodes; + +extern inline unsigned div_roundup(unsigned size, unsigned n) +{ + return (size + n-1) / n; +} + +#define INODE_BUF1 (((struct minix1_inode*)inode_buffer) - 1) +#define INODE_BUF2 (((struct minix2_inode*)inode_buffer) - 1) + +#define SB (*(struct minix_super_block*)super_block_buffer) + +#define SB_INODES (SB.s_ninodes) +#define SB_IMAPS (SB.s_imap_blocks) +#define SB_ZMAPS (SB.s_zmap_blocks) +#define SB_FIRSTZONE (SB.s_firstdatazone) +#define SB_ZONE_SIZE (SB.s_log_zone_size) +#define SB_MAXSIZE (SB.s_max_size) +#define SB_MAGIC (SB.s_magic) + +#if !ENABLE_FEATURE_MINIX2 +# define SB_ZONES (SB.s_nzones) +# define INODE_BLOCKS div_roundup(SB_INODES, MINIX1_INODES_PER_BLOCK) +#else +# define SB_ZONES (version2 ? SB.s_zones : SB.s_nzones) +# define INODE_BLOCKS div_roundup(SB_INODES, \ + version2 ? MINIX2_INODES_PER_BLOCK : MINIX1_INODES_PER_BLOCK) +#endif + +#define INODE_BUFFER_SIZE (INODE_BLOCKS * BLOCK_SIZE) +#define NORM_FIRSTZONE (2 + SB_IMAPS + SB_ZMAPS + INODE_BLOCKS) + +static int bit(const char* a, unsigned i) +{ + return a[i >> 3] & (1<<(i & 7)); +} + +/* Note: do not assume 0/1, it is 0/nonzero */ +#define inode_in_use(x) bit(inode_map,(x)) +#define zone_in_use(x) bit(zone_map,(x)-SB_FIRSTZONE+1) + +#define mark_inode(x) setbit(inode_map,(x)) +#define unmark_inode(x) clrbit(inode_map,(x)) +#define mark_zone(x) setbit(zone_map,(x)-SB_FIRSTZONE+1) +#define unmark_zone(x) clrbit(zone_map,(x)-SB_FIRSTZONE+1) + +#ifndef BLKGETSIZE +# define BLKGETSIZE _IO(0x12,96) /* return device size */ +#endif + + +static long valid_offset(int fd, int offset) +{ + char ch; + + if (lseek(fd, offset, SEEK_SET) < 0) + return 0; + if (read(fd, &ch, 1) < 1) + return 0; + return 1; +} + +static int count_blocks(int fd) +{ + int high, low; + + low = 0; + for (high = 1; valid_offset(fd, high); high *= 2) + low = high; + + while (low < high - 1) { + const int mid = (low + high) / 2; + + if (valid_offset(fd, mid)) + low = mid; + else + high = mid; + } + valid_offset(fd, 0); + return (low + 1); +} + +static int get_size(const char *file) +{ + int fd; + long size; + + fd = xopen(file, O_RDWR); + if (ioctl(fd, BLKGETSIZE, &size) >= 0) { + close(fd); + return (size * 512); + } + + size = count_blocks(fd); + close(fd); + return size; +} + +static void write_tables(void) +{ + /* Mark the super block valid. */ + SB.s_state |= MINIX_VALID_FS; + SB.s_state &= ~MINIX_ERROR_FS; + + msg_eol = "seek to 0 failed"; + xlseek(dev_fd, 0, SEEK_SET); + + msg_eol = "cannot clear boot sector"; + xwrite(dev_fd, boot_block_buffer, 512); + + msg_eol = "seek to BLOCK_SIZE failed"; + xlseek(dev_fd, BLOCK_SIZE, SEEK_SET); + + msg_eol = "cannot write superblock"; + xwrite(dev_fd, super_block_buffer, BLOCK_SIZE); + + msg_eol = "cannot write inode map"; + xwrite(dev_fd, inode_map, SB_IMAPS * BLOCK_SIZE); + + msg_eol = "cannot write zone map"; + xwrite(dev_fd, zone_map, SB_ZMAPS * BLOCK_SIZE); + + msg_eol = "cannot write inodes"; + xwrite(dev_fd, inode_buffer, INODE_BUFFER_SIZE); + + msg_eol = "\n"; +} + +static void write_block(int blk, char *buffer) +{ + xlseek(dev_fd, blk * BLOCK_SIZE, SEEK_SET); + xwrite(dev_fd, buffer, BLOCK_SIZE); +} + +static int get_free_block(void) +{ + int blk; + + if (used_good_blocks + 1 >= MAX_GOOD_BLOCKS) + bb_error_msg_and_die("too many bad blocks"); + if (used_good_blocks) + blk = good_blocks_table[used_good_blocks - 1] + 1; + else + blk = SB_FIRSTZONE; + while (blk < SB_ZONES && zone_in_use(blk)) + blk++; + if (blk >= SB_ZONES) + bb_error_msg_and_die("not enough good blocks"); + good_blocks_table[used_good_blocks] = blk; + used_good_blocks++; + return blk; +} + +static void mark_good_blocks(void) +{ + int blk; + + for (blk = 0; blk < used_good_blocks; blk++) + mark_zone(good_blocks_table[blk]); +} + +static int next(int zone) +{ + if (!zone) + zone = SB_FIRSTZONE - 1; + while (++zone < SB_ZONES) + if (zone_in_use(zone)) + return zone; + return 0; +} + +static void make_bad_inode(void) +{ + struct minix1_inode *inode = &INODE_BUF1[MINIX_BAD_INO]; + int i, j, zone; + int ind = 0, dind = 0; + unsigned short ind_block[BLOCK_SIZE >> 1]; + unsigned short dind_block[BLOCK_SIZE >> 1]; + +#define NEXT_BAD (zone = next(zone)) + + if (!badblocks) + return; + mark_inode(MINIX_BAD_INO); + inode->i_nlinks = 1; + /* BTW, setting this makes all images different */ + /* it's harder to check for bugs then - diff isn't helpful :(... */ + inode->i_time = CUR_TIME; + inode->i_mode = S_IFREG + 0000; + inode->i_size = badblocks * BLOCK_SIZE; + zone = next(0); + for (i = 0; i < 7; i++) { + inode->i_zone[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[7] = ind = get_free_block(); + memset(ind_block, 0, BLOCK_SIZE); + for (i = 0; i < 512; i++) { + ind_block[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[8] = dind = get_free_block(); + memset(dind_block, 0, BLOCK_SIZE); + for (i = 0; i < 512; i++) { + write_block(ind, (char *) ind_block); + dind_block[i] = ind = get_free_block(); + memset(ind_block, 0, BLOCK_SIZE); + for (j = 0; j < 512; j++) { + ind_block[j] = zone; + if (!NEXT_BAD) + goto end_bad; + } + } + bb_error_msg_and_die("too many bad blocks"); + end_bad: + if (ind) + write_block(ind, (char *) ind_block); + if (dind) + write_block(dind, (char *) dind_block); +} + +#if ENABLE_FEATURE_MINIX2 +static void make_bad_inode2(void) +{ + struct minix2_inode *inode = &INODE_BUF2[MINIX_BAD_INO]; + int i, j, zone; + int ind = 0, dind = 0; + unsigned long ind_block[BLOCK_SIZE >> 2]; + unsigned long dind_block[BLOCK_SIZE >> 2]; + + if (!badblocks) + return; + mark_inode(MINIX_BAD_INO); + inode->i_nlinks = 1; + inode->i_atime = inode->i_mtime = inode->i_ctime = CUR_TIME; + inode->i_mode = S_IFREG + 0000; + inode->i_size = badblocks * BLOCK_SIZE; + zone = next(0); + for (i = 0; i < 7; i++) { + inode->i_zone[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[7] = ind = get_free_block(); + memset(ind_block, 0, BLOCK_SIZE); + for (i = 0; i < 256; i++) { + ind_block[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[8] = dind = get_free_block(); + memset(dind_block, 0, BLOCK_SIZE); + for (i = 0; i < 256; i++) { + write_block(ind, (char *) ind_block); + dind_block[i] = ind = get_free_block(); + memset(ind_block, 0, BLOCK_SIZE); + for (j = 0; j < 256; j++) { + ind_block[j] = zone; + if (!NEXT_BAD) + goto end_bad; + } + } + /* Could make triple indirect block here */ + bb_error_msg_and_die("too many bad blocks"); + end_bad: + if (ind) + write_block(ind, (char *) ind_block); + if (dind) + write_block(dind, (char *) dind_block); +} +#endif + +static void make_root_inode(void) +{ + struct minix1_inode *inode = &INODE_BUF1[MINIX_ROOT_INO]; + + mark_inode(MINIX_ROOT_INO); + inode->i_zone[0] = get_free_block(); + inode->i_nlinks = 2; + inode->i_time = CUR_TIME; + if (badblocks) + inode->i_size = 3 * dirsize; + else { + root_block[2 * dirsize] = '\0'; + root_block[2 * dirsize + 1] = '\0'; + inode->i_size = 2 * dirsize; + } + inode->i_mode = S_IFDIR + 0755; + inode->i_uid = GETUID; + if (inode->i_uid) + inode->i_gid = GETGID; + write_block(inode->i_zone[0], root_block); +} + +#if ENABLE_FEATURE_MINIX2 +static void make_root_inode2(void) +{ + struct minix2_inode *inode = &INODE_BUF2[MINIX_ROOT_INO]; + + mark_inode(MINIX_ROOT_INO); + inode->i_zone[0] = get_free_block(); + inode->i_nlinks = 2; + inode->i_atime = inode->i_mtime = inode->i_ctime = CUR_TIME; + if (badblocks) + inode->i_size = 3 * dirsize; + else { + root_block[2 * dirsize] = '\0'; + root_block[2 * dirsize + 1] = '\0'; + inode->i_size = 2 * dirsize; + } + inode->i_mode = S_IFDIR + 0755; + inode->i_uid = GETUID; + if (inode->i_uid) + inode->i_gid = GETGID; + write_block(inode->i_zone[0], root_block); +} +#endif + +static void setup_tables(void) +{ + unsigned long inodes; + unsigned norm_firstzone; + unsigned sb_zmaps; + unsigned i; + + memset(super_block_buffer, 0, BLOCK_SIZE); + memset(boot_block_buffer, 0, 512); + SB_MAGIC = magic; + SB_ZONE_SIZE = 0; + SB_MAXSIZE = version2 ? 0x7fffffff : (7 + 512 + 512 * 512) * 1024; + if (version2) + SB.s_zones = total_blocks; + else + SB.s_nzones = total_blocks; + + /* some magic nrs: 1 inode / 3 blocks */ + if (req_nr_inodes == 0) + inodes = total_blocks / 3; + else + inodes = req_nr_inodes; + /* Round up inode count to fill block size */ + if (version2) + inodes = (inodes + MINIX2_INODES_PER_BLOCK - 1) & + ~(MINIX2_INODES_PER_BLOCK - 1); + else + inodes = (inodes + MINIX1_INODES_PER_BLOCK - 1) & + ~(MINIX1_INODES_PER_BLOCK - 1); + if (inodes > 65535) + inodes = 65535; + SB_INODES = inodes; + SB_IMAPS = div_roundup(SB_INODES + 1, BITS_PER_BLOCK); + + /* Real bad hack but overwise mkfs.minix can be thrown + * in infinite loop... + * try: + * dd if=/dev/zero of=test.fs count=10 bs=1024 + * mkfs.minix -i 200 test.fs + */ + /* This code is not insane: NORM_FIRSTZONE is not a constant, + * it is calculated from SB_INODES, SB_IMAPS and SB_ZMAPS */ + i = 999; + SB_ZMAPS = 0; + do { + norm_firstzone = NORM_FIRSTZONE; + sb_zmaps = div_roundup(total_blocks - norm_firstzone + 1, BITS_PER_BLOCK); + if (SB_ZMAPS == sb_zmaps) goto got_it; + SB_ZMAPS = sb_zmaps; + /* new SB_ZMAPS, need to recalc NORM_FIRSTZONE */ + } while (--i); + bb_error_msg_and_die("incompatible size/inode count, try different -i N"); + got_it: + + SB_FIRSTZONE = norm_firstzone; + inode_map = xmalloc(SB_IMAPS * BLOCK_SIZE); + zone_map = xmalloc(SB_ZMAPS * BLOCK_SIZE); + memset(inode_map, 0xff, SB_IMAPS * BLOCK_SIZE); + memset(zone_map, 0xff, SB_ZMAPS * BLOCK_SIZE); + for (i = SB_FIRSTZONE; i < SB_ZONES; i++) + unmark_zone(i); + for (i = MINIX_ROOT_INO; i <= SB_INODES; i++) + unmark_inode(i); + inode_buffer = xzalloc(INODE_BUFFER_SIZE); + printf("%ld inodes\n", (long)SB_INODES); + printf("%ld blocks\n", (long)SB_ZONES); + printf("Firstdatazone=%ld (%ld)\n", (long)SB_FIRSTZONE, (long)norm_firstzone); + printf("Zonesize=%d\n", BLOCK_SIZE << SB_ZONE_SIZE); + printf("Maxsize=%ld\n", (long)SB_MAXSIZE); +} + +/* + * Perform a test of a block; return the number of + * blocks readable/writable. + */ +static long do_check(char *buffer, int try, unsigned current_block) +{ + long got; + + /* Seek to the correct loc. */ + msg_eol = "seek failed during testing of blocks"; + xlseek(dev_fd, current_block * BLOCK_SIZE, SEEK_SET); + msg_eol = "\n"; + + /* Try the read */ + got = read(dev_fd, buffer, try * BLOCK_SIZE); + if (got < 0) + got = 0; + if (got & (BLOCK_SIZE - 1)) { + printf("Weird values in do_check: probably bugs\n"); + } + got /= BLOCK_SIZE; + return got; +} + +static unsigned currently_testing; + +static void alarm_intr(int alnum) +{ + if (currently_testing >= SB_ZONES) + return; + signal(SIGALRM, alarm_intr); + alarm(5); + if (!currently_testing) + return; + printf("%d ...", currently_testing); + fflush(stdout); +} + +static void check_blocks(void) +{ + int try, got; + /* buffer[] was the biggest static in entire bbox */ + char *buffer = xmalloc(BLOCK_SIZE * TEST_BUFFER_BLOCKS); + + currently_testing = 0; + signal(SIGALRM, alarm_intr); + alarm(5); + while (currently_testing < SB_ZONES) { + msg_eol = "seek failed in check_blocks"; + xlseek(dev_fd, currently_testing * BLOCK_SIZE, SEEK_SET); + msg_eol = "\n"; + try = TEST_BUFFER_BLOCKS; + if (currently_testing + try > SB_ZONES) + try = SB_ZONES - currently_testing; + got = do_check(buffer, try, currently_testing); + currently_testing += got; + if (got == try) + continue; + if (currently_testing < SB_FIRSTZONE) + bb_error_msg_and_die("bad blocks before data-area: cannot make fs"); + mark_zone(currently_testing); + badblocks++; + currently_testing++; + } + free(buffer); + printf("%d bad block(s)\n", badblocks); +} + +static void get_list_blocks(char *filename) +{ + FILE *listfile; + unsigned long blockno; + + listfile = xfopen(filename, "r"); + while (!feof(listfile)) { + fscanf(listfile, "%ld\n", &blockno); + mark_zone(blockno); + badblocks++; + } + printf("%d bad block(s)\n", badblocks); +} + +int mkfs_minix_main(int argc, char **argv) +{ + struct mntent *mp; + unsigned opt; + char *tmp; + struct stat statbuf; + char *str_i, *str_n; + char *listfile = NULL; + + if (INODE_SIZE1 * MINIX1_INODES_PER_BLOCK != BLOCK_SIZE) + bb_error_msg_and_die("bad inode size"); +#if ENABLE_FEATURE_MINIX2 + if (INODE_SIZE2 * MINIX2_INODES_PER_BLOCK != BLOCK_SIZE) + bb_error_msg_and_die("bad inode size"); +#endif + + opt = getopt32(argc, argv, "ci:l:n:v", &str_i, &listfile, &str_n); + argv += optind; + //if (opt & 1) -c + if (opt & 2) req_nr_inodes = xatoul(str_i); // -i + //if (opt & 4) -l + if (opt & 8) { // -n + namelen = xatoi_u(str_n); + if (namelen == 14) magic = MINIX1_SUPER_MAGIC; + else if (namelen == 30) magic = MINIX1_SUPER_MAGIC2; + else bb_show_usage(); + dirsize = namelen + 2; + } + if (opt & 0x10) { // -v +#if ENABLE_FEATURE_MINIX2 + version2 = 1; +#else + bb_error_msg_and_die("%s: not compiled with minix v2 support", + device_name); +#endif + } + + device_name = *argv++; + if (!device_name) + bb_show_usage(); + if (*argv) + total_blocks = xatou32(*argv); + else + total_blocks = get_size(device_name) / 1024; + + if (total_blocks < 10) + bb_error_msg_and_die("must have at least 10 blocks"); + + if (version2) { + magic = MINIX2_SUPER_MAGIC2; + if (namelen == 14) + magic = MINIX2_SUPER_MAGIC; + } else if (total_blocks > 65535) + total_blocks = 65535; + + /* Check if it is mounted */ + mp = find_mount_point(device_name, NULL); + if (mp && strcmp(device_name, mp->mnt_fsname) == 0) + bb_error_msg_and_die("%s is mounted on %s; " + "refusing to make a filesystem", + device_name, mp->mnt_dir); + + dev_fd = xopen(device_name, O_RDWR); + if (fstat(dev_fd, &statbuf) < 0) + bb_error_msg_and_die("cannot stat %s", device_name); + if (!S_ISBLK(statbuf.st_mode)) + opt &= ~1; // clear -c (check) + +/* I don't know why someone has special code to prevent mkfs.minix + * on IDE devices. Why IDE but not SCSI, etc?... */ +#if 0 + else if (statbuf.st_rdev == 0x0300 || statbuf.st_rdev == 0x0340) + /* what is this? */ + bb_error_msg_and_die("will not try " + "to make filesystem on '%s'", device_name); +#endif + + tmp = root_block; + *(short *) tmp = 1; + strcpy(tmp + 2, "."); + tmp += dirsize; + *(short *) tmp = 1; + strcpy(tmp + 2, ".."); + tmp += dirsize; + *(short *) tmp = 2; + strcpy(tmp + 2, ".badblocks"); + + setup_tables(); + + if (opt & 1) // -c ? + check_blocks(); + else if (listfile) + get_list_blocks(listfile); + + if (version2) { + make_root_inode2(); + make_bad_inode2(); + } else { + make_root_inode(); + make_bad_inode(); + } + + mark_good_blocks(); + write_tables(); + return 0; +} diff --git a/util-linux/mkswap.c b/util-linux/mkswap.c new file mode 100644 index 000000000..7baa3ecfb --- /dev/null +++ b/util-linux/mkswap.c @@ -0,0 +1,47 @@ +/* vi: set sw=4 ts=4: */ +/* mkswap.c - format swap device (Linux v1 only) + * + * Copyright 2006 Rob Landley <rob@landley.net> + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +int mkswap_main(int argc, char *argv[]) +{ + int fd, pagesize; + off_t len; + unsigned int hdr[129]; + + // No options supported. + + if (argc != 2) bb_show_usage(); + + // Figure out how big the device is and announce our intentions. + + fd = xopen(argv[1], O_RDWR); + len = fdlength(fd); + pagesize = getpagesize(); + printf("Setting up swapspace version 1, size = %"OFF_FMT"d bytes\n", + len - pagesize); + + // Make a header. + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 1; + hdr[1] = (len / pagesize) - 1; + + // Write the header. Sync to disk because some kernel versions check + // signature on disk (not in cache) during swapon. + + xlseek(fd, 1024, SEEK_SET); + xwrite(fd, hdr, sizeof(hdr)); + xlseek(fd, pagesize-10, SEEK_SET); + xwrite(fd, "SWAPSPACE2", 10); + fsync(fd); + + if (ENABLE_FEATURE_CLEAN_UP) close(fd); + + return 0; +} diff --git a/util-linux/more.c b/util-linux/more.c new file mode 100644 index 000000000..d048ace92 --- /dev/null +++ b/util-linux/more.c @@ -0,0 +1,177 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini more implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>. + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Latest version blended together by Erik Andersen <andersen@codepoet.org>, + * based on the original more implementation by Bruce, and code from the + * Debian boot-floppies team. + * + * Termios corrects by Vladimir Oleynik <dzo@simtreas.ru> + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ + +#include "busybox.h" + + +#if ENABLE_FEATURE_USE_TERMIOS +static int cin_fileno; +#include <termios.h> +#define setTermSettings(fd, argp) tcsetattr(fd, TCSANOW, argp) +#define getTermSettings(fd, argp) tcgetattr(fd, argp); + +static struct termios initial_settings, new_settings; + +static void set_tty_to_initial_mode(void) +{ + setTermSettings(cin_fileno, &initial_settings); +} + +static void gotsig(int sig) +{ + putchar('\n'); + exit(EXIT_FAILURE); +} +#endif /* FEATURE_USE_TERMIOS */ + + +int more_main(int argc, char **argv) +{ + int c, lines, input = 0; + int please_display_more_prompt = 0; + struct stat st; + FILE *file; + FILE *cin; + int len, page_height; + int terminal_width; + int terminal_height; + + argc--; + argv++; + + cin = stdin; + /* use input from terminal unless we do "more >outfile" */ + if (isatty(STDOUT_FILENO)) { + cin = fopen(CURRENT_TTY, "r"); + if (!cin) + cin = xfopen(CONSOLE_DEV, "r"); + please_display_more_prompt = 2; +#if ENABLE_FEATURE_USE_TERMIOS + cin_fileno = fileno(cin); + getTermSettings(cin_fileno, &initial_settings); + new_settings = initial_settings; + new_settings.c_lflag &= ~ICANON; + new_settings.c_lflag &= ~ECHO; + new_settings.c_cc[VMIN] = 1; + new_settings.c_cc[VTIME] = 0; + setTermSettings(cin_fileno, &new_settings); + atexit(set_tty_to_initial_mode); + signal(SIGINT, gotsig); + signal(SIGQUIT, gotsig); + signal(SIGTERM, gotsig); +#endif + } + + do { + file = stdin; + if (argc != 0) { + file = fopen_or_warn(*argv, "r"); + if (!file) + goto loop; + } + + st.st_size = 0; + fstat(fileno(file), &st); + + please_display_more_prompt &= ~1; + + get_terminal_width_height(fileno(cin), &terminal_width, &terminal_height); + if (terminal_height > 4) + terminal_height -= 2; + if (terminal_width > 0) + terminal_width -= 1; + + len = 0; + lines = 0; + page_height = terminal_height; + while ((c = getc(file)) != EOF) { + + if ((please_display_more_prompt & 3) == 3) { + len = printf("--More-- "); + if (file != stdin && st.st_size > 0) { + len += printf("(%d%% of %"OFF_FMT"d bytes)", + (int) (ftello(file)*100 / st.st_size), + st.st_size); + } + fflush(stdout); + + /* + * We've just displayed the "--More--" prompt, so now we need + * to get input from the user. + */ + input = getc(cin); +#if !ENABLE_FEATURE_USE_TERMIOS + printf("\033[A"); /* up cursor */ +#endif + /* Erase the "More" message */ + printf("\r%*s\r", len, ""); + len = 0; + lines = 0; + page_height = terminal_height; + please_display_more_prompt &= ~1; + + if (input == 'q') + goto end; + } + + /* + * There are two input streams to worry about here: + * + * c : the character we are reading from the file being "mored" + * input : a character received from the keyboard + * + * If we hit a newline in the _file_ stream, we want to test and + * see if any characters have been hit in the _input_ stream. This + * allows the user to quit while in the middle of a file. + */ + if (c == '\n') { + /* increment by just one line if we are at + * the end of this line */ + if (input == '\n') + please_display_more_prompt |= 1; + /* Adjust the terminal height for any overlap, so that + * no lines get lost off the top. */ + if (len >= terminal_width) { + int quot, rem; + quot = len / terminal_width; + rem = len - (quot * terminal_width); + if (quot) { + if (rem) + page_height -= quot; + else + page_height -= (quot - 1); + } + } + if (++lines >= page_height) { + please_display_more_prompt |= 1; + } + len = 0; + } + /* + * If we just read a newline from the file being 'mored' and any + * key other than a return is hit, scroll by one page + */ + putc(c, stdout); + len++; + } + fclose(file); + fflush(stdout); + loop: + argv++; + } while (--argc > 0); + end: + return 0; +} diff --git a/util-linux/mount.c b/util-linux/mount.c new file mode 100644 index 000000000..f8ae1df19 --- /dev/null +++ b/util-linux/mount.c @@ -0,0 +1,1712 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini mount implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>. + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * Copyright (C) 2005-2006 by Rob Landley <rob@landley.net> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Design notes: There is no spec for mount. Remind me to write one. + + mount_main() calls singlemount() which calls mount_it_now(). + + mount_main() can loop through /etc/fstab for mount -a + singlemount() can loop through /etc/filesystems for fstype detection. + mount_it_now() does the actual mount. +*/ + +#include "busybox.h" +#include <mntent.h> + +/* Needed for nfs support only... */ +#include <syslog.h> +#include <sys/utsname.h> +#undef TRUE +#undef FALSE +#include <rpc/rpc.h> +#include <rpc/pmap_prot.h> +#include <rpc/pmap_clnt.h> + + +// Not real flags, but we want to be able to check for this. +enum { + MOUNT_USERS = (1<<28)*ENABLE_DESKTOP, + MOUNT_NOAUTO = (1<<29), + MOUNT_SWAP = (1<<30), +}; +// TODO: more "user" flag compatibility. +// "user" option (from mount manpage): +// Only the user that mounted a filesystem can unmount it again. +// If any user should be able to unmount, then use users instead of user +// in the fstab line. The owner option is similar to the user option, +// with the restriction that the user must be the owner of the special file. +// This may be useful e.g. for /dev/fd if a login script makes +// the console user owner of this device. + +/* Standard mount options (from -o options or --options), with corresponding + * flags */ + +struct { + char *name; + long flags; +} static mount_options[] = { + // MS_FLAGS set a bit. ~MS_FLAGS disable that bit. 0 flags are NOPs. + + USE_FEATURE_MOUNT_LOOP( + {"loop", 0}, + ) + + USE_FEATURE_MOUNT_FSTAB( + {"defaults", 0}, + /* {"quiet", 0}, - do not filter out, vfat wants to see it */ + {"noauto", MOUNT_NOAUTO}, + {"swap", MOUNT_SWAP}, + USE_DESKTOP({"user", MOUNT_USERS},) + USE_DESKTOP({"users", MOUNT_USERS},) + ) + + USE_FEATURE_MOUNT_FLAGS( + // vfs flags + {"nosuid", MS_NOSUID}, + {"suid", ~MS_NOSUID}, + {"dev", ~MS_NODEV}, + {"nodev", MS_NODEV}, + {"exec", ~MS_NOEXEC}, + {"noexec", MS_NOEXEC}, + {"sync", MS_SYNCHRONOUS}, + {"async", ~MS_SYNCHRONOUS}, + {"atime", ~MS_NOATIME}, + {"noatime", MS_NOATIME}, + {"diratime", ~MS_NODIRATIME}, + {"nodiratime", MS_NODIRATIME}, + {"loud", ~MS_SILENT}, + + // action flags + + {"bind", MS_BIND}, + {"move", MS_MOVE}, + {"shared", MS_SHARED}, + {"slave", MS_SLAVE}, + {"private", MS_PRIVATE}, + {"unbindable", MS_UNBINDABLE}, + {"rshared", MS_SHARED|MS_RECURSIVE}, + {"rslave", MS_SLAVE|MS_RECURSIVE}, + {"rprivate", MS_SLAVE|MS_RECURSIVE}, + {"runbindable", MS_UNBINDABLE|MS_RECURSIVE}, + ) + + // Always understood. + + {"ro", MS_RDONLY}, // vfs flag + {"rw", ~MS_RDONLY}, // vfs flag + {"remount", MS_REMOUNT}, // action flag +}; + +#define VECTOR_SIZE(v) (sizeof(v) / sizeof((v)[0])) + +/* Append mount options to string */ +static void append_mount_options(char **oldopts, char *newopts) +{ + if (*oldopts && **oldopts) { + /* do not insert options which are already there */ + while (newopts[0]) { + char *p; + int len = strlen(newopts); + p = strchr(newopts, ','); + if (p) len = p - newopts; + p = *oldopts; + while (1) { + if (!strncmp(p, newopts, len) + && (p[len]==',' || p[len]==0)) + goto skip; + p = strchr(p,','); + if(!p) break; + p++; + } + p = xasprintf("%s,%.*s", *oldopts, len, newopts); + free(*oldopts); + *oldopts = p; +skip: + newopts += len; + while (newopts[0] == ',') newopts++; + } + } else { + if (ENABLE_FEATURE_CLEAN_UP) free(*oldopts); + *oldopts = xstrdup(newopts); + } +} + +/* Use the mount_options list to parse options into flags. + * Also return list of unrecognized options if unrecognized!=NULL */ +static int parse_mount_options(char *options, char **unrecognized) +{ + int flags = MS_SILENT; + + // Loop through options + for (;;) { + int i; + char *comma = strchr(options, ','); + + if (comma) *comma = 0; + + // Find this option in mount_options + for (i = 0; i < VECTOR_SIZE(mount_options); i++) { + if (!strcasecmp(mount_options[i].name, options)) { + long fl = mount_options[i].flags; + if (fl < 0) flags &= fl; + else flags |= fl; + break; + } + } + // If unrecognized not NULL, append unrecognized mount options */ + if (unrecognized && i == VECTOR_SIZE(mount_options)) { + // Add it to strflags, to pass on to kernel + i = *unrecognized ? strlen(*unrecognized) : 0; + *unrecognized = xrealloc(*unrecognized, i+strlen(options)+2); + + // Comma separated if it's not the first one + if (i) (*unrecognized)[i++] = ','; + strcpy((*unrecognized)+i, options); + } + + // Advance to next option, or finish + if (comma) { + *comma = ','; + options = ++comma; + } else break; + } + + return flags; +} + +// Return a list of all block device backed filesystems + +static llist_t *get_block_backed_filesystems(void) +{ + static const char *const filesystems[] = { + "/etc/filesystems", + "/proc/filesystems", + 0 + }; + char *fs, *buf; + llist_t *list = 0; + int i; + FILE *f; + + for (i = 0; filesystems[i]; i++) { + f = fopen(filesystems[i], "r"); + if (!f) continue; + + while ((buf = xmalloc_getline(f)) != 0) { + if (!strncmp(buf, "nodev", 5) && isspace(buf[5])) + continue; + fs = skip_whitespace(buf); + if (*fs=='#' || *fs=='*' || !*fs) continue; + + llist_add_to_end(&list, xstrdup(fs)); + free(buf); + } + if (ENABLE_FEATURE_CLEAN_UP) fclose(f); + } + + return list; +} + +llist_t *fslist = 0; + +#if ENABLE_FEATURE_CLEAN_UP +static void delete_block_backed_filesystems(void) +{ + llist_free(fslist, free); +} +#else +void delete_block_backed_filesystems(void); +#endif + +#if ENABLE_FEATURE_MTAB_SUPPORT +static int useMtab = 1; +static int fakeIt; +#else +#define useMtab 0 +#define fakeIt 0 +#endif + +// Perform actual mount of specific filesystem at specific location. +// NB: mp->xxx fields may be trashed on exit +static int mount_it_now(struct mntent *mp, int vfsflags, char *filteropts) +{ + int rc = 0; + + if (fakeIt) goto mtab; + + // Mount, with fallback to read-only if necessary. + + for (;;) { + rc = mount(mp->mnt_fsname, mp->mnt_dir, mp->mnt_type, + vfsflags, filteropts); + if (!rc || (vfsflags&MS_RDONLY) || (errno!=EACCES && errno!=EROFS)) + break; + bb_error_msg("%s is write-protected, mounting read-only", + mp->mnt_fsname); + vfsflags |= MS_RDONLY; + } + + // Abort entirely if permission denied. + + if (rc && errno == EPERM) + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + + /* If the mount was successful, and we're maintaining an old-style + * mtab file by hand, add the new entry to it now. */ + mtab: + if (ENABLE_FEATURE_MTAB_SUPPORT && useMtab && !rc && !(vfsflags & MS_REMOUNT)) { + char *fsname; + FILE *mountTable = setmntent(bb_path_mtab_file, "a+"); + int i; + + if (!mountTable) { + bb_error_msg("no %s",bb_path_mtab_file); + goto ret; + } + + // Add vfs string flags + + for (i=0; mount_options[i].flags != MS_REMOUNT; i++) + if (mount_options[i].flags > 0 && (mount_options[i].flags & vfsflags)) + append_mount_options(&(mp->mnt_opts), mount_options[i].name); + + // Remove trailing / (if any) from directory we mounted on + + i = strlen(mp->mnt_dir) - 1; + if (i > 0 && mp->mnt_dir[i] == '/') mp->mnt_dir[i] = 0; + + // Convert to canonical pathnames as needed + + mp->mnt_dir = bb_simplify_path(mp->mnt_dir); + fsname = 0; + if (!mp->mnt_type || !*mp->mnt_type) { /* bind mount */ + mp->mnt_fsname = fsname = bb_simplify_path(mp->mnt_fsname); + mp->mnt_type = "bind"; + } + mp->mnt_freq = mp->mnt_passno = 0; + + // Write and close. + + addmntent(mountTable, mp); + endmntent(mountTable); + if (ENABLE_FEATURE_CLEAN_UP) { + free(mp->mnt_dir); + free(fsname); + } + } + ret: + return rc; +} + +#if ENABLE_FEATURE_MOUNT_NFS + +/* + * Linux NFS mount + * Copyright (C) 1993 Rick Sladkey <jrs@world.std.com> + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + * + * Wed Feb 8 12:51:48 1995, biro@yggdrasil.com (Ross Biro): allow all port + * numbers to be specified on the command line. + * + * Fri, 8 Mar 1996 18:01:39, Swen Thuemmler <swen@uni-paderborn.de>: + * Omit the call to connect() for Linux version 1.3.11 or later. + * + * Wed Oct 1 23:55:28 1997: Dick Streefland <dick_streefland@tasking.com> + * Implemented the "bg", "fg" and "retry" mount options for NFS. + * + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org> + * - added Native Language Support + * + * Modified by Olaf Kirch and Trond Myklebust for new NFS code, + * plus NFSv3 stuff. + */ + +/* This is just a warning of a common mistake. Possibly this should be a + * uclibc faq entry rather than in busybox... */ +#if defined(__UCLIBC__) && ! defined(__UCLIBC_HAS_RPC__) +#error "You need to build uClibc with UCLIBC_HAS_RPC for NFS support." +#endif + +#define MOUNTPORT 635 +#define MNTPATHLEN 1024 +#define MNTNAMLEN 255 +#define FHSIZE 32 +#define FHSIZE3 64 + +typedef char fhandle[FHSIZE]; + +typedef struct { + unsigned int fhandle3_len; + char *fhandle3_val; +} fhandle3; + +enum mountstat3 { + MNT_OK = 0, + MNT3ERR_PERM = 1, + MNT3ERR_NOENT = 2, + MNT3ERR_IO = 5, + MNT3ERR_ACCES = 13, + MNT3ERR_NOTDIR = 20, + MNT3ERR_INVAL = 22, + MNT3ERR_NAMETOOLONG = 63, + MNT3ERR_NOTSUPP = 10004, + MNT3ERR_SERVERFAULT = 10006, +}; +typedef enum mountstat3 mountstat3; + +struct fhstatus { + unsigned int fhs_status; + union { + fhandle fhs_fhandle; + } fhstatus_u; +}; +typedef struct fhstatus fhstatus; + +struct mountres3_ok { + fhandle3 fhandle; + struct { + unsigned int auth_flavours_len; + char *auth_flavours_val; + } auth_flavours; +}; +typedef struct mountres3_ok mountres3_ok; + +struct mountres3 { + mountstat3 fhs_status; + union { + mountres3_ok mountinfo; + } mountres3_u; +}; +typedef struct mountres3 mountres3; + +typedef char *dirpath; + +typedef char *name; + +typedef struct mountbody *mountlist; + +struct mountbody { + name ml_hostname; + dirpath ml_directory; + mountlist ml_next; +}; +typedef struct mountbody mountbody; + +typedef struct groupnode *groups; + +struct groupnode { + name gr_name; + groups gr_next; +}; +typedef struct groupnode groupnode; + +typedef struct exportnode *exports; + +struct exportnode { + dirpath ex_dir; + groups ex_groups; + exports ex_next; +}; +typedef struct exportnode exportnode; + +struct ppathcnf { + int pc_link_max; + short pc_max_canon; + short pc_max_input; + short pc_name_max; + short pc_path_max; + short pc_pipe_buf; + u_char pc_vdisable; + char pc_xxx; + short pc_mask[2]; +}; +typedef struct ppathcnf ppathcnf; + +#define MOUNTPROG 100005 +#define MOUNTVERS 1 + +#define MOUNTPROC_NULL 0 +#define MOUNTPROC_MNT 1 +#define MOUNTPROC_DUMP 2 +#define MOUNTPROC_UMNT 3 +#define MOUNTPROC_UMNTALL 4 +#define MOUNTPROC_EXPORT 5 +#define MOUNTPROC_EXPORTALL 6 + +#define MOUNTVERS_POSIX 2 + +#define MOUNTPROC_PATHCONF 7 + +#define MOUNT_V3 3 + +#define MOUNTPROC3_NULL 0 +#define MOUNTPROC3_MNT 1 +#define MOUNTPROC3_DUMP 2 +#define MOUNTPROC3_UMNT 3 +#define MOUNTPROC3_UMNTALL 4 +#define MOUNTPROC3_EXPORT 5 + +enum { +#ifndef NFS_FHSIZE + NFS_FHSIZE = 32, +#endif +#ifndef NFS_PORT + NFS_PORT = 2049 +#endif +}; + +/* + * We want to be able to compile mount on old kernels in such a way + * that the binary will work well on more recent kernels. + * Thus, if necessary we teach nfsmount.c the structure of new fields + * that will come later. + * + * Moreover, the new kernel includes conflict with glibc includes + * so it is easiest to ignore the kernel altogether (at compile time). + */ + +struct nfs2_fh { + char data[32]; +}; +struct nfs3_fh { + unsigned short size; + unsigned char data[64]; +}; + +struct nfs_mount_data { + int version; /* 1 */ + int fd; /* 1 */ + struct nfs2_fh old_root; /* 1 */ + int flags; /* 1 */ + int rsize; /* 1 */ + int wsize; /* 1 */ + int timeo; /* 1 */ + int retrans; /* 1 */ + int acregmin; /* 1 */ + int acregmax; /* 1 */ + int acdirmin; /* 1 */ + int acdirmax; /* 1 */ + struct sockaddr_in addr; /* 1 */ + char hostname[256]; /* 1 */ + int namlen; /* 2 */ + unsigned int bsize; /* 3 */ + struct nfs3_fh root; /* 4 */ +}; + +/* bits in the flags field */ +enum { + NFS_MOUNT_SOFT = 0x0001, /* 1 */ + NFS_MOUNT_INTR = 0x0002, /* 1 */ + NFS_MOUNT_SECURE = 0x0004, /* 1 */ + NFS_MOUNT_POSIX = 0x0008, /* 1 */ + NFS_MOUNT_NOCTO = 0x0010, /* 1 */ + NFS_MOUNT_NOAC = 0x0020, /* 1 */ + NFS_MOUNT_TCP = 0x0040, /* 2 */ + NFS_MOUNT_VER3 = 0x0080, /* 3 */ + NFS_MOUNT_KERBEROS = 0x0100, /* 3 */ + NFS_MOUNT_NONLM = 0x0200 /* 3 */ +}; + + +/* + * We need to translate between nfs status return values and + * the local errno values which may not be the same. + * + * Andreas Schwab <schwab@LS5.informatik.uni-dortmund.de>: change errno: + * "after #include <errno.h> the symbol errno is reserved for any use, + * it cannot even be used as a struct tag or field name". + */ + +#ifndef EDQUOT +#define EDQUOT ENOSPC +#endif + +// Convert each NFSERR_BLAH into EBLAH + +static const struct { + int stat; + int errnum; +} nfs_errtbl[] = { + {0,0}, {1,EPERM}, {2,ENOENT}, {5,EIO}, {6,ENXIO}, {13,EACCES}, {17,EEXIST}, + {19,ENODEV}, {20,ENOTDIR}, {21,EISDIR}, {22,EINVAL}, {27,EFBIG}, + {28,ENOSPC}, {30,EROFS}, {63,ENAMETOOLONG}, {66,ENOTEMPTY}, {69,EDQUOT}, + {70,ESTALE}, {71,EREMOTE}, {-1,EIO} +}; + +static char *nfs_strerror(int status) +{ + int i; + static char buf[sizeof("unknown nfs status return value: ") + sizeof(int)*3]; + + for (i = 0; nfs_errtbl[i].stat != -1; i++) { + if (nfs_errtbl[i].stat == status) + return strerror(nfs_errtbl[i].errnum); + } + sprintf(buf, "unknown nfs status return value: %d", status); + return buf; +} + +static bool_t xdr_fhandle(XDR *xdrs, fhandle objp) +{ + if (!xdr_opaque(xdrs, objp, FHSIZE)) + return FALSE; + return TRUE; +} + +static bool_t xdr_fhstatus(XDR *xdrs, fhstatus *objp) +{ + if (!xdr_u_int(xdrs, &objp->fhs_status)) + return FALSE; + switch (objp->fhs_status) { + case 0: + if (!xdr_fhandle(xdrs, objp->fhstatus_u.fhs_fhandle)) + return FALSE; + break; + default: + break; + } + return TRUE; +} + +static bool_t xdr_dirpath(XDR *xdrs, dirpath *objp) +{ + if (!xdr_string(xdrs, objp, MNTPATHLEN)) + return FALSE; + return TRUE; +} + +static bool_t xdr_fhandle3(XDR *xdrs, fhandle3 *objp) +{ + if (!xdr_bytes(xdrs, (char **)&objp->fhandle3_val, (unsigned int *) &objp->fhandle3_len, FHSIZE3)) + return FALSE; + return TRUE; +} + +static bool_t xdr_mountres3_ok(XDR *xdrs, mountres3_ok *objp) +{ + if (!xdr_fhandle3(xdrs, &objp->fhandle)) + return FALSE; + if (!xdr_array(xdrs, &(objp->auth_flavours.auth_flavours_val), &(objp->auth_flavours.auth_flavours_len), ~0, + sizeof (int), (xdrproc_t) xdr_int)) + return FALSE; + return TRUE; +} + +static bool_t xdr_mountstat3(XDR *xdrs, mountstat3 *objp) +{ + if (!xdr_enum(xdrs, (enum_t *) objp)) + return FALSE; + return TRUE; +} + +static bool_t xdr_mountres3(XDR *xdrs, mountres3 *objp) +{ + if (!xdr_mountstat3(xdrs, &objp->fhs_status)) + return FALSE; + switch (objp->fhs_status) { + case MNT_OK: + if (!xdr_mountres3_ok(xdrs, &objp->mountres3_u.mountinfo)) + return FALSE; + break; + default: + break; + } + return TRUE; +} + +#define MAX_NFSPROT ((nfs_mount_version >= 4) ? 3 : 2) + +/* + * nfs_mount_version according to the sources seen at compile time. + */ +static int nfs_mount_version; +static int kernel_version; + +/* + * Unfortunately, the kernel prints annoying console messages + * in case of an unexpected nfs mount version (instead of + * just returning some error). Therefore we'll have to try + * and figure out what version the kernel expects. + * + * Variables: + * KERNEL_NFS_MOUNT_VERSION: kernel sources at compile time + * NFS_MOUNT_VERSION: these nfsmount sources at compile time + * nfs_mount_version: version this source and running kernel can handle + */ +static void +find_kernel_nfs_mount_version(void) +{ + if (kernel_version) + return; + + nfs_mount_version = 4; /* default */ + + kernel_version = get_linux_version_code(); + if (kernel_version) { + if (kernel_version < KERNEL_VERSION(2,1,32)) + nfs_mount_version = 1; + else if (kernel_version < KERNEL_VERSION(2,2,18) || + (kernel_version >= KERNEL_VERSION(2,3,0) && + kernel_version < KERNEL_VERSION(2,3,99))) + nfs_mount_version = 3; + /* else v4 since 2.3.99pre4 */ + } +} + +static struct pmap * +get_mountport(struct sockaddr_in *server_addr, + long unsigned prog, + long unsigned version, + long unsigned proto, + long unsigned port) +{ + struct pmaplist *pmap; + static struct pmap p = {0, 0, 0, 0}; + + server_addr->sin_port = PMAPPORT; + pmap = pmap_getmaps(server_addr); + + if (version > MAX_NFSPROT) + version = MAX_NFSPROT; + if (!prog) + prog = MOUNTPROG; + p.pm_prog = prog; + p.pm_vers = version; + p.pm_prot = proto; + p.pm_port = port; + + while (pmap) { + if (pmap->pml_map.pm_prog != prog) + goto next; + if (!version && p.pm_vers > pmap->pml_map.pm_vers) + goto next; + if (version > 2 && pmap->pml_map.pm_vers != version) + goto next; + if (version && version <= 2 && pmap->pml_map.pm_vers > 2) + goto next; + if (pmap->pml_map.pm_vers > MAX_NFSPROT || + (proto && p.pm_prot && pmap->pml_map.pm_prot != proto) || + (port && pmap->pml_map.pm_port != port)) + goto next; + memcpy(&p, &pmap->pml_map, sizeof(p)); +next: + pmap = pmap->pml_next; + } + if (!p.pm_vers) + p.pm_vers = MOUNTVERS; + if (!p.pm_port) + p.pm_port = MOUNTPORT; + if (!p.pm_prot) + p.pm_prot = IPPROTO_TCP; + return &p; +} + +static int daemonize(void) +{ + int fd; + int pid = fork(); + if (pid < 0) /* error */ + return -errno; + if (pid > 0) /* parent */ + return 0; + /* child */ + fd = xopen(bb_dev_null, O_RDWR); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + if (fd > 2) close(fd); + setsid(); + openlog(applet_name, LOG_PID, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + return 1; +} + +// TODO +static inline int we_saw_this_host_before(const char *hostname) +{ + return 0; +} + +/* RPC strerror analogs are terminally idiotic: + * *mandatory* prefix and \n at end. + * This hopefully helps. Usage: + * error_msg_rpc(clnt_*error*(" ")) */ +static void error_msg_rpc(const char *msg) +{ + int len; + while (msg[0] == ' ' || msg[0] == ':') msg++; + len = strlen(msg); + while (len && msg[len-1] == '\n') len--; + bb_error_msg("%.*s", len, msg); +} + +// NB: mp->xxx fields may be trashed on exit +static int nfsmount(struct mntent *mp, int vfsflags, char *filteropts) +{ + CLIENT *mclient; + char *hostname; + char *pathname; + char *mounthost; + struct nfs_mount_data data; + char *opt; + struct hostent *hp; + struct sockaddr_in server_addr; + struct sockaddr_in mount_server_addr; + int msock, fsock; + union { + struct fhstatus nfsv2; + struct mountres3 nfsv3; + } status; + int daemonized; + char *s; + int port; + int mountport; + int proto; + int bg; + int soft; + int intr; + int posix; + int nocto; + int noac; + int nolock; + int retry; + int tcp; + int mountprog; + int mountvers; + int nfsprog; + int nfsvers; + int retval; + + find_kernel_nfs_mount_version(); + + daemonized = 0; + mounthost = NULL; + retval = ETIMEDOUT; + msock = fsock = -1; + mclient = NULL; + + /* NB: hostname, mounthost, filteropts must be free()d prior to return */ + + filteropts = xstrdup(filteropts); /* going to trash it later... */ + + hostname = xstrdup(mp->mnt_fsname); + /* mount_main() guarantees that ':' is there */ + s = strchr(hostname, ':'); + pathname = s + 1; + *s = '\0'; + /* Ignore all but first hostname in replicated mounts + until they can be fully supported. (mack@sgi.com) */ + s = strchr(hostname, ','); + if (s) { + *s = '\0'; + bb_error_msg("warning: multiple hostnames not supported"); + } + + server_addr.sin_family = AF_INET; + if (!inet_aton(hostname, &server_addr.sin_addr)) { + hp = gethostbyname(hostname); + if (hp == NULL) { + bb_herror_msg("%s", hostname); + goto fail; + } + if (hp->h_length > sizeof(struct in_addr)) { + bb_error_msg("got bad hp->h_length"); + hp->h_length = sizeof(struct in_addr); + } + memcpy(&server_addr.sin_addr, + hp->h_addr, hp->h_length); + } + + memcpy(&mount_server_addr, &server_addr, sizeof(mount_server_addr)); + + /* add IP address to mtab options for use when unmounting */ + + if (!mp->mnt_opts) { /* TODO: actually mp->mnt_opts is never NULL */ + mp->mnt_opts = xasprintf("addr=%s", inet_ntoa(server_addr.sin_addr)); + } else { + char *tmp = xasprintf("%s%saddr=%s", mp->mnt_opts, + mp->mnt_opts[0] ? "," : "", + inet_ntoa(server_addr.sin_addr)); + free(mp->mnt_opts); + mp->mnt_opts = tmp; + } + + /* Set default options. + * rsize/wsize (and bsize, for ver >= 3) are left 0 in order to + * let the kernel decide. + * timeo is filled in after we know whether it'll be TCP or UDP. */ + memset(&data, 0, sizeof(data)); + data.retrans = 3; + data.acregmin = 3; + data.acregmax = 60; + data.acdirmin = 30; + data.acdirmax = 60; + data.namlen = NAME_MAX; + + bg = 0; + soft = 0; + intr = 0; + posix = 0; + nocto = 0; + nolock = 0; + noac = 0; + retry = 10000; /* 10000 minutes ~ 1 week */ + tcp = 0; + + mountprog = MOUNTPROG; + mountvers = 0; + port = 0; + mountport = 0; + nfsprog = 100003; + nfsvers = 0; + + /* parse options */ + + for (opt = strtok(filteropts, ","); opt; opt = strtok(NULL, ",")) { + char *opteq = strchr(opt, '='); + if (opteq) { + const char *const options[] = { + /* 0 */ "rsize", + /* 1 */ "wsize", + /* 2 */ "timeo", + /* 3 */ "retrans", + /* 4 */ "acregmin", + /* 5 */ "acregmax", + /* 6 */ "acdirmin", + /* 7 */ "acdirmax", + /* 8 */ "actimeo", + /* 9 */ "retry", + /* 10 */ "port", + /* 11 */ "mountport", + /* 12 */ "mounthost", + /* 13 */ "mountprog", + /* 14 */ "mountvers", + /* 15 */ "nfsprog", + /* 16 */ "nfsvers", + /* 17 */ "vers", + /* 18 */ "proto", + /* 19 */ "namlen", + /* 20 */ "addr", + NULL + }; + int val = xatoi_u(opteq + 1); + *opteq = '\0'; + switch (index_in_str_array(options, opt)) { + case 0: // "rsize" + data.rsize = val; + break; + case 1: // "wsize" + data.wsize = val; + break; + case 2: // "timeo" + data.timeo = val; + break; + case 3: // "retrans" + data.retrans = val; + break; + case 4: // "acregmin" + data.acregmin = val; + break; + case 5: // "acregmax" + data.acregmax = val; + break; + case 6: // "acdirmin" + data.acdirmin = val; + break; + case 7: // "acdirmax" + data.acdirmax = val; + break; + case 8: // "actimeo" + data.acregmin = val; + data.acregmax = val; + data.acdirmin = val; + data.acdirmax = val; + break; + case 9: // "retry" + retry = val; + break; + case 10: // "port" + port = val; + break; + case 11: // "mountport" + mountport = val; + break; + case 12: // "mounthost" + mounthost = xstrndup(opteq+1, + strcspn(opteq+1," \t\n\r,")); + break; + case 13: // "mountprog" + mountprog = val; + break; + case 14: // "mountvers" + mountvers = val; + break; + case 15: // "nfsprog" + nfsprog = val; + break; + case 16: // "nfsvers" + case 17: // "vers" + nfsvers = val; + break; + case 18: // "proto" + if (!strncmp(opteq+1, "tcp", 3)) + tcp = 1; + else if (!strncmp(opteq+1, "udp", 3)) + tcp = 0; + else + bb_error_msg("warning: unrecognized proto= option"); + break; + case 19: // "namlen" + if (nfs_mount_version >= 2) + data.namlen = val; + else + bb_error_msg("warning: option namlen is not supported\n"); + break; + case 20: // "addr" - ignore + break; + default: + bb_error_msg("unknown nfs mount parameter: %s=%d", opt, val); + goto fail; + } + } + else { + const char *const options[] = { + "bg", + "fg", + "soft", + "hard", + "intr", + "posix", + "cto", + "ac", + "tcp", + "udp", + "lock", + NULL + }; + int val = 1; + if (!strncmp(opt, "no", 2)) { + val = 0; + opt += 2; + } + switch (index_in_str_array(options, opt)) { + case 0: // "bg" + bg = val; + break; + case 1: // "fg" + bg = !val; + break; + case 2: // "soft" + soft = val; + break; + case 3: // "hard" + soft = !val; + break; + case 4: // "intr" + intr = val; + break; + case 5: // "posix" + posix = val; + break; + case 6: // "cto" + nocto = !val; + break; + case 7: // "ac" + noac = !val; + break; + case 8: // "tcp" + tcp = val; + break; + case 9: // "udp" + tcp = !val; + break; + case 10: // "lock" + if (nfs_mount_version >= 3) + nolock = !val; + else + bb_error_msg("warning: option nolock is not supported"); + break; + default: + bb_error_msg("unknown nfs mount option: %s%s", val ? "" : "no", opt); + goto fail; + } + } + } + proto = (tcp) ? IPPROTO_TCP : IPPROTO_UDP; + + data.flags = (soft ? NFS_MOUNT_SOFT : 0) + | (intr ? NFS_MOUNT_INTR : 0) + | (posix ? NFS_MOUNT_POSIX : 0) + | (nocto ? NFS_MOUNT_NOCTO : 0) + | (noac ? NFS_MOUNT_NOAC : 0); + if (nfs_mount_version >= 2) + data.flags |= (tcp ? NFS_MOUNT_TCP : 0); + if (nfs_mount_version >= 3) + data.flags |= (nolock ? NFS_MOUNT_NONLM : 0); + if (nfsvers > MAX_NFSPROT || mountvers > MAX_NFSPROT) { + bb_error_msg("NFSv%d not supported", nfsvers); + goto fail; + } + if (nfsvers && !mountvers) + mountvers = (nfsvers < 3) ? 1 : nfsvers; + if (nfsvers && nfsvers < mountvers) { + mountvers = nfsvers; + } + + /* Adjust options if none specified */ + if (!data.timeo) + data.timeo = tcp ? 70 : 7; + + data.version = nfs_mount_version; + + if (vfsflags & MS_REMOUNT) + goto do_mount; + + /* + * If the previous mount operation on the same host was + * backgrounded, and the "bg" for this mount is also set, + * give up immediately, to avoid the initial timeout. + */ + if (bg && we_saw_this_host_before(hostname)) { + daemonized = daemonize(); /* parent or error */ + if (daemonized <= 0) { /* parent or error */ + retval = -daemonized; + goto ret; + } + } + + /* create mount daemon client */ + /* See if the nfs host = mount host. */ + if (mounthost) { + if (mounthost[0] >= '0' && mounthost[0] <= '9') { + mount_server_addr.sin_family = AF_INET; + mount_server_addr.sin_addr.s_addr = inet_addr(hostname); + } else { + hp = gethostbyname(mounthost); + if (hp == NULL) { + bb_herror_msg("%s", mounthost); + goto fail; + } else { + if (hp->h_length > sizeof(struct in_addr)) { + bb_error_msg("got bad hp->h_length?"); + hp->h_length = sizeof(struct in_addr); + } + mount_server_addr.sin_family = AF_INET; + memcpy(&mount_server_addr.sin_addr, + hp->h_addr, hp->h_length); + } + } + } + + /* + * The following loop implements the mount retries. When the mount + * times out, and the "bg" option is set, we background ourself + * and continue trying. + * + * The case where the mount point is not present and the "bg" + * option is set, is treated as a timeout. This is done to + * support nested mounts. + * + * The "retry" count specified by the user is the number of + * minutes to retry before giving up. + */ + { + struct timeval total_timeout; + struct timeval retry_timeout; + struct pmap* pm_mnt; + time_t t; + time_t prevt; + time_t timeout; + + retry_timeout.tv_sec = 3; + retry_timeout.tv_usec = 0; + total_timeout.tv_sec = 20; + total_timeout.tv_usec = 0; + timeout = time(NULL) + 60 * retry; + prevt = 0; + t = 30; +retry: + /* be careful not to use too many CPU cycles */ + if (t - prevt < 30) + sleep(30); + + pm_mnt = get_mountport(&mount_server_addr, + mountprog, + mountvers, + proto, + mountport); + nfsvers = (pm_mnt->pm_vers < 2) ? 2 : pm_mnt->pm_vers; + + /* contact the mount daemon via TCP */ + mount_server_addr.sin_port = htons(pm_mnt->pm_port); + msock = RPC_ANYSOCK; + + switch (pm_mnt->pm_prot) { + case IPPROTO_UDP: + mclient = clntudp_create(&mount_server_addr, + pm_mnt->pm_prog, + pm_mnt->pm_vers, + retry_timeout, + &msock); + if (mclient) + break; + mount_server_addr.sin_port = htons(pm_mnt->pm_port); + msock = RPC_ANYSOCK; + case IPPROTO_TCP: + mclient = clnttcp_create(&mount_server_addr, + pm_mnt->pm_prog, + pm_mnt->pm_vers, + &msock, 0, 0); + break; + default: + mclient = 0; + } + if (!mclient) { + if (!daemonized && prevt == 0) + error_msg_rpc(clnt_spcreateerror(" ")); + } else { + enum clnt_stat clnt_stat; + /* try to mount hostname:pathname */ + mclient->cl_auth = authunix_create_default(); + + /* make pointers in xdr_mountres3 NULL so + * that xdr_array allocates memory for us + */ + memset(&status, 0, sizeof(status)); + + if (pm_mnt->pm_vers == 3) + clnt_stat = clnt_call(mclient, MOUNTPROC3_MNT, + (xdrproc_t) xdr_dirpath, + (caddr_t) &pathname, + (xdrproc_t) xdr_mountres3, + (caddr_t) &status, + total_timeout); + else + clnt_stat = clnt_call(mclient, MOUNTPROC_MNT, + (xdrproc_t) xdr_dirpath, + (caddr_t) &pathname, + (xdrproc_t) xdr_fhstatus, + (caddr_t) &status, + total_timeout); + + if (clnt_stat == RPC_SUCCESS) + goto prepare_kernel_data; /* we're done */ + if (errno != ECONNREFUSED) { + error_msg_rpc(clnt_sperror(mclient, " ")); + goto fail; /* don't retry */ + } + /* Connection refused */ + if (!daemonized && prevt == 0) /* print just once */ + error_msg_rpc(clnt_sperror(mclient, " ")); + auth_destroy(mclient->cl_auth); + clnt_destroy(mclient); + mclient = 0; + close(msock); + } + + /* Timeout. We are going to retry... maybe */ + + if (!bg) + goto fail; + if (!daemonized) { + daemonized = daemonize(); + if (daemonized <= 0) { /* parent or error */ + retval = -daemonized; + goto ret; + } + } + prevt = t; + t = time(NULL); + if (t >= timeout) + /* TODO error message */ + goto fail; + + goto retry; + } + +prepare_kernel_data: + + if (nfsvers == 2) { + if (status.nfsv2.fhs_status != 0) { + bb_error_msg("%s:%s failed, reason given by server: %s", + hostname, pathname, + nfs_strerror(status.nfsv2.fhs_status)); + goto fail; + } + memcpy(data.root.data, + (char *) status.nfsv2.fhstatus_u.fhs_fhandle, + NFS_FHSIZE); + data.root.size = NFS_FHSIZE; + memcpy(data.old_root.data, + (char *) status.nfsv2.fhstatus_u.fhs_fhandle, + NFS_FHSIZE); + } else { + fhandle3 *my_fhandle; + if (status.nfsv3.fhs_status != 0) { + bb_error_msg("%s:%s failed, reason given by server: %s", + hostname, pathname, + nfs_strerror(status.nfsv3.fhs_status)); + goto fail; + } + my_fhandle = &status.nfsv3.mountres3_u.mountinfo.fhandle; + memset(data.old_root.data, 0, NFS_FHSIZE); + memset(&data.root, 0, sizeof(data.root)); + data.root.size = my_fhandle->fhandle3_len; + memcpy(data.root.data, + (char *) my_fhandle->fhandle3_val, + my_fhandle->fhandle3_len); + + data.flags |= NFS_MOUNT_VER3; + } + + /* create nfs socket for kernel */ + + if (tcp) { + if (nfs_mount_version < 3) { + bb_error_msg("NFS over TCP is not supported"); + goto fail; + } + fsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + } else + fsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fsock < 0) { + bb_perror_msg("nfs socket"); + goto fail; + } + if (bindresvport(fsock, 0) < 0) { + bb_perror_msg("nfs bindresvport"); + goto fail; + } + if (port == 0) { + server_addr.sin_port = PMAPPORT; + port = pmap_getport(&server_addr, nfsprog, nfsvers, + tcp ? IPPROTO_TCP : IPPROTO_UDP); + if (port == 0) + port = NFS_PORT; + } + server_addr.sin_port = htons(port); + + /* prepare data structure for kernel */ + + data.fd = fsock; + memcpy((char *) &data.addr, (char *) &server_addr, sizeof(data.addr)); + strncpy(data.hostname, hostname, sizeof(data.hostname)); + + /* clean up */ + + auth_destroy(mclient->cl_auth); + clnt_destroy(mclient); + close(msock); + + if (bg) { + /* We must wait until mount directory is available */ + struct stat statbuf; + int delay = 1; + while (stat(mp->mnt_dir, &statbuf) == -1) { + if (!daemonized) { + daemonized = daemonize(); + if (daemonized <= 0) { /* parent or error */ + retval = -daemonized; + goto ret; + } + } + sleep(delay); /* 1, 2, 4, 8, 16, 30, ... */ + delay *= 2; + if (delay > 30) + delay = 30; + } + } + +do_mount: /* perform actual mount */ + + mp->mnt_type = "nfs"; + retval = mount_it_now(mp, vfsflags, (char*)&data); + goto ret; + +fail: /* abort */ + + if (msock != -1) { + if (mclient) { + auth_destroy(mclient->cl_auth); + clnt_destroy(mclient); + } + close(msock); + } + if (fsock != -1) + close(fsock); + +ret: + free(hostname); + free(mounthost); + free(filteropts); + return retval; +} + +#else /* !ENABLE_FEATURE_MOUNT_NFS */ + +/* Never called. Call should be optimized out. */ +int nfsmount(struct mntent *mp, int vfsflags, char *filteropts); + +#endif /* !ENABLE_FEATURE_MOUNT_NFS */ + +// Mount one directory. Handles CIFS, NFS, loopback, autobind, and filesystem +// type detection. Returns 0 for success, nonzero for failure. +// NB: mp->xxx fields may be trashed on exit +static int singlemount(struct mntent *mp, int ignore_busy) +{ + int rc = -1, vfsflags; + char *loopFile = 0, *filteropts = 0; + llist_t *fl = 0; + struct stat st; + + vfsflags = parse_mount_options(mp->mnt_opts, &filteropts); + + // Treat fstype "auto" as unspecified. + + if (mp->mnt_type && !strcmp(mp->mnt_type,"auto")) mp->mnt_type = 0; + + // Might this be an CIFS filesystem? + + if (ENABLE_FEATURE_MOUNT_CIFS && + (!mp->mnt_type || !strcmp(mp->mnt_type,"cifs")) && + (mp->mnt_fsname[0]==mp->mnt_fsname[1] && (mp->mnt_fsname[0]=='/' || mp->mnt_fsname[0]=='\\'))) + { + struct hostent *he; + char ip[32], *s; + + rc = 1; + // Replace '/' with '\' and verify that unc points to "//server/share". + + for (s = mp->mnt_fsname; *s; ++s) + if (*s == '/') *s = '\\'; + + // get server IP + + s = strrchr(mp->mnt_fsname, '\\'); + if (s == mp->mnt_fsname+1) goto report_error; + *s = 0; + he = gethostbyname(mp->mnt_fsname+2); + *s = '\\'; + if (!he) goto report_error; + + // Insert ip=... option into string flags. (NOTE: Add IPv6 support.) + + sprintf(ip, "ip=%d.%d.%d.%d", he->h_addr[0], he->h_addr[1], + he->h_addr[2], he->h_addr[3]); + parse_mount_options(ip, &filteropts); + + // compose new unc '\\server-ip\share' + + mp->mnt_fsname = xasprintf("\\\\%s%s", ip+3, + strchr(mp->mnt_fsname+2,'\\')); + + // lock is required + vfsflags |= MS_MANDLOCK; + + mp->mnt_type = "cifs"; + rc = mount_it_now(mp, vfsflags, filteropts); + if (ENABLE_FEATURE_CLEAN_UP) free(mp->mnt_fsname); + goto report_error; + } + + // Might this be an NFS filesystem? + + if (ENABLE_FEATURE_MOUNT_NFS && + (!mp->mnt_type || !strcmp(mp->mnt_type,"nfs")) && + strchr(mp->mnt_fsname, ':') != NULL) + { + rc = nfsmount(mp, vfsflags, filteropts); + goto report_error; + } + + // Look at the file. (Not found isn't a failure for remount, or for + // a synthetic filesystem like proc or sysfs.) + + if (!lstat(mp->mnt_fsname, &st) && !(vfsflags & (MS_REMOUNT | MS_BIND | MS_MOVE))) + { + // Do we need to allocate a loopback device for it? + + if (ENABLE_FEATURE_MOUNT_LOOP && S_ISREG(st.st_mode)) { + loopFile = bb_simplify_path(mp->mnt_fsname); + mp->mnt_fsname = 0; + switch (set_loop(&(mp->mnt_fsname), loopFile, 0)) { + case 0: + case 1: + break; + default: + bb_error_msg( errno == EPERM || errno == EACCES + ? bb_msg_perm_denied_are_you_root + : "cannot setup loop device"); + return errno; + } + + // Autodetect bind mounts + + } else if (S_ISDIR(st.st_mode) && !mp->mnt_type) + vfsflags |= MS_BIND; + } + + /* If we know the fstype (or don't need to), jump straight + * to the actual mount. */ + + if (mp->mnt_type || (vfsflags & (MS_REMOUNT | MS_BIND | MS_MOVE))) + rc = mount_it_now(mp, vfsflags, filteropts); + + // Loop through filesystem types until mount succeeds or we run out + + else { + + /* Initialize list of block backed filesystems. This has to be + * done here so that during "mount -a", mounts after /proc shows up + * can autodetect. */ + + if (!fslist) { + fslist = get_block_backed_filesystems(); + if (ENABLE_FEATURE_CLEAN_UP && fslist) + atexit(delete_block_backed_filesystems); + } + + for (fl = fslist; fl; fl = fl->link) { + mp->mnt_type = fl->data; + rc = mount_it_now(mp, vfsflags, filteropts); + if (!rc) break; + } + } + + // If mount failed, clean up loop file (if any). + + if (ENABLE_FEATURE_MOUNT_LOOP && rc && loopFile) { + del_loop(mp->mnt_fsname); + if (ENABLE_FEATURE_CLEAN_UP) { + free(loopFile); + free(mp->mnt_fsname); + } + } + +report_error: + if (ENABLE_FEATURE_CLEAN_UP) free(filteropts); + + if (rc && errno == EBUSY && ignore_busy) rc = 0; + if (rc < 0) + /* perror here sometimes says "mounting ... on ... failed: Success" */ + bb_error_msg("mounting %s on %s failed", mp->mnt_fsname, mp->mnt_dir); + + return rc; +} + +// Parse options, if necessary parse fstab/mtab, and call singlemount for +// each directory to be mounted. + +const char must_be_root[] = "you must be root"; + +int mount_main(int argc, char **argv) +{ + enum { OPT_ALL = 0x10 }; + + char *cmdopts = xstrdup(""), *fstype=0, *storage_path=0; + char *opt_o; + const char *fstabname; + FILE *fstab; + int i, j, rc = 0; + unsigned opt; + struct mntent mtpair[2], *mtcur = mtpair; + SKIP_DESKTOP(const int nonroot = 0;) + USE_DESKTOP( int nonroot = (getuid() != 0);) + + /* parse long options, like --bind and --move. Note that -o option + * and --option are synonymous. Yes, this means --remount,rw works. */ + + for (i = j = 0; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] == '-') { + append_mount_options(&cmdopts, argv[i]+2); + } else argv[j++] = argv[i]; + } + argv[j] = 0; + argc = j; + + // Parse remaining options + + opt = getopt32(argc, argv, "o:t:rwanfvs", &opt_o, &fstype); + if (opt & 0x1) append_mount_options(&cmdopts, opt_o); // -o + //if (opt & 0x2) // -t + if (opt & 0x4) append_mount_options(&cmdopts, "ro"); // -r + if (opt & 0x8) append_mount_options(&cmdopts, "rw"); // -w + //if (opt & 0x10) // -a + if (opt & 0x20) USE_FEATURE_MTAB_SUPPORT(useMtab = 0); // -n + if (opt & 0x40) USE_FEATURE_MTAB_SUPPORT(fakeIt = 1); // -f + //if (opt & 0x80) // -v: verbose (ignore) + //if (opt & 0x100) // -s: sloppy (ignore) + argv += optind; + argc -= optind; + + // Three or more non-option arguments? Die with a usage message. + + if (argc > 2) bb_show_usage(); + + // If we have no arguments, show currently mounted filesystems + + if (!argc) { + if (!(opt & OPT_ALL)) { + FILE *mountTable = setmntent(bb_path_mtab_file, "r"); + + if (!mountTable) bb_error_msg_and_die("no %s", bb_path_mtab_file); + + while (getmntent_r(mountTable, mtpair, bb_common_bufsiz1, + sizeof(bb_common_bufsiz1))) + { + // Don't show rootfs. FIXME: why?? + if (!strcmp(mtpair->mnt_fsname, "rootfs")) continue; + + if (!fstype || !strcmp(mtpair->mnt_type, fstype)) + printf("%s on %s type %s (%s)\n", mtpair->mnt_fsname, + mtpair->mnt_dir, mtpair->mnt_type, + mtpair->mnt_opts); + } + if (ENABLE_FEATURE_CLEAN_UP) endmntent(mountTable); + return EXIT_SUCCESS; + } + } else storage_path = bb_simplify_path(argv[0]); + + // When we have two arguments, the second is the directory and we can + // skip looking at fstab entirely. We can always abspath() the directory + // argument when we get it. + + if (argc == 2) { + if (nonroot) + bb_error_msg_and_die(must_be_root); + mtpair->mnt_fsname = argv[0]; + mtpair->mnt_dir = argv[1]; + mtpair->mnt_type = fstype; + mtpair->mnt_opts = cmdopts; + rc = singlemount(mtpair, 0); + goto clean_up; + } + + i = parse_mount_options(cmdopts, 0); + if (nonroot && (i & ~MS_SILENT)) // Non-root users cannot specify flags + bb_error_msg_and_die(must_be_root); + + // If we have a shared subtree flag, don't worry about fstab or mtab. + + if (ENABLE_FEATURE_MOUNT_FLAGS && + (i & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))) + { + rc = mount("", argv[0], "", i, ""); + if (rc) bb_perror_msg_and_die("%s", argv[0]); + goto clean_up; + } + + // Open either fstab or mtab + + fstabname = "/etc/fstab"; + if (i & MS_REMOUNT) { + fstabname = bb_path_mtab_file; + } + fstab = setmntent(fstabname, "r"); + if (!fstab) + bb_perror_msg_and_die("cannot read %s", fstabname); + + // Loop through entries until we find what we're looking for. + + memset(mtpair, 0, sizeof(mtpair)); + for (;;) { + struct mntent *mtnext = (mtcur==mtpair ? mtpair+1 : mtpair); + + // Get next fstab entry + + if (!getmntent_r(fstab, mtcur, bb_common_bufsiz1 + + (mtcur==mtpair ? sizeof(bb_common_bufsiz1)/2 : 0), + sizeof(bb_common_bufsiz1)/2)) + { + // Were we looking for something specific? + + if (argc) { + + // If we didn't find anything, complain. + + if (!mtnext->mnt_fsname) + bb_error_msg_and_die("can't find %s in %s", + argv[0], fstabname); + + mtcur = mtnext; + if (nonroot) { + // fstab must have "users" or "user" + if (!(parse_mount_options(mtcur->mnt_opts, 0) & MOUNT_USERS)) + bb_error_msg_and_die(must_be_root); + } + + // Mount the last thing we found. + + mtcur->mnt_opts = xstrdup(mtcur->mnt_opts); + append_mount_options(&(mtcur->mnt_opts), cmdopts); + rc = singlemount(mtcur, 0); + free(mtcur->mnt_opts); + } + goto clean_up; + } + + /* If we're trying to mount something specific and this isn't it, + * skip it. Note we must match both the exact text in fstab (ala + * "proc") or a full path from root */ + + if (argc) { + + // Is this what we're looking for? + + if (strcmp(argv[0], mtcur->mnt_fsname) && + strcmp(storage_path, mtcur->mnt_fsname) && + strcmp(argv[0], mtcur->mnt_dir) && + strcmp(storage_path, mtcur->mnt_dir)) continue; + + // Remember this entry. Something later may have overmounted + // it, and we want the _last_ match. + + mtcur = mtnext; + + // If we're mounting all. + + } else { + // Do we need to match a filesystem type? + // TODO: support "-t type1,type2"; "-t notype1,type2" + + if (fstype && strcmp(mtcur->mnt_type, fstype)) continue; + + // Skip noauto and swap anyway. + + if (parse_mount_options(mtcur->mnt_opts, 0) + & (MOUNT_NOAUTO | MOUNT_SWAP)) continue; + + // No, mount -a won't mount anything, + // even user mounts, for mere humans. + + if (nonroot) + bb_error_msg_and_die(must_be_root); + + // Mount this thing. + + if (singlemount(mtcur, 1)) { + /* Count number of failed mounts */ + rc++; + } + } + } + if (ENABLE_FEATURE_CLEAN_UP) endmntent(fstab); + +clean_up: + + if (ENABLE_FEATURE_CLEAN_UP) { + free(storage_path); + free(cmdopts); + } + + return rc; +} diff --git a/util-linux/pivot_root.c b/util-linux/pivot_root.c new file mode 100644 index 000000000..bd02302c7 --- /dev/null +++ b/util-linux/pivot_root.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * pivot_root.c - Change root file system. Based on util-linux 2.10s + * + * busyboxed by Evin Robertson + * pivot_root syscall stubbed by Erik Andersen, so it will compile + * regardless of the kernel being used. + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ +#include "busybox.h" + +extern int pivot_root(const char * new_root,const char * put_old); + +int pivot_root_main(int argc, char **argv) +{ + if (argc != 3) + bb_show_usage(); + + if (pivot_root(argv[1],argv[2]) < 0) + bb_perror_msg_and_die("pivot_root"); + + return EXIT_SUCCESS; +} diff --git a/util-linux/rdate.c b/util-linux/rdate.c new file mode 100644 index 000000000..12105953d --- /dev/null +++ b/util-linux/rdate.c @@ -0,0 +1,86 @@ +/* vi: set sw=4 ts=4: */ +/* + * The Rdate command will ask a time server for the RFC 868 time + * and optionally set the system time. + * + * by Sterling Huxley <sterling@europa.com> + * + * Licensed under GPL v2 or later, see file License for details. +*/ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> + +#include "busybox.h" + + +static const int RFC_868_BIAS = 2208988800UL; + +static void socket_timeout(int sig) +{ + bb_error_msg_and_die("timeout connecting to time server"); +} + +static time_t askremotedate(const char *host) +{ + unsigned long nett; + struct sockaddr_in s_in; + int fd; + + bb_lookup_host(&s_in, host); + s_in.sin_port = bb_lookup_port("time", "tcp", 37); + + /* Add a timeout for dead or inaccessible servers */ + alarm(10); + signal(SIGALRM, socket_timeout); + + fd = xconnect_tcp_v4(&s_in); + + if (safe_read(fd, (void *)&nett, 4) != 4) /* read time from server */ + bb_error_msg_and_die("%s did not send the complete time", host); + close(fd); + + /* convert from network byte order to local byte order. + * RFC 868 time is the number of seconds + * since 00:00 (midnight) 1 January 1900 GMT + * the RFC 868 time 2,208,988,800 corresponds to 00:00 1 Jan 1970 GMT + * Subtract the RFC 868 time to get Linux epoch + */ + + return ntohl(nett) - RFC_868_BIAS; +} + +int rdate_main(int argc, char **argv) +{ + time_t remote_time; + unsigned long flags; + + opt_complementary = "-1"; + flags = getopt32(argc, argv, "sp"); + + remote_time = askremotedate(argv[optind]); + + if ((flags & 2) == 0) { + time_t current_time; + + time(¤t_time); + if (current_time == remote_time) + bb_error_msg("current time matches remote time"); + else + if (stime(&remote_time) < 0) + bb_perror_msg_and_die("cannot set time of day"); + } + + if ((flags & 1) == 0) + printf("%s", ctime(&remote_time)); + + return EXIT_SUCCESS; +} diff --git a/util-linux/readprofile.c b/util-linux/readprofile.c new file mode 100644 index 000000000..dd810f021 --- /dev/null +++ b/util-linux/readprofile.c @@ -0,0 +1,236 @@ +/* vi: set sw=4 ts=4: */ +/* + * readprofile.c - used to read /proc/profile + * + * Copyright (C) 1994,1996 Alessandro Rubini (rubini@ipvvis.unipv.it) + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * 1999-09-01 Stephane Eranian <eranian@cello.hpl.hp.com> + * - 64bit clean patch + * 3Feb2001 Andrew Morton <andrewm@uow.edu.au> + * - -M option to write profile multiplier. + * 2001-11-07 Werner Almesberger <wa@almesberger.net> + * - byte order auto-detection and -n option + * 2001-11-09 Werner Almesberger <wa@almesberger.net> + * - skip step size (index 0) + * 2002-03-09 John Levon <moz@compsoc.man.ac.uk> + * - make maplineno do something + * 2002-11-28 Mads Martin Joergensen + + * - also try /boot/System.map-`uname -r` + * 2003-04-09 Werner Almesberger <wa@almesberger.net> + * - fixed off-by eight error and improved heuristics in byte order detection + * 2003-08-12 Nikita Danilov <Nikita@Namesys.COM> + * - added -s option; example of use: + * "readprofile -s -m /boot/System.map-test | grep __d_lookup | sort -n -k3" + * + * Taken from util-linux and adapted for busybox by + * Paul Mundt <lethal@linux-sh.org>. + */ + +#include "busybox.h" +#include <sys/utsname.h> + +#define S_LEN 128 + +/* These are the defaults */ +static const char defaultmap[] = "/boot/System.map"; +static const char defaultpro[] = "/proc/profile"; + +int readprofile_main(int argc, char **argv) +{ + FILE *map; + const char *mapFile, *proFile, *mult = 0; + unsigned long indx = 1; + size_t len; + uint64_t add0 = 0; + unsigned int step; + unsigned int *buf, total, fn_len; + unsigned long long fn_add, next_add; /* current and next address */ + char fn_name[S_LEN], next_name[S_LEN]; /* current and next name */ + char mapline[S_LEN]; + char mode[8]; + int optAll = 0, optInfo = 0, optReset = 0; + int optVerbose = 0, optNative = 0; + int optBins = 0, optSub = 0; + int maplineno = 1; + int header_printed; + +#define next (current^1) + + proFile = defaultpro; + mapFile = defaultmap; + + opt_complementary = "nn:aa:bb:ss:ii:rr:vv"; + getopt32(argc, argv, "M:m:p:nabsirv", + &mult, &mapFile, &proFile, + &optNative, &optAll, &optBins, &optSub, + &optInfo, &optReset, &optVerbose); + + if (optReset || mult) { + int multiplier, fd, to_write; + + /* + * When writing the multiplier, if the length of the write is + * not sizeof(int), the multiplier is not changed + */ + if (mult) { + multiplier = xatoi_u(mult); + to_write = sizeof(int); + } else { + multiplier = 0; + to_write = 1; /* sth different from sizeof(int) */ + } + + fd = xopen(defaultpro, O_WRONLY); + + if (full_write(fd, &multiplier, to_write) != to_write) + bb_perror_msg_and_die("error writing %s", defaultpro); + + close(fd); + return EXIT_SUCCESS; + } + + /* + * Use an fd for the profiling buffer, to skip stdio overhead + */ + len = INT_MAX; + buf = xmalloc_open_read_close(proFile, &len); + if (!optNative) { + int entries = len/sizeof(*buf); + int big = 0, small = 0, i; + unsigned *p; + + for (p = buf+1; p < buf+entries; p++) { + if (*p & ~0U << (sizeof(*buf)*4)) + big++; + if (*p & ((1 << (sizeof(*buf)*4))-1)) + small++; + } + if (big > small) { + bb_error_msg("assuming reversed byte order, " + "use -n to force native byte order"); + for (p = buf; p < buf+entries; p++) + for (i = 0; i < sizeof(*buf)/2; i++) { + unsigned char *b = (unsigned char *) p; + unsigned char tmp; + + tmp = b[i]; + b[i] = b[sizeof(*buf)-i-1]; + b[sizeof(*buf)-i-1] = tmp; + } + } + } + + step = buf[0]; + if (optInfo) { + printf("Sampling_step: %i\n", step); + return EXIT_SUCCESS; + } + + total = 0; + + map = xfopen(mapFile, "r"); + + while (fgets(mapline, S_LEN, map)) { + if (sscanf(mapline, "%llx %s %s", &fn_add, mode, fn_name) != 3) + bb_error_msg_and_die("%s(%i): wrong map line", + mapFile, maplineno); + + if (!strcmp(fn_name, "_stext")) /* only elf works like this */ { + add0 = fn_add; + break; + } + maplineno++; + } + + if (!add0) + bb_error_msg_and_die("can't find \"_stext\" in %s", mapFile); + + /* + * Main loop. + */ + while (fgets(mapline, S_LEN, map)) { + unsigned int this = 0; + + if (sscanf(mapline, "%llx %s %s", &next_add, mode, next_name) != 3) + bb_error_msg_and_die("%s(%i): wrong map line", + mapFile, maplineno); + + header_printed = 0; + + /* ignore any LEADING (before a '[tT]' symbol is found) + Absolute symbols */ + if ((*mode == 'A' || *mode == '?') && total == 0) continue; + if (*mode != 'T' && *mode != 't' && + *mode != 'W' && *mode != 'w') + break; /* only text is profiled */ + + if (indx >= len / sizeof(*buf)) + bb_error_msg_and_die("profile address out of range. " + "Wrong map file?"); + + while (indx < (next_add-add0)/step) { + if (optBins && (buf[indx] || optAll)) { + if (!header_printed) { + printf("%s:\n", fn_name); + header_printed = 1; + } + printf("\t%"PRIx64"\t%u\n", (indx - 1)*step + add0, buf[indx]); + } + this += buf[indx++]; + } + total += this; + + if (optBins) { + if (optVerbose || this > 0) + printf(" total\t\t\t\t%u\n", this); + } else if ((this || optAll) && + (fn_len = next_add-fn_add) != 0) { + if (optVerbose) + printf("%016llx %-40s %6i %8.4f\n", fn_add, + fn_name, this, this/(double)fn_len); + else + printf("%6i %-40s %8.4f\n", + this, fn_name, this/(double)fn_len); + if (optSub) { + unsigned long long scan; + + for (scan = (fn_add-add0)/step + 1; + scan < (next_add-add0)/step; scan++) { + unsigned long long addr; + + addr = (scan - 1)*step + add0; + printf("\t%#llx\t%s+%#llx\t%u\n", + addr, fn_name, addr - fn_add, + buf[scan]); + } + } + } + + fn_add = next_add; + strcpy(fn_name, next_name); + + maplineno++; + } + + /* clock ticks, out of kernel text - probably modules */ + printf("%6i %s\n", buf[len/sizeof(*buf)-1], "*unknown*"); + + /* trailer */ + if (optVerbose) + printf("%016x %-40s %6i %8.4f\n", + 0, "total", total, total/(double)(fn_add-add0)); + else + printf("%6i %-40s %8.4f\n", + total, "total", total/(double)(fn_add-add0)); + + fclose(map); + free(buf); + + return EXIT_SUCCESS; +} diff --git a/util-linux/setarch.c b/util-linux/setarch.c new file mode 100644 index 000000000..d7e1c0917 --- /dev/null +++ b/util-linux/setarch.c @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: */ +/* + * Linux32/linux64 allows for changing uname emulation. + * + * Copyright 2002 Andi Kleen, SuSE Labs. + * + * Licensed under GPL v2 or later, see file License for details. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <sys/personality.h> + +#include "busybox.h" + +int setarch_main(int ATTRIBUTE_UNUSED argc, char **argv) +{ + int pers = -1; + + /* Figure out what personality we are supposed to switch to ... + * we can be invoked as either: + * argv[0],argv[1] -> "setarch","personality" + * argv[0] -> "personality" + */ +retry: + if (argv[0][5] == '6') /* linux64 */ + pers = PER_LINUX; + else if (argv[0][5] == '3') /* linux32 */ + pers = PER_LINUX32; + else if (pers == -1 && argv[1] != NULL) { + pers = PER_LINUX32; + ++argv; + goto retry; + } + + /* make user actually gave us something to do */ + ++argv; + if (argv[0] == NULL) + bb_show_usage(); + + /* Try to set personality */ + if (personality(pers) >= 0) { + + /* Try to execute the program */ + execvp(argv[0], argv); + } + + bb_perror_msg_and_die("%s", argv[0]); +} diff --git a/util-linux/swaponoff.c b/util-linux/swaponoff.c new file mode 100644 index 000000000..8434d121c --- /dev/null +++ b/util-linux/swaponoff.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini swapon/swapoff implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include <mntent.h> +#include <sys/swap.h> + + +static int swap_enable_disable(char *device) +{ + int status; + struct stat st; + + xstat(device, &st); + + /* test for holes */ + if (S_ISREG(st.st_mode)) + if (st.st_blocks * 512 < st.st_size) + bb_error_msg_and_die("swap file has holes"); + + if (applet_name[5] == 'n') + status = swapon(device, 0); + else + status = swapoff(device); + + if (status != 0) { + bb_perror_msg("%s", device); + return 1; + } + + return 0; +} + +static int do_em_all(void) +{ + struct mntent *m; + FILE *f; + int err; + + f = setmntent("/etc/fstab", "r"); + if (f == NULL) + bb_perror_msg_and_die("/etc/fstab"); + + err = 0; + while ((m = getmntent(f)) != NULL) + if (strcmp(m->mnt_type, MNTTYPE_SWAP) == 0) + err += swap_enable_disable(m->mnt_fsname); + + endmntent(f); + + return err; +} + +#define DO_ALL 0x01 + +int swap_on_off_main(int argc, char **argv) +{ + int ret; + + if (argc == 1) + bb_show_usage(); + + ret = getopt32(argc, argv, "a"); + if (ret & DO_ALL) + return do_em_all(); + + ret = 0; + while (*++argv) + ret += swap_enable_disable(*argv); + return ret; +} diff --git a/util-linux/switch_root.c b/util-linux/switch_root.c new file mode 100644 index 000000000..4c23f69da --- /dev/null +++ b/util-linux/switch_root.c @@ -0,0 +1,122 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright 2005 Rob Landley <rob@landley.net> + * + * Switch from rootfs to another filesystem as the root of the mount tree. + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <sys/vfs.h> + + +// Make up for header deficiencies. + +#ifndef RAMFS_MAGIC +#define RAMFS_MAGIC 0x858458f6 +#endif + +#ifndef TMPFS_MAGIC +#define TMPFS_MAGIC 0x01021994 +#endif + +#ifndef MS_MOVE +#define MS_MOVE 8192 +#endif + +dev_t rootdev; + +// Recursively delete contents of rootfs. + +static void delete_contents(char *directory) +{ + DIR *dir; + struct dirent *d; + struct stat st; + + // Don't descend into other filesystems + if (lstat(directory, &st) || st.st_dev != rootdev) return; + + // Recursively delete the contents of directories. + if (S_ISDIR(st.st_mode)) { + if((dir = opendir(directory))) { + while ((d = readdir(dir))) { + char *newdir=d->d_name; + + // Skip . and .. + if(*newdir=='.' && (!newdir[1] || (newdir[1]=='.' && !newdir[2]))) + continue; + + // Recurse to delete contents + newdir = alloca(strlen(directory) + strlen(d->d_name) + 2); + sprintf(newdir, "%s/%s", directory, d->d_name); + delete_contents(newdir); + } + closedir(dir); + + // Directory should now be empty. Zap it. + rmdir(directory); + } + + // It wasn't a directory. Zap it. + + } else unlink(directory); +} + +int switch_root_main(int argc, char *argv[]) +{ + char *newroot, *console=NULL; + struct stat st1, st2; + struct statfs stfs; + + // Parse args (-c console) + + opt_complementary = "-2"; + getopt32(argc, argv, "c:", &console); + + // Change to new root directory and verify it's a different fs. + + newroot=argv[optind++]; + + if (chdir(newroot) || lstat(".", &st1) || lstat("/", &st2) || + st1.st_dev == st2.st_dev) + { + bb_error_msg_and_die("bad newroot %s", newroot); + } + rootdev=st2.st_dev; + + // Additional sanity checks: we're about to rm -rf /, so be REALLY SURE + // we mean it. (I could make this a CONFIG option, but I would get email + // from all the people who WILL eat their filesystemss.) + + if (lstat("/init", &st1) || !S_ISREG(st1.st_mode) || statfs("/", &stfs) || + (stfs.f_type != RAMFS_MAGIC && stfs.f_type != TMPFS_MAGIC) || + getpid() != 1) + { + bb_error_msg_and_die("not rootfs"); + } + + // Zap everything out of rootdev + + delete_contents("/"); + + // Overmount / with newdir and chroot into it. The chdir is needed to + // recalculate "." and ".." links. + + if (mount(".", "/", NULL, MS_MOVE, NULL) || chroot(".") || chdir("/")) + bb_error_msg_and_die("moving root"); + + // If a new console specified, redirect stdin/stdout/stderr to that. + + if (console) { + close(0); + if(open(console, O_RDWR) < 0) + bb_error_msg_and_die("bad console '%s'", console); + dup2(0, 1); + dup2(0, 2); + } + + // Exec real init. (This is why we must be pid 1.) + execv(argv[optind], argv+optind); + bb_error_msg_and_die("bad init '%s'", argv[optind]); +} diff --git a/util-linux/umount.c b/util-linux/umount.c new file mode 100644 index 000000000..6ba72aed1 --- /dev/null +++ b/util-linux/umount.c @@ -0,0 +1,151 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini umount implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * Copyright (C) 2005 by Rob Landley <rob@landley.net> + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <mntent.h> +#include <getopt.h> + +#define OPTION_STRING "flDnravd" +#define OPT_FORCE 1 +#define OPT_LAZY 2 +#define OPT_DONTFREELOOP 4 +#define OPT_NO_MTAB 8 +#define OPT_REMOUNT 16 +#define OPT_ALL (ENABLE_FEATURE_UMOUNT_ALL ? 32 : 0) + +int umount_main(int argc, char **argv) +{ + int doForce; + char path[2*PATH_MAX]; + struct mntent me; + FILE *fp; + int status = EXIT_SUCCESS; + unsigned opt; + struct mtab_list { + char *dir; + char *device; + struct mtab_list *next; + } *mtl, *m; + + /* Parse any options */ + + opt = getopt32(argc, argv, OPTION_STRING); + + argc -= optind; + argv += optind; + + doForce = MAX((opt & OPT_FORCE), (opt & OPT_LAZY)); + + /* Get a list of mount points from mtab. We read them all in now mostly + * for umount -a (so we don't have to worry about the list changing while + * we iterate over it, or about getting stuck in a loop on the same failing + * entry. Notice that this also naturally reverses the list so that -a + * umounts the most recent entries first. */ + + m = mtl = 0; + + /* If we're umounting all, then m points to the start of the list and + * the argument list should be empty (which will match all). */ + + fp = setmntent(bb_path_mtab_file, "r"); + if (!fp) { + if (opt & OPT_ALL) + bb_error_msg_and_die("cannot open %s", bb_path_mtab_file); + } else { + while (getmntent_r(fp, &me, path, sizeof(path))) { + m = xmalloc(sizeof(struct mtab_list)); + m->next = mtl; + m->device = xstrdup(me.mnt_fsname); + m->dir = xstrdup(me.mnt_dir); + mtl = m; + } + endmntent(fp); + } + + /* If we're not umounting all, we need at least one argument. */ + if (!(opt & OPT_ALL)) { + m = 0; + if (!argc) bb_show_usage(); + } + + // Loop through everything we're supposed to umount, and do so. + for (;;) { + int curstat; + char *zapit = *argv; + + // Do we already know what to umount this time through the loop? + if (m) safe_strncpy(path, m->dir, PATH_MAX); + // For umount -a, end of mtab means time to exit. + else if (opt & OPT_ALL) break; + // Get next command line argument (and look it up in mtab list) + else if (!argc--) break; + else { + argv++; + realpath(zapit, path); + for (m = mtl; m; m = m->next) + if (!strcmp(path, m->dir) || !strcmp(path, m->device)) + break; + } + // If we couldn't find this sucker in /etc/mtab, punt by passing our + // command line argument straight to the umount syscall. Otherwise, + // umount the directory even if we were given the block device. + if (m) zapit = m->dir; + + // Let's ask the thing nicely to unmount. + curstat = umount(zapit); + + // Force the unmount, if necessary. + if (curstat && doForce) { + curstat = umount2(zapit, doForce); + if (curstat) + bb_error_msg("forced umount of %s failed!", zapit); + } + + // If still can't umount, maybe remount read-only? + if (curstat && (opt & OPT_REMOUNT) && errno == EBUSY && m) { + curstat = mount(m->device, zapit, NULL, MS_REMOUNT|MS_RDONLY, NULL); + bb_error_msg(curstat ? "cannot remount %s read-only" : + "%s busy - remounted read-only", m->device); + } + + if (curstat) { + status = EXIT_FAILURE; + bb_perror_msg("cannot umount %s", zapit); + } else { + /* De-allocate the loop device. This ioctl should be ignored on + * any non-loop block devices. */ + if (ENABLE_FEATURE_MOUNT_LOOP && !(opt & OPT_DONTFREELOOP) && m) + del_loop(m->device); + if (ENABLE_FEATURE_MTAB_SUPPORT && !(opt & OPT_NO_MTAB) && m) + erase_mtab(m->dir); + } + + // Find next matching mtab entry for -a or umount /dev + // Note this means that "umount /dev/blah" will unmount all instances + // of /dev/blah, not just the most recent. + while (m && (m = m->next)) + if ((opt & OPT_ALL) || !strcmp(path, m->device)) + break; + } + + // Free mtab list if necessary + + if (ENABLE_FEATURE_CLEAN_UP) { + while (mtl) { + m = mtl->next; + free(mtl->device); + free(mtl->dir); + free(mtl); + mtl = m; + } + } + + return status; +} -- 2.25.1